Wpf 如何使ItemsControl对同一列中的不同行使用不同的编辑器(取决于数据类型)?

Wpf 如何使ItemsControl对同一列中的不同行使用不同的编辑器(取决于数据类型)?,wpf,templates,binding,datatemplate,itemscontrol,Wpf,Templates,Binding,Datatemplate,Itemscontrol,我需要允许用户指定应用于搜索的过滤器。我希望UI看起来像这样: (在上面的图片中,我手动键入“Test”并在ComboBox中选择“Both”,实际绑定不起作用) 所以,用户可以选择要应用的过滤器,并使用相应的编辑器(字符串的文本框、枚举的组合框等)指定值 为了创建这个问题,我使用了带有TemplateColumn、DataTriggers和DataTemplates的DataGrid(它不能完全按照我的需要工作,这就是我写这个问题的原因): 这种方法的问题是数据绑定在DataTemplate

我需要允许用户指定应用于搜索的过滤器。我希望UI看起来像这样:


(在上面的图片中,我手动键入“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}}"/>