在 DataTemplateSelector 中修改基于 XAML 的 DataTemplate
Modifying XAML-based DataTemplate in DataTemplateSelector
有没有办法在 DataTemplateSelector
返回之前修改 DataTemplate
?
我的DataTemplate
定义在XAML。此模板中有一个元素需要设置绑定,但其绑定路径仅在 运行 时确定。模板如下所示:
<DataTemplate DataType="vm:FormField">
<StackPanel>
<ComboBox ItemsSource="{Binding ValueList.DefaultView}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Mode=OneWay}" /> <!--This is the problem child-->
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
TextBlock.Text
需要将其绑定路径设置为将由基础数据项提供的 属性。我的 DataTemplateSelector
使用以下代码为其分配新路径:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//MultiValueTemplate is the above template
var Content = MultiValueTemplate.LoadContent() as StackPanel;
var ComboItemText = (Content.Children[0] as ComboBox).ItemTemplate.LoadContent() as TextBlock;
//Underlying item contains the field name that I want this to bind to.
ComboItemText.SetBinding(TextBlock.TextProperty, (item as MyItemVM).FieldName);
return MultiValueTemplate;
}
这行不通。代码 运行s,但输出未设置 TextProperty
绑定。我需要什么 change/add?
注意:我已经使用FrameworkElementFactory
方法解决了这个问题,但是我不得不在代码中重新定义整个DataTemplate
(这是一个即使是像上面这样的简单模板也很痛苦)。我想使用我已经在 XAML.
中定义的那个
注释 2:FrameworkElementFactory
方法在返回之前的最后一步将构造的模板对象分配给 DataTemplate.VisualTree
。我认为这是我遗漏的部分,但是没有办法做到这一点,因为 VisualTree
要求 FrameworkElementFactory
类型的对象,而我们在使用 XAML 时没有基于模板。
背景
我们基本上从服务器端获得 JSON 结构,看起来像这样:
`[
"Person":
{
"Name": "Peter",
"Score": 53000
},
"Person":
{
"Name": "dotNET",
"Score": 24000
}
,...
]
JSON中包含哪些字段将由服务器决定。我们的应用程序需要解析此 JSON,然后显示与字段一样多的组合框。然后每个 ComboBox 将在其中列出一个字段。因此,在上面的示例中,将有一个名称组合和一个分数组合。用户可以从第一个或第二个组合框中选择一个选项,但是从一个组合中选择 select 将自动从其他组合中 select 相应的项目。
现在你可能会问,到底是谁设计了这个白痴UI?不幸的是,我们既不知道也无法控制这个决定。我要求客户改为使用 ONE Combo(而不是许多)和 DataGrid 作为其下拉列表,这样我们就可以显示一个 data item网格行和用户可以选择其中一项。清晰简单。但管理层不同意,我们在这里试图模仿 同步组合框 。哈哈
所以我们目前正在做的是将传入的 JSON 即时转换为 DataTable
。此 DataTable 为每个 JSON 字段获取一列,并且行数与项目一样多;你可以说有点旋转。然后我们创建 ComboBoes 并将每个 ComboBoes 绑定到此 DataTable 的单个字段。这个字段名当然是动态的,是在运行时决定的,也就是说我要在运行时修改DataTemplate
,这就提出了这个问题。
希望不会太无聊! :)
注意:对于未来的读者,正如@ASh 在他的回答中提到的,DisplayMemberPath
是一个 DependencyProperty
并且可以是用于绑定到动态字段名称。此答案中的此解决方案将针对此特定问题进行过度设计。我仍将其保留在这里,因为它在 Binding 可能不够用的某些其他情况下很有用。
想通了,比我想象的要容易。我现在没有在 DataTemplateSelector
中修改模板,而是在运行时使用 Behavior
修改绑定路径。这是行为:
public class DynamicBindingPathBehavior : Behavior<TextBlock>
{
public string BindingPath
{
get { return (string)GetValue(BindingPathProperty); }
set { SetValue(BindingPathProperty, value); }
}
public static readonly DependencyProperty BindingPathProperty =
DependencyProperty.Register("BindingPath", typeof(string), typeof(DynamicBindingPathBehavior),
new FrameworkPropertyMetadata(null, (sender, e) =>
{
var Behavior = (sender as DynamicBindingPathBehavior);
Behavior.AssociatedObject.SetBinding(TextBlock.TextProperty, new Binding(Behavior.BindingPath));
}));
}
这是我必须在 XAML 模板中进行的修改:
<DataTemplate DataType="vm:FormField">
<StackPanel>
<ComboBox ItemsSource="{Binding ValueList.DefaultView}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Mode=OneWay}">
<e:Interaction.Behaviors>
<local:DynamicBindingPathBehavior BindingPath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName, Mode=OneWay}" />
</e:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
从现在开始一切正常。
另一种方法是在 DataTemplateSelector
中以编程方式创建模板。如果你想沿着这条路走下去,这里有一个关于如何在 SelectTemplate
函数中进行的粗略描述:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var DT = new DataTemplate();
FrameworkElementFactory stackpanelElement = new FrameworkElementFactory(typeof(StackPanel), "stackpanel");
FrameworkElementFactory comboboxElement = new FrameworkElementFactory(typeof(ComboBox), "combobox");
comboboxElement.SetBinding(ComboBox.ItemsSourceProperty, new Binding() { Path = new PropertyPath("ValueList.DefaultView") });
comboboxElement.SetBinding(ComboBox.SelectedItemProperty, new Binding() { Path = new PropertyPath("Value") });
var ItemTemplate = new DataTemplate();
FrameworkElementFactory textblockElement2 = new FrameworkElementFactory(typeof(TextBlock), "textblock2");
textblockElement2.SetBinding(TextBlock.TextProperty, new Binding() { Path = new PropertyPath(YOUR_BINDING_PROPERTY_PATH) });
ItemTemplate.VisualTree = textblockElement2;
comboboxElement.SetValue(ComboBox.ItemTemplateProperty, ItemTemplate);
stackpanelElement.AppendChild(comboboxElement);
DT.VisualTree = stackpanelElement;
return MultiValueTemplate;
}
看起来您可以将 SelectedValuePath
和 DisplayMemberPath
绑定到 FieldName 并完成:
<ComboBox SelectedValuePath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName}"
DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName}"/>
有没有办法在 DataTemplateSelector
返回之前修改 DataTemplate
?
我的DataTemplate
定义在XAML。此模板中有一个元素需要设置绑定,但其绑定路径仅在 运行 时确定。模板如下所示:
<DataTemplate DataType="vm:FormField">
<StackPanel>
<ComboBox ItemsSource="{Binding ValueList.DefaultView}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Mode=OneWay}" /> <!--This is the problem child-->
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
TextBlock.Text
需要将其绑定路径设置为将由基础数据项提供的 属性。我的 DataTemplateSelector
使用以下代码为其分配新路径:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//MultiValueTemplate is the above template
var Content = MultiValueTemplate.LoadContent() as StackPanel;
var ComboItemText = (Content.Children[0] as ComboBox).ItemTemplate.LoadContent() as TextBlock;
//Underlying item contains the field name that I want this to bind to.
ComboItemText.SetBinding(TextBlock.TextProperty, (item as MyItemVM).FieldName);
return MultiValueTemplate;
}
这行不通。代码 运行s,但输出未设置 TextProperty
绑定。我需要什么 change/add?
注意:我已经使用FrameworkElementFactory
方法解决了这个问题,但是我不得不在代码中重新定义整个DataTemplate
(这是一个即使是像上面这样的简单模板也很痛苦)。我想使用我已经在 XAML.
注释 2:FrameworkElementFactory
方法在返回之前的最后一步将构造的模板对象分配给 DataTemplate.VisualTree
。我认为这是我遗漏的部分,但是没有办法做到这一点,因为 VisualTree
要求 FrameworkElementFactory
类型的对象,而我们在使用 XAML 时没有基于模板。
背景
我们基本上从服务器端获得 JSON 结构,看起来像这样:
`[
"Person":
{
"Name": "Peter",
"Score": 53000
},
"Person":
{
"Name": "dotNET",
"Score": 24000
}
,...
]
JSON中包含哪些字段将由服务器决定。我们的应用程序需要解析此 JSON,然后显示与字段一样多的组合框。然后每个 ComboBox 将在其中列出一个字段。因此,在上面的示例中,将有一个名称组合和一个分数组合。用户可以从第一个或第二个组合框中选择一个选项,但是从一个组合中选择 select 将自动从其他组合中 select 相应的项目。
现在你可能会问,到底是谁设计了这个白痴UI?不幸的是,我们既不知道也无法控制这个决定。我要求客户改为使用 ONE Combo(而不是许多)和 DataGrid 作为其下拉列表,这样我们就可以显示一个 data item网格行和用户可以选择其中一项。清晰简单。但管理层不同意,我们在这里试图模仿 同步组合框 。哈哈
所以我们目前正在做的是将传入的 JSON 即时转换为 DataTable
。此 DataTable 为每个 JSON 字段获取一列,并且行数与项目一样多;你可以说有点旋转。然后我们创建 ComboBoes 并将每个 ComboBoes 绑定到此 DataTable 的单个字段。这个字段名当然是动态的,是在运行时决定的,也就是说我要在运行时修改DataTemplate
,这就提出了这个问题。
希望不会太无聊! :)
注意:对于未来的读者,正如@ASh 在他的回答中提到的,DisplayMemberPath
是一个 DependencyProperty
并且可以是用于绑定到动态字段名称。此答案中的此解决方案将针对此特定问题进行过度设计。我仍将其保留在这里,因为它在 Binding 可能不够用的某些其他情况下很有用。
想通了,比我想象的要容易。我现在没有在 DataTemplateSelector
中修改模板,而是在运行时使用 Behavior
修改绑定路径。这是行为:
public class DynamicBindingPathBehavior : Behavior<TextBlock>
{
public string BindingPath
{
get { return (string)GetValue(BindingPathProperty); }
set { SetValue(BindingPathProperty, value); }
}
public static readonly DependencyProperty BindingPathProperty =
DependencyProperty.Register("BindingPath", typeof(string), typeof(DynamicBindingPathBehavior),
new FrameworkPropertyMetadata(null, (sender, e) =>
{
var Behavior = (sender as DynamicBindingPathBehavior);
Behavior.AssociatedObject.SetBinding(TextBlock.TextProperty, new Binding(Behavior.BindingPath));
}));
}
这是我必须在 XAML 模板中进行的修改:
<DataTemplate DataType="vm:FormField">
<StackPanel>
<ComboBox ItemsSource="{Binding ValueList.DefaultView}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Mode=OneWay}">
<e:Interaction.Behaviors>
<local:DynamicBindingPathBehavior BindingPath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName, Mode=OneWay}" />
</e:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
从现在开始一切正常。
另一种方法是在 DataTemplateSelector
中以编程方式创建模板。如果你想沿着这条路走下去,这里有一个关于如何在 SelectTemplate
函数中进行的粗略描述:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var DT = new DataTemplate();
FrameworkElementFactory stackpanelElement = new FrameworkElementFactory(typeof(StackPanel), "stackpanel");
FrameworkElementFactory comboboxElement = new FrameworkElementFactory(typeof(ComboBox), "combobox");
comboboxElement.SetBinding(ComboBox.ItemsSourceProperty, new Binding() { Path = new PropertyPath("ValueList.DefaultView") });
comboboxElement.SetBinding(ComboBox.SelectedItemProperty, new Binding() { Path = new PropertyPath("Value") });
var ItemTemplate = new DataTemplate();
FrameworkElementFactory textblockElement2 = new FrameworkElementFactory(typeof(TextBlock), "textblock2");
textblockElement2.SetBinding(TextBlock.TextProperty, new Binding() { Path = new PropertyPath(YOUR_BINDING_PROPERTY_PATH) });
ItemTemplate.VisualTree = textblockElement2;
comboboxElement.SetValue(ComboBox.ItemTemplateProperty, ItemTemplate);
stackpanelElement.AppendChild(comboboxElement);
DT.VisualTree = stackpanelElement;
return MultiValueTemplate;
}
看起来您可以将 SelectedValuePath
和 DisplayMemberPath
绑定到 FieldName 并完成:
<ComboBox SelectedValuePath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName}"
DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName}"/>