wpf 具有基于类型的数据模板选择和绑定的 Contentpresenter
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21531516/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Contentpresenter with type based datatemplate selection and binding
提问by Martijn
I have an ItemsControl that binds to a list of items. These items have a name and value property. The value property is of type Object to allow different datatypes to be used. To display the value property correctly I use a ContentPresenter with a datatemplate for every datatype I might use.
我有一个绑定到项目列表的 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>
The ContentPresenter uses the correct datatype and works great. My problem is that editing these values does not have any effect on the bound items. I suspect it is because I bind to the content property of the ContentPresenter rather than directly to the Value. I've tried using the ContentPresenter like this:
ContentPresenter 使用正确的数据类型并且效果很好。我的问题是编辑这些值对绑定的项目没有任何影响。我怀疑这是因为我绑定到 ContentPresenter 的内容属性而不是直接绑定到值。我试过像这样使用 ContentPresenter:
<ContentPresenter Grid.Column="2" Content="{Binding}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding Value}"
BorderThickness="0"/>
</DataTemplate>
But this way the correct DataTemplate isn't selected and it just displays the Object instead of a String for example. I also tried to leave out the path in the binding of the DataTemplate like this:
但是这样就不会选择正确的 DataTemplate 并且它只是显示对象而不是例如字符串。我还尝试像这样省略 DataTemplate 绑定中的路径:
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding}" BorderThickness="0"/>
</DataTemplate>
With this I get an exception telling me to use the Path or XPath attribute.
有了这个,我得到一个异常,告诉我使用 Path 或 XPath 属性。
So my question is: how do I correctly bind to the Value so it displays with the right DataTemplate and that any editing of the values is applied to the bound item.
所以我的问题是:我如何正确绑定到 Value 以便它显示正确的 DataTemplate 并且对值的任何编辑都应用于绑定项目。
Btw for some reason the formatted code blocks in my question indent much more after the first line. I tried fixing it but I don't understand what's happening.
顺便说一句,由于某种原因,我问题中的格式化代码块在第一行之后缩进了更多。我尝试修复它,但我不明白发生了什么。
采纳答案by Martijn
Already being uncomfortable with my solution I also ran into the problem of not being able to add a List DataType to the DataTemplates. I ended up using a DataTemplateSelector which resulted in much nicer code. Here it is:
我已经对我的解决方案感到不舒服,我还遇到了无法将 List DataType 添加到 DataTemplates 的问题。我最终使用了一个 DataTemplateSelector,它产生了更好的代码。这里是:
The ContentControl. A container for the data which the DataTemplate is applied over:
内容控件。应用 DataTemplate 的数据的容器:
<ContentControl Grid.Column="2" Content="{Binding}"
ContentTemplateSelector="{StaticResource propertyItemTemplateSelector}">
</ContentControl>
A few DataTemplates and a declaration for the DataTemplateSelector:
一些 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>
...
The code for the DataTemplateSelector:
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;
}
....
回答by Sheridan
I think you'll benefit from reading about DataTemplates. First, I'd advise you to have a good, long read of the Data Templating Overviewpage on MSDN. As I mentioned in my comments, you should notuse a ContentPresenterin your DataTemplates. From the linked page:
我认为你会从阅读关于DataTemplates 中受益。首先,我建议您仔细阅读MSDN上的数据模板概述页面。正如我在我的评论中提到,你应该不使用ContentPresenter你DataTemplate秒。从链接页面:
You typically use the ContentPresenter in the ControlTemplate of a ContentControl to specify where the content is to be added.
您通常使用 ContentControl 的 ControlTemplate 中的 ContentPresenter 来指定要添加内容的位置。
The thing that you seemto be missing is how and what to data bind to from insidea DataTemplate. The DataContextof the DataTemplatewill automatically be set to an instance of the type specified in the DataTypeproperty. Therefore, the properties that the DataTemplatehas access to will also depend on the type specified in the DataTypeproperty. For example, you cannot do this, because a stringdoes not have a Valueproperty.
你的东西似乎是丢失的是如何和什么将数据绑定到从里面一个DataTemplate。所述DataContext的DataTemplate将被自动设定为在所指定的类型的实例DataType属性。因此,DataTemplate可以访问的属性也将取决于DataType属性中指定的类型。例如,您不能这样做,因为 astring没有Value属性。
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding Value}" BorderThickness="0" />
</DataTemplate>
Instead, for a string, you'd need to data bind to the whole DataContextvalue like this:
相反,对于 a string,您需要DataContext像这样将数据绑定到整个值:
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding}" BorderThickness="0" />
</DataTemplate>
Alternatively, if you had a class names SomeClassand that class has a Valueproperty, thenyou could do this:
或者,如果您有一个类名SomeClass并且该类有一个Value属性,那么您可以这样做:
<DataTemplate DataType="{x:Type YourDataTypesPrefix:SomeClass}">
<TextBox Text="{Binding Value}" BorderThickness="0"/>
</DataTemplate>
Now, because these DataTemplates have been defined without setting their x:Keyvalues, the Framework will automatically render the content of each DataTemplatewhenever it sees an object of the relevant type (and no other, explicit templates set). So try this out and if you still have a problem, let me know.
现在,因为这些DataTemplates 是在没有设置它们的x:Key值的情况下定义的,所以框架将DataTemplate在看到相关类型的对象(并且没有其他显式模板集)时自动呈现每个的内容。所以试试这个,如果你仍然有问题,请告诉我。
回答by Martijn
I found sort of a workaround for this problem. The reason the binding did not work is because I bound to the content of the ContentControl. Two-way binding to a binding source does not work as stated here. This is the reason I got the exception. I still use the ContentControl with the DataTemplates to differentiate between the data types. But instead of binding to the content of the ContentControl I bind to the value the ContentControl binds to. Notice the Path in the binding.
我找到了解决这个问题的方法。绑定不起作用的原因是因为我绑定到 ContentControl 的内容。双向绑定到绑定源为表示不工作在这里。这就是我得到异常的原因。我仍然使用 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>
It is a solution to the problem. I just feel a bit uncomfortable for using the ContentControl just to diferentiate between the DataTypes and having an illogical binding.
它是解决问题的方法。我只是觉得使用 ContentControl 只是为了区分数据类型和具有不合逻辑的绑定而感到有点不舒服。
Also, thank you for helping me unravel this problem Sheridan.
另外,感谢您帮助我解决这个问题 Sheridan。

