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

中定义的那个

注释 2FrameworkElementFactory 方法在返回之前的最后一步将构造的模板对象分配给 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;
}

看起来您可以将 SelectedValuePathDisplayMemberPath 绑定到 FieldName 并完成:

<ComboBox SelectedValuePath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName}"
          DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName}"/>