【发布时间】:2016-03-26 14:59:30
【问题描述】:
我有一个非常奇怪的问题,我几乎确信这是一个错误。
我有三个 UserControl,FolderView、LocalFolderView、RemoteFolderView。 LocalFolderView 和 RemoteFolderView 都继承了 FolderView 并分别用于另外两个控件 LocalExplorer 和 RemoteExplorer。
LocalExplorer/RemoteExplorer 有一个字符串列表,我将其绑定到 FolderView。
问题是每当我有超过 1 个 LocalExplorer/RemoteExplorer 实例时,两个 Explorer 的 FolderView 中的 ListBox 都会显示相同的项目,但控件的依赖属性似乎不同。
代码真的很长,所以我会尽量精简。目前,我认为问题在于我绑定事物的方式。
这是我有多个实例显示该错误的控件:
LocalExplorer.xaml(RemoteExplorer.xaml 遵循相同的模式):
<UserControl x:Class="LocalExplorer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyNamespace"
mc:Ignorable="d"
Width="Auto"
Height="Auto"
ClipToBounds="True"
x:Name="explorer">
<local:ExplorerBase Path="{Binding ElementName=explorer, Path=Path}" Orientation="{Binding ElementName=explorer, Path=Orientation}">
<local:ExplorerBase.FolderView>
<local:LocalFolderView x:Name="FolderView" Path="{Binding Path, RelativeSource={RelativeSource AncestorType={x:Type local:LocalExplorer}}}"/>
</local:ExplorerBase.FolderView>
</local:ExplorerBase>
</UserControl>
LocalExplorer.xaml.cs(RemoteExplorer.xaml.cs 遵循相同的模式):
public partial class Explorer : UserControl
{
#region Explorer
public Explorer()
{
InitializeComponent();
}
#endregion
#region Dependency Properties
public static DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(string), typeof(Explorer), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Path
{
get
{
return (string)GetValue(PathProperty);
}
set
{
SetValue(PathProperty, value);
}
}
#endregion
}
接下来是 ExplorerBase,其中包含特定于所有 Explorer 的 UI 逻辑:
ExplorerBase.cs:
public partial class ExplorerBase : Control
{
public ExplorerBase()
{
this.DefaultStyleKey = typeof(ExplorerBase);
}
public override void OnApplyTemplate()
{
base.ApplyTemplate();
}
public static readonly DependencyProperty FolderViewProperty = DependencyProperty.Register("FolderView", typeof(object), typeof(ExplorerBase), null);
public object FolderView
{
get
{
return GetValue(FolderViewProperty);
}
set
{
SetValue(FolderViewProperty, value);
}
}
public static DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(string), typeof(ExplorerBase), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Path
{
get
{
return (string)GetValue(PathProperty);
}
set
{
SetValue(PathProperty, value);
}
}
}
我使用 Themes/Generic.xaml 方法对其进行模板化:
<Style TargetType="{x:Type Imagin.Controls:ExplorerBase}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Imagin.Controls:ExplorerBase">
<ContentPresenter Content="{TemplateBinding FolderView}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
最后,FolderView,我相信它是有 bug 的。 FolderView 是实际使用的控件 LocalFolderView 和 RemoteFolderView 的基础。请注意,无论我是否同时使用 LocalExplorer 和 RemoteExplorer,或者两者都使用 1,都会出现该错误。我一次只测试了两个实例。
FolderView.xaml:
<UserControl x:Class="FolderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Height="Auto"
Width="Auto"
x:Name="folderView">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibility" />
<Imagin.Data:InverseBooleanToVisibilityConverter x:Key="InverseBoolToVisibility" />
<Grid>
<ListBox x:Name="ListBox" AllowDrop="True" ItemsSource="{Binding Path=Items, RelativeSource={RelativeSource AncestorType={x:Type local:FolderView}}}" SelectionMode="Extended" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:BindableFile}">
<local:Thumbnail FilePath="{Binding Path}" IsCheckBoxEnabled="False" ToolTip="{Binding ToolTip}" Title="{Binding Name}" Width="Auto" Height="Auto"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:BindableFolder}">
<local:Thumbnail FilePath="{Binding Path}" IsCheckBoxEnabled="False" ToolTip="{Binding ToolTip}" Title="{Binding Name}" Width="Auto" Height="Auto"/>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<DataGrid x:Name="DataGrid" ItemsSource="{Binding Items, RelativeSource={RelativeSource AncestorType={x:Type local:FolderView}}}" AutoGenerateColumns="False" BorderThickness="0" AlternationCount="2" GridLinesVisibility="None" HeadersVisibility="Column" CanUserAddRows="False" CanUserResizeColumns="True" IsSynchronizedWithCurrentItem="True" AllowDrop="True">
</DataGrid>
</Grid>
</UserControl>
FolderView.xaml.cs:
public abstract partial class FolderView : UserControl
{
#region DependencyProperties
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<BindablePath>), typeof(FolderView), new FrameworkPropertyMetadata(new ObservableCollection<BindablePath>(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public ObservableCollection<BindablePath> Items
{
get
{
return (ObservableCollection<BindablePath>)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
public static DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(string), typeof(FolderView), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPathChanged));
public string Path
{
get
{
return (string)GetValue(PathProperty);
}
set
{
SetValue(PathProperty, value);
}
}
private static void OnPathChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
{
FolderView FolderView = (FolderView)Object;
FolderView.Refresh();
FolderView.SearchTextBox.Text = string.Empty;
}
#endregion
#region Methods
public virtual void GetItems(string Path, out List<string> Folders, out List<string> Files)
{
Folders = default(List<string>);
Files = default(List<string>);
}
/// <summary>
/// Refreshes current path with contents.
/// </summary>
public virtual void Refresh()
{
//Used to debug property values at runtime. So far the values for each object instance are unique.
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(this))
{
string name = descriptor.Name;
object value = descriptor.GetValue(this);
Console.WriteLine("{0}={1}", name, value);
}
}
/// <summary>
/// Populates controls with actual items via binding. We must do this on UI thread. This occurs immediately after <Refresh()>.
/// </summary>
/// <param name="Folders"></param>
/// <param name="Files"></param>
public virtual void Populate(List<FtpListItem> Folders, List<FtpListItem> Files)
{
}
public virtual void Populate(List<string> Folders, List<string> Files)
{
}
#endregion
#region FolderView
public FolderView()
{
InitializeComponent();
}
#endregion
}
LocalFolderView.cs(RemoteFolderView.cs 遵循相同的模式):
public sealed class LocalFolderView : FolderView
{
public override void GetItems(string Path, out List<string> Folders, out List<string> Files)
{
//These are my own functions
Folders = Directory.GetDirectories(Path);
Files = Directory.GetFiles(Path, null);
}
public override void Populate(List<string> Folders, List<string> Files)
{
int NumFolders = Folders.Count, NumFiles = Files.Count;
this.IsEmpty = NumFolders == 0 && NumFiles == 0 ? true : false;
if (Folders == null || Files == null || (NumFolders == 0 && NumFiles == 0)) return;
for (int j = 0, Count = NumFolders; j < Count; j++)
{
this.Items.Add(new BindableFolder(Folders[j]));
}
for (int j = 0, Count = NumFiles; j < Count; j++)
{
this.Items.Add(new BindableFile(Files[j]));
}
}
public override void Refresh()
{
base.Refresh();
this.Items.Clear();
//If directory doesn't exist, we don't want to enter it.
if (!System.IO.Directory.Exists(this.Path)) return;
List<string> Folders = null;
List<string> Files = null;
string CurrentPath = this.Path;
BackgroundWorker Worker = new BackgroundWorker();
Worker.DoWork += (s, e) =>
{
this.GetItems(CurrentPath, out Folders, out Files);
};
Worker.RunWorkerCompleted += (s, e) =>
{
//Start populating items
var DispatcherOperation = Application.Current.Dispatcher.BeginInvoke(new Action(() => this.Populate(Folders, Files)));
};
Worker.RunWorkerAsync();
}
}
注意事项:
- 两个实例中的 FolderViews 中的 DataGrids 也填充了相同的项目。
- 每个 FolderView 实例的路径属性都不同。
- 这仅在使用项目填充第二个实例,然后尝试填充第一个实例后才会发生。如果我首先填充第一个实例,则第二个实例根本不会发生任何事情。
- 当我说两个实例填充相同的项目时,我的意思是如果我填充第一个,第一个的项目会出现在第二个中。如果我填充第二个,第二个的项目会出现在第一个中。
- 另外,当我说“填充”时,我的意思是我将
Path属性设置为 FolderView。
我尝试过的事情:
- 更改绑定方式。例如,我不会像
Binding ElementName=explorer, Path=Property那样绑定,而是将其更改为Binding Property, RelativeSource={RelativeSource AncestorType={x:Type local:UserControlType}}。 - 从各种元素中删除
x:Name属性。 - 转储 FolderView 的属性/值。上面来源中的一个示例。
老实说,我不知道如何调试。这是一个错误还是我的绑定逻辑不那么合乎逻辑?
编辑
以下是我显示两个 Explorer 实例的方式:
<local:LocalExplorer />
<local:RemoteExplorer/>
鉴于它们都是自己的实例,我看不出它们如何错误地绑定到另一个实例,尤其是考虑到 ListBox 在可视化树中嵌套的深度。
【问题讨论】:
标签: c# wpf binding user-controls