Search code examples
c#wpfxamldatatemplatedatatemplateselector

Binding not working anymore when using TemplateSelector in DataGridTemplateColumn


I have the following (working) XAML definition:

<DataGridTemplateColumn Header="Station &amp; Programm" Width="*">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>                                                
            <ListBox 
                ItemsSource="{Binding Targets}" 
                Style="{StaticResource ListBoxTransparentStyle}"    
                VerticalAlignment="Center"
                IsHitTestVisible="False"> <!--Disable Selection-->
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition SharedSizeGroup="A"/>
                                <ColumnDefinition Width="5"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock
                                Grid.Column="0"
                                Text="{Binding Station}"
                                Height="Auto"/>
                            <TextBlock
                                Grid.Column="2"
                                Text="{Binding Program}"
                                Height="Auto"/>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

I want to outsource this DataTemplate and use the following DataTemplateSelector:

public class ZlsRouteEditorDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate ViewDataTemplate { get;set; }
    public DataTemplate EditDataTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is ERouteEditor e)
        {
            switch (e)
            {
                case ERouteEditor.View:
                    return ViewDataTemplate;
                case ERouteEditor.Edit:
                    return EditDataTemplate;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        return base.SelectTemplate(item, container);
    }
}

Afterwards I have changed my code as follow:

<ControlTemplate>
    <ControlTemplate.Resources>
        <DataTemplate x:Key="StationProgramView" x:Shared="True">                                                
            <ListBox 
                ItemsSource="{Binding Targets}" 
                Style="{StaticResource ListBoxTransparentStyle}"    
                VerticalAlignment="Center"
                IsHitTestVisible="False"> <!--Disable Selection-->
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid >
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition SharedSizeGroup="A"/>
                                <ColumnDefinition Width="5"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <controls:HtTextfeld
                                Grid.Column="0"
                                Text="{Binding Station}"
                                DisableTranslation="True"
                                Height="Auto"/>
                            <controls:HtTextfeld
                                Grid.Column="2"
                                Text="{Binding Program}"
                                DisableTranslation="True"
                                Height="Auto"/>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </DataTemplate>
        <DataTemplate x:Key="StationProgramEdit" x:Shared="True">                                                
            <TextBlock Text="Hello World"></TextBlock>
        </DataTemplate>
        <recipeControls:ZlsRouteEditorDataTemplateSelector x:Key="StationProgramTemplateSelector" ViewDataTemplate="{StaticResource StationProgramView}" EditDataTemplate="{StaticResource StationProgramEdit}"/>

<DataGridTemplateColumn Header="Station &amp; Programm" Width="*">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ContentControl
                ContentTemplateSelector="{StaticResource StationProgramTemplateSelector}"
                Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=recipeControls:ZlsRouteEditor}, Path=Mode}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

The DataTemplates are selected correctly, but the data binding does not work anymore.


Solution

  • Here is my personal solution how to get it working.

    I have changed the ZlsRouteEditorDataTemplateSelector class with the ability to look for my specific control (ZlsRouteEditor) and get the Mode value.

    ZlsRouteEditorDataTemplateSelector

    public class ZlsRouteEditorDataTemplateSelector : DataTemplateSelector
    {
        public DataTemplate ViewDataTemplate { get;set; }
        public DataTemplate EditDataTemplate { get; set; }
    
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            if (item is ERouteEditor e)
                return _GetTemplate(e);
    
            ZlsRouteEditor parent = container.TryFindParent<ZlsRouteEditor>();
            if (parent != null)
                return _GetTemplate(parent.Mode);
    
            return base.SelectTemplate(item, container);
        }
    
        private DataTemplate _GetTemplate(ERouteEditor e)
        {
            switch (e)
            {
                case ERouteEditor.View:
                    return ViewDataTemplate;
                case ERouteEditor.Edit:
                    return EditDataTemplate;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
    

    TryFindParent

    /// <summary>
    /// Finds a parent of a given item on the visual tree.
    /// </summary>
    /// <typeparam name="T">The type of the queried item.</typeparam>
    /// <param name="child">A direct or indirect child of the
    /// queried item.</param>
    /// <returns>The first parent item that matches the submitted
    /// type parameter. If not matching item can be found, a null
    /// reference is being returned.</returns>
    public static T TryFindParent<T>(this DependencyObject child) where T : DependencyObject
    {
        //get parent item
        DependencyObject parentObject = GetParentObject(child);
    
        //we've reached the end of the tree
        if (parentObject == null) return null;
    
        //check if the parent matches the type we're looking for
        T parent = parentObject as T;
        if (parent != null)
        {
            return parent;
        }
        else
        {
            //use recursion to proceed with next level
            return TryFindParent<T>(parentObject);
        }
    }
    

    Then call it directly (like mm8 said)

    <DataGridTemplateColumn Header="Station &amp; Programm" Width="*" CellTemplateSelector="{StaticResource StationProgramTemplateSelector}"/>
    

    Where, why and when do you need to Mode property?

    I have bound a recipe object to the ZlsRouteEditor control to view or edit the recipe.

    To avoid multiple controls or Styles (ZlsRouteEditorView, ZlsRouteEditorEdit ... ), I made an enum ERouteEditor as DependencyProperty in the ZlsRouteEditor to change the DataTemplates of specific areas inside of my Template.

    enter image description here

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <recipeControls:ZlsRouteEditor Height="570" Margin="5" Mode="View"/>
        <recipeControls:ZlsRouteEditor Height="570" Margin="5" Mode="Edit"/>
    </StackPanel>