基于类型的DataTemplate选择和绑定的ContentPresenter

5

我有一个ItemsControl绑定到一组项目。这些项目具有名称和值属性。值属性是Object类型,以允许使用不同的数据类型。为了正确显示值属性,我使用ContentPresenter,对于可能使用的每种数据类型都有一个datatemplate。

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

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

<ContentPresenter Grid.Column="2" Content="{Binding}">
    <ContentPresenter.Resources>
        <DataTemplate DataType="{x:Type System:String}">
            <TextBox Text="{Binding Value}" 
                 BorderThickness="0"/>
        </DataTemplate>

但这种方法不会选中正确的DataTemplate,它只会显示对象而不是字符串。我也尝试了在DataTemplate的绑定中省略路径,像这样:
 <DataTemplate DataType="{x:Type System:String}">
    <TextBox Text="{Binding}" BorderThickness="0"/>
 </DataTemplate>

我使用这个方法时出现了一个异常,告诉我要使用Path或XPath属性。

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

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


将来请格式化您的代码,以便它可以被看到,而不是大部分都在屏幕外。提示:如果您在编辑时选择代码,则可以单击“代码示例”按钮将其全部向前或向后移动。 - Sheridan
你说 为了正确显示value属性,我使用了一个ContentPresenter,它包含了用于每种可能使用的数据类型的datatemplate... 但是 这不是正确显示的方法... 一般来说,ContentPresenter 不应该在 ControlTemplate 之外使用。从链接页面上可以看到:通常情况下,你会在ContentControl的ControlTemplate中使用ContentPresenter来指定内容添加的位置 - Sheridan
3个回答

7
我认为你阅读有关 DataTemplate 的文章会很有帮助。首先,我建议你在 MSDN 上仔细阅读 Data Templating Overview 页面。正如我在评论中提到的那样,你不应该在 DataTemplate 中使用 ContentPresenter。来自链接页面的引用:

通常情况下,你可以在 ContentControl 的 ControlTemplate 中使用 ContentPresenter 来指定添加内容的位置。

你似乎缺少的是如何以及从DataTemplate内部绑定数据。 DataTemplateDataContext将自动设置为DataType属性中指定类型的实例。 因此,DataTemplate可以访问的属性也将取决于DataType属性中指定的类型。例如,你不能这样做,因为string没有Value属性。
<DataTemplate DataType="{x:Type System:String}">
    <TextBox Text="{Binding Value}" BorderThickness="0" />
</DataTemplate>

相反,对于一个string,您需要像这样绑定整个DataContext值:

<DataTemplate DataType="{x:Type System:String}">
    <TextBox Text="{Binding}" BorderThickness="0" />
</DataTemplate>

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

<DataTemplate DataType="{x:Type YourDataTypesPrefix:SomeClass}">
    <TextBox Text="{Binding Value}" BorderThickness="0"/>
</DataTemplate>

现在,由于这些DataTemplate没有设置它们的x:Key值,因此框架会自动呈现每个相关类型的对象的内容(并且没有其他显式模板设置)。所以试试这个,如果你仍然有问题,请让我知道。

谢谢你的回答。我已经阅读了数据模板概述并从中学到了很多东西。我确实看到了一些改进设计的方法。但是我仍然不知道如何解决我的问题。我使用ContentPresenter的原因是因为我需要某种方式来呈现我的内容,并根据内容类型结构化我的数据呈现。我不知道另一个仅充当占位符的控件。我尝试过像你说的那样绑定到DataContext而没有给出路径,但我得到了一个XamlParseException,其中包含“双向绑定需要Path或XPath”的消息。 - Martijn
你需要一个ContentControl,而不是一个ContentPresenter - Sheridan
我现在明白了,在这种情况下使用ContentControl是正确的控件。但是ContentPresenter和ContentControl都有相同的结果。我发现使用点作为路径确实可以防止抛出异常,但绑定仍然不起作用。这个答案似乎解释了为什么"Are “{Binding Path=.}” and “{Binding}” really equal"。看来我的方法完全错误。我不知道该如何实现这种行为,但我知道这种方式是不正确的。 - Martijn
您可以针对每种数据类型/控件只使用一个DataTemplate和一个ContentControl,没有问题。 - Sheridan

5

我已经对我的解决方案感到不满意,也遇到了不能将List DataType添加到DataTemplates的问题。最终我使用了DataTemplateSelector,这样代码更加优美。以下是代码:

ContentControl。数据模板应用于其中的数据的容器:

<ContentControl Grid.Column="2" Content="{Binding}"
                ContentTemplateSelector="{StaticResource propertyItemTemplateSelector}">
</ContentControl>

一些DataTemplate以及一个用于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>
...

数据模板选择器的代码如下:
 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;
                }
                 ....

3
我发现了解决这个问题的方法。绑定没有生效的原因是我将数据绑定到了ContentControl的内容上。根据这里所述,双向绑定到绑定源不起作用,这就是我得到异常信息的原因。我仍然使用ContentControl和DataTemplates来区分数据类型,但是我绑定到ContentControl绑定到的值而不是内容上。请注意绑定中的路径。
<ContentControl Content="{Binding Value}" Grid.Column="2">
    <ContentControl.Resources>
        <DataTemplate DataType="{x:Type System:String}">
            <TextBox Text="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=DataContext.Value}" BorderThickness="0" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type System:Int32}">
            <TextBox Text="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=DataContext.Value}"
                     TextAlignment="Right"
                     BorderThickness="0"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type System:Double}">
            <TextBox Text="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=DataContext.Value}"
                     TextAlignment="Right"
                     BorderThickness="0"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type System:Boolean}">
            <CheckBox IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=DataContext.Value}"
                          HorizontalAlignment="Center"/>
        </DataTemplate>
    </ContentControl.Resources>
</ContentControl>

这是一个问题的解决方案。我只是觉得使用ContentControl来区分数据类型并具有不合逻辑的绑定感到有些不舒服。

此外,谢谢您帮助我解决这个问题,Sheridan。


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