【问题标题】:WPF Custom Control: Bind CollectionViewSource to DependencyPropertyWPF 自定义控件:将 CollectionViewSource 绑定到 DependencyProperty
【发布时间】:2016-05-02 10:24:36
【问题描述】:

我有一个WPF custom Control,其中包含一个ComboBox。我想通过CollectionViewSource将此组合框的ItemsSource绑定到自定义控件类的Dependency Property,但我不知道如何让CollectionViewSource识别正确的DataContext(我的自定义控制属性,在这种情况下)。

我搜索了很多,阅读了几乎所有 SO 自定义控件\CollectionViewSource\Collection binding\dependency properties binding\etc。问题并尝试了一些类似问题的解决方案,例如this onethissomemore,但它仍然无法正常工作。我可能错过了什么,但我不知道是什么。

以下是自定义控件类的相关部分:

public class CustomComboBox : Control
{
   static CustomComboBox()
   {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomComboBox), new FrameworkPropertyMetadata(typeof(CustomComboBox)));
   }

   public CustomComboBox()
   {
      CustomItems = new ObservableCollection<ComboBoxItem>();
      DataContext = this;
   }

   internal ObservableCollection<ComboBoxItem> CustomItems 
   {
      get { return (ObservableCollection<ComboBoxItem>)GetValue(CustomItemsProperty); }
      set { SetValue(CustomItemsProperty, value); }
   }

   // Using a DependencyProperty as the backing store for CustomItems.  This enables animation, styling, binding, etc...
   public static readonly DependencyProperty CustomItemsProperty =
            DependencyProperty.Register("CustomItems", typeof(ObservableCollection<ComboBoxItem>), typeof(CustomComboBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

   private string text;
   public string Text
   {
       get { return text; }
       set
       {
           text = value;
           OnPropertyChanged("Text");
       }
   }

  // More properties, events and functions...

}

以及xaml代码的相关部分:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MyNamespace"
                    xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
                    x:Class="CustomComboBox">

<CollectionViewSource x:Key="GroupedData"
                      Source="{Binding Path=CustomItems, RelativeSource={RelativeSource FindAncestor, AncestorType=local:CustomComboBox}, diag:PresentationTraceSources.TraceLevel=High}">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="GroupName" />
        </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

<!-- ...Some more Styles and DataTemplates... -->

<Style TargetType="{x:Type local:CustomComboBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomComboBox}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <ComboBox IsEditable="True"
                                  Text="{Binding Text}"
                                  ItemsSource="{Binding Source={StaticResource GroupedData}}">
                                  <!-- ...Some more properties... --> 
                        </ComboBox>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

我从System.Diagnostics得到这个输出:

System.Windows.Data 警告:58:路径:'CustomItems'

System.Windows.Data 警告:60:BindingExpression (hash=28932383):默认模式解析为 OneWay

System.Windows.Data 警告:61:BindingExpression (hash=28932383):默认更新触发器解析为 PropertyChanged

System.Windows.Data 警告:62:BindingExpression (hash=28932383):附加到 System.Windows.Data.CollectionViewSource.Source (hash=23914501)

System.Windows.Data 警告:66:BindingExpression (hash=28932383):RelativeSource (FindAncestor) 需要树上下文

System.Windows.Data 警告:65 : BindingExpression (hash=28932383): Resolve source deferred

System.Windows.Data 警告:67:BindingExpression (hash=28932383):解析源

System.Windows.Data 警告:70:BindingExpression (hash=28932383):找到数据上下文元素:(OK)

System.Windows.Data 警告:67:BindingExpression (hash=28932383):解析源

System.Windows.Data 警告:70:BindingExpression (hash=28932383):找到数据上下文元素:(OK)

System.Windows.Data 警告:67:BindingExpression (hash=28932383):解析源(最后机会)

System.Windows.Data 警告:70:BindingExpression (hash=28932383):找到数据上下文元素:(OK)

System.Windows.Data 错误:4:找不到与引用“RelativeSource FindAncestor,AncestorType='MyNamespace.CustomComboBox',AncestorLevel='1''的绑定源。 BindingExpression:Path=CustomItems;数据项=空;目标元素是“CollectionViewSource”(HashCode=23914501);目标属性是“源”(类型“对象”)

我首先尝试了这个绑定:

<CollectionViewSource x:Key="GroupedData"
                          Source="{Binding CustomItems}">

然后我得到错误:

System.Windows.Data 错误:2:找不到管理 FrameworkElement 或 FrameworkContentElement 为目标元素。 BindingExpression:Path=CustomItems;数据项=空;目标元素是 'CollectionViewSource' (HashCode=37908782);目标属性是 “源”(类型“对象”)

顺便说一句-在 ComboBox 内绑定到另一个属性的工作原理-类似于Text 绑定。

【问题讨论】:

    标签: c# wpf data-binding custom-controls dependency-properties


    【解决方案1】:

    虽然@KyloRen 的回答通常是正确的(Inheritance Context 特别有用),但我想提供替代解决方案。关键点是有问题的CollectionViewSource 应该被定义为FrameworkElement 的资源,它是模板可视化树的一部分(在我看来,根元素是一个不错的选择)。这是模板:

    <ControlTemplate TargetType="{x:Type local:CustomComboBox}">
        <Border (...)>
            <Border.Resources>
                <CollectionViewSource x:Key="GroupedData"
                                      Source="{Binding CustomItems, RelativeSource={RelativeSource TemplatedParent}}">
                    (...)
                </CollectionViewSource>
            </Border.Resources>
            <ComboBox IsEditable="True"
                      Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}}"
                      ItemsSource="{Binding Source={StaticResource GroupedData}}">
                (...)
            </ComboBox>
        </Border>
    </ControlTemplate>
    

    以下是此解决方案的一些优点:

    • 它完全包含在模板中,因此:
      • 更改模板不会留下任何不必要的资源和/或对新模板产生任何影响
      • 它是完全可重复使用的“开箱即用”,即它不需要任何额外的设置(例如设置属性或添加资源)
    • 它是DataContext-independent,它允许您在任何数据上下文中使用它而不会破坏控件的功能

    请注意,要使后者为真,您应该修改模板中的绑定以使用RelativeSource={RelativeSource TemplatedParent}(或在适用的情况下使用TemplateBinding)。另外,我建议从构造函数中删除 DataContext = this; 行,因为它会阻止数据上下文继承。

    【讨论】:

    • DataContext = this;防止数据上下文继承。@Grx70?
    • 通常的情况是你有一个Window(或UserControl),你将一些视图模型设置为DataContext。然后,您希望它可用于您定义的所有控件。但是在这种情况下,如果您设置DataContext = thisCustomComboBox(以及它所有的视觉后代)将不再继承视图模型作为数据上下文。这意味着如果不明确指定绑定源或将DataContext 设置为适当的值,您将无法将任何属性绑定到视图模型属性。
    • 查看 local:CustomComboBox 的模板,我认为 OP 不希望将任何属性绑定到 ViewModel 的属性。所以他一定是故意的。但我完全同意你在一般意义上的观点,即它如何防止数据上下文继承。我只是怀疑你是否还有别的意思。
    • 我更关心绑定CustomComboBox 属性而不是模板元素的属性。 OP 将无法使用“传统”方式绑定CustomItems 属性,也无法绑定其他经常绑定的属性(如VisibilityIsEnabled)。
    【解决方案2】:

    我在您的代码中发现了一些问题,您为什么要这样设计?但是我会跳过所有这部分(你必须有一些理由这样做,比如设置DataContext,初始化CustomItems 等),然后编辑XAML 让它工作。要使您的Binding 工作,您需要了解WPF 中的Inheritance Context 的概念。谷歌它你会发现很多关于它的东西。据此,我已将您的代码更改如下:

    <local:CustomComboBox>
        <local:CustomComboBox.Resources>
            <CollectionViewSource x:Key="GroupedData"
                      Source="{Binding Path=CustomItems}">
                <CollectionViewSource.GroupDescriptions>
                    <PropertyGroupDescription PropertyName="GroupName" />
                </CollectionViewSource.GroupDescriptions>
            </CollectionViewSource>
        </local:CustomComboBox.Resources>
        <local:CustomComboBox.Style>
            <Style TargetType="{x:Type local:CustomComboBox}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type local:CustomComboBox}">
                            <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                                <ComboBox IsEditable="True"
                                  Text="{Binding Text}"
                                  ItemsSource="{Binding Source={StaticResource GroupedData}}">                                    
                                </ComboBox>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </local:CustomComboBox.Style>
    </local:CustomComboBox>
    

    而且它有效。上面并不完美,但我只是做了一些微小的改动(改变结构和绑定)以使上面的代码正常工作。还添加了DataSource 为:

     public CustomComboBox()
        {
            CustomItems = new ObservableCollection<ComboBoxItem>();
            CustomItems.Add(new ComboBoxItem() { Content = "4" });
            CustomItems.Add(new ComboBoxItem() { Content = "5" });
            DataContext = this;
        }
    

    输出:

    【讨论】:

      【解决方案3】:

      我在这里创建了一个解决方案:https://github.com/orhtun/WPFCustomControlBinding

      我故意保持它非常简单,没有 View Models、GroupedData(如您的示例)等。如果我把您的问题弄错了,请发表评论,我会尽力解决它 :)

      用法是这样的;

       <wpfCustomControlBinding:CustomControl Grid.Row="0" Grid.Column="0" CustomItems="{Binding DataStringList}"></wpfCustomControlBinding:CustomControl>
      

      【讨论】:

        猜你喜欢
        • 2011-08-06
        • 1970-01-01
        • 2013-01-20
        • 2019-07-27
        • 2011-01-18
        • 1970-01-01
        • 1970-01-01
        • 2021-07-10
        • 1970-01-01
        相关资源
        最近更新 更多