Wpf 如何使ItemsControl对同一列中的不同行使用不同的编辑器(取决于数据类型)?
我需要允许用户指定应用于搜索的过滤器。我希望UI看起来像这样:Wpf 如何使ItemsControl对同一列中的不同行使用不同的编辑器(取决于数据类型)?,wpf,templates,binding,datatemplate,itemscontrol,Wpf,Templates,Binding,Datatemplate,Itemscontrol,我需要允许用户指定应用于搜索的过滤器。我希望UI看起来像这样: (在上面的图片中,我手动键入“Test”并在ComboBox中选择“Both”,实际绑定不起作用) 所以,用户可以选择要应用的过滤器,并使用相应的编辑器(字符串的文本框、枚举的组合框等)指定值 为了创建这个问题,我使用了带有TemplateColumn、DataTriggers和DataTemplates的DataGrid(它不能完全按照我的需要工作,这就是我写这个问题的原因): 这种方法的问题是数据绑定在DataTemplate
(在上面的图片中,我手动键入“Test”并在ComboBox中选择“Both”,实际绑定不起作用) 所以,用户可以选择要应用的过滤器,并使用相应的编辑器(字符串的文本框、枚举的组合框等)指定值 为了创建这个问题,我使用了带有TemplateColumn、DataTriggers和DataTemplates的DataGrid(它不能完全按照我的需要工作,这就是我写这个问题的原因): 这种方法的问题是数据绑定在DataTemplate内部不起作用(至少在我的代码中,可能我做错了什么),我指的是这一部分:
<TextBox Text="{Binding SearchFilter.Value}"/>
当然,我可以手动创建一组控件(即不使用ItemsControl),但我需要一个通用的解决方案,因此我可以简单地获取一个过滤器对象列表,并获得一个完全正常工作的UI
请帮我解决我的任务。如果你在一个数据模板中,你需要挖一点通道。尝试:
<TextBox Text="{Binding SearchFilter.Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}" />
这将迫使绑定一直工作到DataGrid类型,然后使用该控件的DataContext作为绑定的上下文 您可以创建自己的数据模板,该模板将决定对哪个特定数据类型使用哪个DataTemplate
下面是一个数据模板选择器的示例:
class ValueCellTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate EnumTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is PositionSearchFilter)
{
PositionSearchFilter element = (PositionSearchFilter)item;
if (element.Value is string)
{
return this.StringTemplate;
}
else if (element.Value is MyEnumType)
{
return this.EnumTemplate;
}
}
return null;
}
}
您可以在资源中实例化此选择器,同时提供相应的DataTemplate
s:
<DataGrid.Resources>
<local:ValueCellTemplateSelector x:Key="ValueCellTemplateSelector">
<local:ValueCellTemplateSelector.StringTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}"/>
</DataTemplate>
</local:ValueCellTemplateSelector.StringTemplate>
<local:ValueCellTemplateSelector.EnumTemplate>
<DataTemplate>
<ComboBox ItemsSource="{wpf:EnumMembers dataModel:MyEnumType}" SelectedItem="{Binding Value}"/>
</DataTemplate>
</local:ValueCellTemplateSelector.EnumTemplate>
</local:ValueCellTemplateSelector>
</DataGrid.Resources>
最后,只需在列中设置此选择器:
<DataGridTemplateColumn CellTemplateSelector="{StaticResource ValueCellTemplateSelector}"/>
请注意,在我的示例中,DataTemplate
将根据Value
属性中包含的实际对象类型进行选择。因此,我们不需要filter类中额外的Type
属性。如果它对您很重要,只需相应地更改选择器。DataGridTemplateColumn不是DataGrid逻辑树或可视树的一部分。要解决此问题并在其中设置绑定的DataContext,可以使用绑定代理,如下所述:
然后在控件/窗口资源中定义代理的实例:
<UserControl.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</UserControl.Resources>
从DataGridTemplateColumn中,您可以引用绑定的代理:
<TextBox Text="{Binding SearchFilter.Value, Source={StaticResource proxy}}"/>
请注意,DataTemplateSelector
不会对更改事件做出响应,因此如果模板所基于的值可能会更改,DataTemplateSelector
将不会重新评估模板以供自己使用。@Rachel,是的,也许我应该问OP这是否重要。@dymanoid,非常感谢您的帮助和宝贵的时间!它起作用了!我准备将此标记为一个答案,除非有人建议可能更简单/更短的解决方案?尽管如此,我们有实际的代码(这里不仅仅是XAML)可能是好事,因为它可能提供额外的类型安全性,或者在重构过程中很有用。您通常看到正确的控件,但没有数据绑定到它,或者当您单击编辑单元格时,是否在单元格中看不到任何内容?我注意到的第一件事是这是一个CellTemplate
,而不是可能相关的CellEditingTemplate
。如果不更好地描述实际发生的情况,很难判断。我看到了正确的控件,但没有绑定到它的数据。因此,对DataTrigger本身的绑定是有效的(并且它设置了正确的DataTemplate),但在DataTemplate绑定内部不起作用。我建议使用一个工具,比如在运行时检查UI,并查看ContentControl
背后的DataContext是什么。我最好的猜测是ContentControl
是一种特殊情况,ContentTemplate的DataContext被设置为.Content属性本身,这可以解释绑定失败的原因。如果是这种情况,将.Content
属性绑定到DataContext应该可以解决问题:
@Rachel,我使用了Snoop,它表明ContentControl的DataContext已正确设置为预期的ViewModel。在那个ContentControl里面有ContentPresenter,里面是我的实际控件(例如TextBox)。ContentPresenter和实际控件都具有Null作为DataContext。如果我设置了,我认为ContentControl的特殊之处在于ContentTemplate将其DataContext设置为.Content
属性,因此,您必须确保为ContentTemplate中的绑定设置了正确显示:)这很可能会导致问题,因为它会将绑定的数据源更改为DataGrid.DataContext
,而该行不代表该项。对我不起作用:(我试图指定AncestorType={x:Type ContentControl}(因为我使用Snoop工具看到我的TextBox/ComboBox被放置在ContentControl中,ContentControl设置了正确的DataContext),但它也不起作用。@Rachel你学到的东西!我这里的方法不起作用,但不是因为它改变了数据源。DataGridTemplateColumn不是DataGrid的逻辑树或可视树的一部分,因此没有相关的资源可供查找。请参阅:这用于绑定DataGridTemplateColumn的属性的情况ode>到数据本身,例如.Header
属性。但它不适用于这种情况,因为我们没有在DataGridTemplateColumn
属性中的任何位置使用DataContext。Rachel,出于好奇,为了更好地理解事情是如何工作的,我们能在这种情况下以某种方式使这个技巧起作用吗?
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
<UserControl.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</UserControl.Resources>
<TextBox Text="{Binding SearchFilter.Value, Source={StaticResource proxy}}"/>