【问题标题】:Binding on UserControl in one UserControl instance syncing with another instance在与另一个实例同步的一个 UserControl 实例中绑定 UserControl
【发布时间】: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();
    }
}

注意事项:

  1. 两个实例中的 FolderViews 中的 DataGrids 也填充了相同的项目。
  2. 每个 FolderView 实例的路径属性都不同。
  3. 这仅在使用项目填充第二个实例,然后尝试填充第一个实例后才会发生。如果我首先填充第一个实例,则第二个实例根本不会发生任何事情。
  4. 当我说两个实例填充相同的项目时,我的意思是如果我填充第一个,第一个的项目会出现在第二个中。如果我填充第二个,第二个的项目会出现在第一个中。
  5. 另外,当我说“填充”时,我的意思是我将 Path 属性设置为 FolderView。

我尝试过的事情:

  1. 更改绑定方式。例如,我不会像Binding ElementName=explorer, Path=Property 那样绑定,而是将其更改为Binding Property, RelativeSource={RelativeSource AncestorType={x:Type local:UserControlType}}
  2. 从各种元素中删除x:Name 属性。
  3. 转储 FolderView 的属性/值。上面来源中的一个示例。

老实说,我不知道如何调试。这是一个错误还是我的绑定逻辑不那么合乎逻辑?

编辑

以下是我显示两个 Explorer 实例的方式:

<local:LocalExplorer />
<local:RemoteExplorer/>

鉴于它们都是自己的实例,我看不出它们如何错误地绑定到另一个实例,尤其是考虑到 ListBox 在可视化树中嵌套的深度。

【问题讨论】:

    标签: c# wpf binding user-controls


    【解决方案1】:

    问题在于Items属性的依赖属性注册。

    public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items",
    typeof(ObservableCollection<BindablePath>), typeof(FolderView),
    new FrameworkPropertyMetadata(new ObservableCollection<BindablePath>(), 
                         FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    

    如您所见,注册是静态的,将在 Type 而不是实例上注册。由于提供的默认值为new ObservableCollection&lt;BindablePath&gt;(),相同的实例将在FolderView 的所有实例之间共享。这就是为什么每当添加任何新项目时,它都会显示在所有实例中,因为简而言之,Items 属性指的是同一个实例。

    作为一个经验法则,您应该始终避免为 依赖属性注册期间的任何引用类型。


    解决方案

    将默认值设为 null,而是从 FolderView 的构造函数(每个实例)将其初始化为新实例。

    new FrameworkPropertyMetadata(null,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-18
      • 1970-01-01
      • 2013-02-05
      相关资源
      最近更新 更多