WPF自定义控件:将CollectionViewSource绑定到DependencyProperty

3
我有一个包含ComboBox的WPF自定义控件。我想通过CollectionViewSource将此ComboBox的ItemsSource绑定到自定义控件类的Dependency Property,但我无法弄清楚如何使CollectionViewSource识别正确的DataContext(在这种情况下是我的自定义控件属性)。
我谷歌了很多,阅读了几乎所有SO自定义控件/CollectionViewSource/Collection binding/dependency properties binding等问题,并尝试了一些类似问题的解决方案,如this onethis以及some more,但仍然不起作用。我可能错过了什么,但我不知道是什么。
以下是自定义控件类的相关部分:
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 Warning: 60 : BindingExpression (hash=28932383): 默认模式解析为单向绑定

System.Windows.Data Warning: 61 : BindingExpression (hash=28932383): 默认更新触发器解析为属性更改

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

System.Windows.Data Warning: 66 : BindingExpression (hash=28932383): 相对源 (FindAncestor) 需要树上下文

System.Windows.Data Warning: 65 : BindingExpression (hash=28932383): 延迟解析源

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

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

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

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

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

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

System.Windows.Data Error: 4 : 找不到绑定引用的源'RelativeSource FindAncestor, AncestorType='MyNamespace.CustomComboBox', AncestorLevel='1''. BindingExpression:Path=CustomItems; DataItem=null; 目标元素为 'CollectionViewSource' (HashCode=23914501); 目标属性为 'Source' (类型 'Object')

我首先尝试了这个绑定:
<CollectionViewSource x:Key="GroupedData"
                          Source="{Binding CustomItems}">

但是我遇到了以下错误:

System.Windows.Data Error: 2 : 找不到目标元素的主要FrameworkElement或FrameworkContentElement。 BindingExpression:Path=CustomItems; DataItem=null; 目标元素为 'CollectionViewSource' (HashCode=37908782); 目标属性为 'Source' (类型为'Object')

顺便提一下,ComboBox内部绑定到其他属性也可以正常工作,比如Text绑定。

3个回答

6

虽然@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无关,这使您可以在任何数据上下文中使用它,而不会破坏控件的功能

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


DataContext = this; 如何防止数据上下文继承?@Grx70 - Kylo Ren
通常情况下,您会为Window(或UserControl)设置一些视图模型作为DataContext,并期望它对您定义的所有控件都可用。但是,在这种情况下,如果您设置DataContext = this,则CustomComboBox(以及其所有可视后代)将不再继承视图模型作为数据上下文。这意味着您将无法将任何属性绑定到视图模型属性,除非显式指定绑定源,或将DataContext设置为适当的值。 - Grx70
看了一下 local:CustomComboBox 的模板,我觉得 OP 不打算绑定任何属性到 ViewModel 的属性上。所以他一定是故意这样做的。但总的来说,我完全同意你的想法,即它如何防止数据上下文的继承问题。只是我有点怀疑你是不是还有别的意思。 - Kylo Ren
我更关注绑定CustomComboBox属性而不是模板元素的属性。使用“传统”方式,OP将无法绑定CustomItems属性或其他经常绑定的属性(如VisibilityIsEnabled)。 - Grx70

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;
    }

输出:

自定义组合


1

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

我故意保持它非常简单,没有视图模型、分组数据(如您的示例中所示)等。如果我误解了您的问题,请留言,我会尝试解决它 :)

使用方法如下:

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

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接