具有基于类型的数据模板选择和绑定的 Contentpresenter

Mar*_*ijn 5 c# wpf xaml binding datatemplate

我有一个绑定到项目列表的 ItemsControl。这些项目具有名称和值属性。value 属性属于 Object 类型,以允许使用不同的数据类型。为了正确显示 value 属性,我将 ContentPresenter 与我可能使用的每种数据类型的数据模板一起使用。

  <ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

                <TextBlock Text="{Binding Path=Name}"/>

                <GridSplitter Width="1" 
                              Grid.RowSpan="4" Grid.Column="1" 
                              HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

                <ContentPresenter Grid.Column="2" Content="{Binding Value}">
                    <ContentPresenter.Resources>
                        <DataTemplate DataType="{x:Type System:String}">
                            <TextBox Text="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}" 
                                     BorderThickness="0"/>
                        </DataTemplate>
                        <DataTemplate DataType="{x:Type System:Int32}">
                            <TextBox Text="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}" 
                                     TextAlignment="Right"
                                     BorderThickness="0"/>
                        </DataTemplate>
                        <DataTemplate DataType="{x:Type System:Double}">
                            <TextBox Text="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}" 
                                     TextAlignment="Right"
                                     BorderThickness="0"/>
                        </DataTemplate>
                        <DataTemplate DataType="{x:Type System:Boolean}">
                            <CheckBox IsChecked="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}"
                                          HorizontalAlignment="Center"/>
                        </DataTemplate>
                    </ContentPresenter.Resources>
                </ContentPresenter>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
Run Code Online (Sandbox Code Playgroud)

ContentPresenter 使用正确的数据类型并且效果很好。我的问题是编辑这些值对绑定的项目没有任何影响。我怀疑这是因为我绑定到 ContentPresenter 的内容属性而不是直接绑定到值。我试过像这样使用 ContentPresenter:

<ContentPresenter Grid.Column="2" Content="{Binding}">
    <ContentPresenter.Resources>
        <DataTemplate DataType="{x:Type System:String}">
            <TextBox Text="{Binding Value}" 
                 BorderThickness="0"/>
        </DataTemplate>
Run Code Online (Sandbox Code Playgroud)

但是这样就不会选择正确的 DataTemplate 并且它只是显示对象而不是例如字符串。我还尝试像这样省略 DataTemplate 绑定中的路径:

 <DataTemplate DataType="{x:Type System:String}">
    <TextBox Text="{Binding}" BorderThickness="0"/>
 </DataTemplate>
Run Code Online (Sandbox Code Playgroud)

有了这个,我得到一个异常,告诉我使用 Path 或 XPath 属性。

所以我的问题是:我如何正确绑定到 Value 以便它显示正确的 DataTemplate 并且对值的任何编辑都应用于绑定项目。

顺便说一句,由于某种原因,我的问题中的格式化代码块在第一行之后缩进了更多。我尝试修复它,但我不明白发生了什么。

She*_*dan 6

我认为你会从阅读关于DataTemplates 中受益。首先,我建议您仔细阅读MSDN上的数据模板概述页面。正如我在我的评论中提到,你应该使用ContentPresenterDataTemplate秒。从链接页面:

您通常使用 ContentControl 的 ControlTemplate 中的 ContentPresenter 来指定要添加内容的位置

你的东西似乎是丢失的是如何和什么将数据绑定到从里面一个DataTemplate。所述DataContextDataTemplate将被自动设定为在所指定的类型的实例DataType属性。因此,DataTemplate可以访问的属性也将取决于DataType属性中指定的类型。例如,您不能这样做,因为 astring没有Value属性。

<DataTemplate DataType="{x:Type System:String}">
    <TextBox Text="{Binding Value}" BorderThickness="0" />
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)

相反,对于 a string,您需要DataContext像这样将数据绑定到整个值:

<DataTemplate DataType="{x:Type System:String}">
    <TextBox Text="{Binding}" BorderThickness="0" />
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)

或者,如果您有一个类名SomeClass并且该类有一个Value属性,那么您可以这样做:

<DataTemplate DataType="{x:Type YourDataTypesPrefix:SomeClass}">
    <TextBox Text="{Binding Value}" BorderThickness="0"/>
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)

现在,因为这些DataTemplates 是在没有设置它们的x:Key值的情况下定义的,所以DataTemplate只要框架看到相关类型的对象(并且没有设置其他显式模板),它就会自动呈现每个的内容。所以试试这个,如果你仍然有问题,请告诉我。


Mar*_*ijn 5

我已经对我的解决方案感到不舒服,我还遇到了无法将 List DataType 添加到 DataTemplates 的问题。我最终使用了一个 DataTemplateSelector,它产生了更好的代码。这里是:

内容控件。应用 DataTemplate 的数据的容器:

<ContentControl Grid.Column="2" Content="{Binding}"
                ContentTemplateSelector="{StaticResource propertyItemTemplateSelector}">
</ContentControl>
Run Code Online (Sandbox Code Playgroud)

一些 DataTemplates 和 DataTemplateSelector 的声明:

<Style.Resources>
    <local:PropertyTemplateSelector x:Key="propertyItemTemplateSelector"/>
    <DataTemplate x:Key="dtStringValue">
        <TextBox Text="{Binding Path=Value}"
                 BorderThickness="0"
                 IsReadOnly="{Binding Path=IsReadOnly}">
        </TextBox>
    </DataTemplate>

    <DataTemplate x:Key="dtIntegerValue">
        <TextBox Text="{Binding Path=Value}"
                 TextAlignment="Right"
                 BorderThickness="0"
                 IsReadOnly="{Binding Path=IsReadOnly}"/>
    </DataTemplate>
...
Run Code Online (Sandbox Code Playgroud)

DataTemplateSelector 的代码:

 public class PropertyTemplateSelector : DataTemplateSelector
 {
    public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
    {
        DataTemplate template = null;

        IPropertyItem propertyItem = item as IPropertyItem;

        if (propertyItem != null)
        {
            FrameworkElement element = container as FrameworkElement;
            if (element != null)
            {
                var value = propertyItem.Value;

                if (value is String)
                {
                    template = element.FindResource("dtStringValue") as DataTemplate;
                }
                else if (value is Int32)
                {
                    template = element.FindResource("dtIntegerValue") as DataTemplate;
                }
                 ....
Run Code Online (Sandbox Code Playgroud)