【问题标题】:Contentpresenter with type based datatemplate selection and binding具有基于类型的数据模板选择和绑定的 Contentpresenter
【发布时间】:2014-02-27 04:25:39
【问题描述】:

我有一个绑定到项目列表的 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>

ContentPresenter 使用正确的数据类型并且效果很好。我的问题是编辑这些值对绑定的项目没有任何影响。我怀疑这是因为我绑定到 ContentPresenter 的 content 属性,而不是直接绑定到 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 显示,并且对值的任何编辑都应用于绑定的项目。

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

【问题讨论】:

  • 以后,请格式化您的代码,以便可以看到,而不是大部分代码都在屏幕外。提示:如果您在编辑时选择了代码,您可以单击“代码示例”按钮将其全部向前或向后移动。
  • 你说 为了正确显示 value 属性,我使用 ContentPresenter 和我可能使用的每种数据类型的数据模板... 那个not 如何正确显示它... 通常ContentPresenter 应该ControlTemplate 之外使用。从链接页面:您通常使用 ContentControl 的 ControlTemplate 中的 ContentPresenter 来指定要添加内容的位置

标签: c# wpf xaml binding datatemplate


【解决方案1】:

我想你会从阅读DataTemplates 中受益。首先,我建议您仔细阅读 MSDN 上的 Data Templating Overview 页面。正如我在我的 cmets 中提到的,您应该在您的 DataTemplates 中使用 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>

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

【讨论】:

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

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

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

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

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;
                }
                 ....

【讨论】:

    【解决方案3】:

    我找到了解决这个问题的方法。绑定不起作用的原因是因为我绑定了 ContentControl 的内容。如here 所述,双向绑定到绑定源不起作用。这就是我得到例外的原因。我仍然使用 ContentControl 和 DataTemplates 来区分数据类型。但不是绑定到 ContentControl 的内容,而是绑定到 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 只是为了区分 DataTypes 和不合逻辑的绑定有点不舒服。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-31
      • 2017-07-14
      • 1970-01-01
      相关资源
      最近更新 更多