WPF 自定义控件:将 CollectionViewSource 绑定到 DependencyProperty
WPF Custom Control: Bind CollectionViewSource to DependencyProperty
我有一个 WPF custom Control
,其中包含一个 ComboBox
。我想通过 CollectionViewSource
将此 ComboBox 的 ItemsSource
绑定到自定义控件 class 的 Dependency Property
,但我不知道如何制作CollectionViewSource 识别正确的 DataContext
(在本例中为我的自定义控件属性)。
我在谷歌上搜索了很多,几乎阅读了所有 SO 自定义 control\CollectionViewSource\Collection binding\dependency 属性 binding\etc。问题并尝试了一些类似问题的解决方案,例如 this one and this and some more 但它仍然无法正常工作。我可能错过了什么,但我不知道是什么。
这里是自定义控件的相关部分class:
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: 58 : Path:'CustomItems'
System.Windows.Data Warning: 60 : BindingExpression (hash=28932383): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=28932383): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=28932383): Attach to System.Windows.Data.CollectionViewSource.Source (hash=23914501)
System.Windows.Data Warning: 66 : BindingExpression (hash=28932383): RelativeSource (FindAncestor) requires tree context
System.Windows.Data Warning: 65 : BindingExpression (hash=28932383): Resolve source deferred
System.Windows.Data Warning: 67 : BindingExpression (hash=28932383): Resolving source
System.Windows.Data Warning: 70 : BindingExpression (hash=28932383): Found data context element: (OK)
System.Windows.Data Warning: 67 : BindingExpression (hash=28932383): Resolving source
System.Windows.Data Warning: 70 : BindingExpression (hash=28932383): Found data context element: (OK)
System.Windows.Data Warning: 67 : BindingExpression (hash=28932383): Resolving source (last chance)
System.Windows.Data Warning: 70 : BindingExpression (hash=28932383): Found data context element: (OK)
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='MyNamespace.CustomComboBox', AncestorLevel='1''. BindingExpression:Path=CustomItems; DataItem=null; target element is 'CollectionViewSource' (HashCode=23914501); target property is 'Source' (type 'Object')
我首先尝试了这个绑定:
<CollectionViewSource x:Key="GroupedData"
Source="{Binding CustomItems}">
但是我得到了错误:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement
or FrameworkContentElement for target element.
BindingExpression:Path=CustomItems; DataItem=null; target element is
'CollectionViewSource' (HashCode=37908782); target property is
'Source' (type 'Object')
BTW- 在 ComboBox 内部绑定到另一个属性的工作方式 - 就像 Text
绑定一样。
我在这里创建了一个解决方案:https://github.com/orhtun/WPFCustomControlBinding
我故意保持它非常简单,没有视图模型、GroupedData(如您的示例)等。如果我弄错了您的问题,请发表评论,我会尽力解决它:)
用法是这样的;
<wpfCustomControlBinding:CustomControl Grid.Row="0" Grid.Column="0" CustomItems="{Binding DataStringList}"></wpfCustomControlBinding:CustomControl>
我看到你的代码中有几个问题,你为什么要这样设计?但是我会跳过所有那部分(你一定有理由这样做比如设置DataContext
,初始化CustomItems
等),只需编辑 XAML
即可。要使 Binding
正常工作,您需要了解 WPF
中 Inheritance Context 的概念。 Google 你会发现很多关于它的东西。据此,我已将您的代码更改如下:
<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;
}
输出:
尽管@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>
以下是此解决方案的一些优点:
- 它完全包含在模板中,因此:
- 更改模板不会留下任何不必要的资源and/or对新模板有任何影响
- 它完全可以重复使用 "out-of-the-box",即它不需要任何额外的设置(例如设置属性或添加资源)
- 它是
DataContext
独立的,这使您可以在任何数据上下文中使用它而不会破坏控件的功能
请注意,要使后者成立,您应该修改模板中的绑定以使用 RelativeSource={RelativeSource TemplatedParent}
(或在适用的情况下使用 TemplateBinding
)。另外,我建议从构造函数中删除 DataContext = this;
行,因为它会阻止数据上下文继承。
我有一个 WPF custom Control
,其中包含一个 ComboBox
。我想通过 CollectionViewSource
将此 ComboBox 的 ItemsSource
绑定到自定义控件 class 的 Dependency Property
,但我不知道如何制作CollectionViewSource 识别正确的 DataContext
(在本例中为我的自定义控件属性)。
我在谷歌上搜索了很多,几乎阅读了所有 SO 自定义 control\CollectionViewSource\Collection binding\dependency 属性 binding\etc。问题并尝试了一些类似问题的解决方案,例如 this one and this and some more 但它仍然无法正常工作。我可能错过了什么,但我不知道是什么。
这里是自定义控件的相关部分class:
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: 58 : Path:'CustomItems'
System.Windows.Data Warning: 60 : BindingExpression (hash=28932383): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=28932383): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=28932383): Attach to System.Windows.Data.CollectionViewSource.Source (hash=23914501)
System.Windows.Data Warning: 66 : BindingExpression (hash=28932383): RelativeSource (FindAncestor) requires tree context
System.Windows.Data Warning: 65 : BindingExpression (hash=28932383): Resolve source deferred
System.Windows.Data Warning: 67 : BindingExpression (hash=28932383): Resolving source
System.Windows.Data Warning: 70 : BindingExpression (hash=28932383): Found data context element: (OK)
System.Windows.Data Warning: 67 : BindingExpression (hash=28932383): Resolving source
System.Windows.Data Warning: 70 : BindingExpression (hash=28932383): Found data context element: (OK)
System.Windows.Data Warning: 67 : BindingExpression (hash=28932383): Resolving source (last chance)
System.Windows.Data Warning: 70 : BindingExpression (hash=28932383): Found data context element: (OK)
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='MyNamespace.CustomComboBox', AncestorLevel='1''. BindingExpression:Path=CustomItems; DataItem=null; target element is 'CollectionViewSource' (HashCode=23914501); target property is 'Source' (type 'Object')
我首先尝试了这个绑定:
<CollectionViewSource x:Key="GroupedData"
Source="{Binding CustomItems}">
但是我得到了错误:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=CustomItems; DataItem=null; target element is 'CollectionViewSource' (HashCode=37908782); target property is 'Source' (type 'Object')
BTW- 在 ComboBox 内部绑定到另一个属性的工作方式 - 就像 Text
绑定一样。
我在这里创建了一个解决方案:https://github.com/orhtun/WPFCustomControlBinding
我故意保持它非常简单,没有视图模型、GroupedData(如您的示例)等。如果我弄错了您的问题,请发表评论,我会尽力解决它:)
用法是这样的;
<wpfCustomControlBinding:CustomControl Grid.Row="0" Grid.Column="0" CustomItems="{Binding DataStringList}"></wpfCustomControlBinding:CustomControl>
我看到你的代码中有几个问题,你为什么要这样设计?但是我会跳过所有那部分(你一定有理由这样做比如设置DataContext
,初始化CustomItems
等),只需编辑 XAML
即可。要使 Binding
正常工作,您需要了解 WPF
中 Inheritance Context 的概念。 Google 你会发现很多关于它的东西。据此,我已将您的代码更改如下:
<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;
}
输出:
尽管@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>
以下是此解决方案的一些优点:
- 它完全包含在模板中,因此:
- 更改模板不会留下任何不必要的资源and/or对新模板有任何影响
- 它完全可以重复使用 "out-of-the-box",即它不需要任何额外的设置(例如设置属性或添加资源)
- 它是
DataContext
独立的,这使您可以在任何数据上下文中使用它而不会破坏控件的功能
请注意,要使后者成立,您应该修改模板中的绑定以使用 RelativeSource={RelativeSource TemplatedParent}
(或在适用的情况下使用 TemplateBinding
)。另外,我建议从构造函数中删除 DataContext = this;
行,因为它会阻止数据上下文继承。