Search code examples
c#bindingdesktopwinui-3

WinUI3 XAML Master-Detail View Binding for Different Types


I'm Stuck creating a Master/Detail View in WinUI 3.

screenshot

I Have a treeview as my master list with two different item types ExplorerItemTypeA and ExplorerItemTypeA each a partial class from the base ExplorerItem

I wish for the detail view to show the correct template for the different type (A&B) so I can bind and edit etc.

The Treeview DataTemplates work fine. Here is the XAML for the details view:

<Page.Resources>

    <DataTemplate x:Key="ContentTypeATemplate" x:DataType="local:ExplorerItemTypeA">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="A Template"/>
            <TextBlock Text="{x:Bind Name}"/>
            </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="ContentTypeBTemplate" x:DataType="local:ExplorerItemTypeB">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="B Template"/>
            <TextBlock Text="{x:Bind Name}"/>
        </StackPanel>
    </DataTemplate>

    <ExplorerContentTemplateSelector x:Key="ExplorerContentTemplateSelector" 
                                        TreeItemTypeATemplate="{StaticResource ContentTypeATemplate}" 
                                        TreeItemTypeBTemplate="{StaticResource ContentTypeBTemplate}"/>
</Page.Resources>

If first tried a frame but am now trying a Content presenter for the detail view.

     <TreeView x:Name="MasterListView" 
        Grid.Column="0" 
        ItemsSource="{x:Bind ViewModel.TemplateItems}"
        SelectedItem="{x:Bind ViewModel.SelectedItem,Mode=TwoWay}"
        ItemTemplateSelector="{StaticResource ExplorerItemTemplateSelector}"/>

    <ContentPresenter
        x:Name="DetailContentPresenter"
        Grid.Column="1"
        Content="{x:Bind MasterListView.SelectedItem, Mode=OneWay}"
        ContentTemplateSelector="{StaticResource ExplorerContentTemplateSelector}" />

        

The Selector is

    public class ExplorerContentTemplateSelector : DataTemplateSelector
{
    public DataTemplate TreeItemTypeATemplate { get; set; }
    public DataTemplate TreeItemTypeBTemplate { get; set; }

    protected override DataTemplate SelectTemplateCore(object item)
    {
        var explorerItem = (ExplorerItem)item;
        switch (explorerItem.Type)
        {
            case ExplorerItem.ExplorerItemType.A:
                return TreeItemTypeATemplate;
            case ExplorerItem.ExplorerItemType.B:
                return TreeItemTypeBTemplate;
            default:
                Console.WriteLine("Default case");
                return TreeItemTypeATemplate;
        }

    }
}

I feel the answer is obvious and elegant, but despite searching for clues and methods this c# beginner is just not getting anywhere. I've been stuck on this simple problem for far longer than I care to admit.

Thanks to anyone who is willing to give their time.


Solution

  • Replace the ContentPresenter with a ContentControl in your XAML markup:

    <ContentControl
        x:Name="DetailContentPresenter"
        Grid.Column="1"
        Content="{x:Bind MasterListView.SelectedItem, Mode=OneWay}"
        ContentTemplateSelector="{StaticResource ExplorerContentTemplateSelector}" />
    

    In your DataTemplateSelector, you should also override the SelectTemplateCore overload that accepts a DependencyObject parameter:

    public class ExplorerContentTemplateSelector : DataTemplateSelector
    {
        public DataTemplate TreeItemTypeATemplate { get; set; }
        public DataTemplate TreeItemTypeBTemplate { get; set; }
    
        protected override DataTemplate SelectTemplateCore(object item) =>
            SelectTemplateCore(item, null);
    
        protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            var explorerItem = item as ExplorerItem;
            switch (explorerItem?.Type)
            {
                case ExplorerItem.ExplorerItemType.A:
                    return TreeItemTypeATemplate;
                case ExplorerItem.ExplorerItemType.B:
                    return TreeItemTypeBTemplate;
                default:
                    Console.WriteLine("Default case");
                    return TreeItemTypeATemplate;
            }
        }
    }