Search code examples
wpfxamldatatemplatedatatemplateselector

Modifying XAML-based DataTemplate in DataTemplateSelector


Is there a way to modify DataTemplate before returning it in DataTemplateSelector?

My DataTemplate is defined in XAML. There is an element in this template that I need to set binding for, but whose binding path will only be decided at run-time. The template looks like this:

<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 needs to set its binding path to a property that will be supplied by the underlying data item. My DataTemplateSelector uses the following code to assign it the new path:

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

This doesn't work. Code runs, but the output doesn't set TextProperty binding. What do I need to change/add?

Note: I have solved this problem using FrameworkElementFactory approach, but I have had to redefine the entire DataTemplate in the code (which is a pain even for simple template like the one above). I want to use the one that I have already defined in XAML.

Note 2: FrameworkElementFactory approach assigns the constructed template object to DataTemplate.VisualTree in the last step, just before returning. I think it is that part that I'm missing, but there is no way of doing that since VisualTree asks for an object of FrameworkElementFactory type, which we do not have when using XAML-based template.

Background

We are basically getting JSON structure from the server-side that looks something like this:

`[ 
   "Person": 
   { 
     "Name": "Peter", 
     "Score": 53000 
   },
   "Person": 
   { 
     "Name": "dotNET", 
     "Score": 24000 
   }
   ,...
 ]

What fields will be included in JSON will be determined by the server. Our application is required to parse this JSON and then display as many ComboBoxes as there are fields. Each ComboBox will then list down one field in it. So in the above example, there will be one combo for Names and one for Scores. User can choose an option either from the first or second ComboBox, but selecting from one combo will automatically select corresponding item from the other combo(s).

Now you may ask, who the hell designed this idiotic UI? Unfortunately we neither know nor control this decision. I ask the client to instead use ONE Combo (instead of many) with a DataGrid as its dropdown, so that we could display one data item per grid row and user could choose one of those items. Clear and Simple. But the management didn't agree and here we are trying to mimic synchronized comboboxes. LOL.

So what we're currently doing is to transform incoming JSON to a DataTable on-the-fly. This DataTable gets one column for each JSON field and as many row as their are items; kind of pivoting you can say. We then create ComboBoes and bind each one to a single field of this DataTable. This field name is of course dynamic and is decided at run-time, which mean that I have to modify the DataTemplate at run-time, which brings up this question.

Hope it didn't get too boring! :)


Solution

  • look like you can bind SelectedValuePath and DisplayMemberPath to FieldName and be done with that:

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