Is this WPF ViewModel-first hookup pattern using implicit DataTemplates possible in WinUI 3?

Viewed 44

I have watched Brian Noyes excellent course WPF MVVM in Depth on Pluralsight. I'm attempting to recreate a structure from there in WinUI 3 but cannot get it to work.

The idea is to create a navigation between different views that are displayed in a ContentControl within the main window. MainWindowViewModel exposes a property called CurrentViewModel. Through commands on buttons this property gets set to different values and are updated with the normal flow using INotifyPropertyChange. This property is bound to from the Content-property of a ContentControl in the layout of MainWindow. In the resources of this window a few DataTemplates are defined and by matching the type of the bound object the proper view is inserted in to the content control. Here are the relevant parts of MainWindow.xaml (note that all this is done in WPF):

<Window <!--Snipped-->>
    <Window.Resources>
        <DataTemplate DataType="{x:Type cust:CustomerListViewModel}">
            <cust:CustomerListView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type order:OrderViewModel}">
            <order:OrderView />
        </DataTemplate>
    </Window.Resources>
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <!--Snipped-->
        <Grid>
            <ContentControl Content="{Binding CurrentViewModel}" />
        </Grid>
    </Grid>
</Window>

Can this be done in WinUI 3?

  • I've moved the resource dictionary to the root Grid-element
  • I've tried using both {Binding} and {x:Bind ViewModel.CurrentViewModel}
  • The markup extension of type "{x:Type cust:CustomerListViewModel}" seems to not be available in WinUI 3, I've instead tried x:DataType=cust:CustomerListViewModel

The closest I've come is that the ToString() of the view models are rendered.

1 Answers

First of all, you should consider using the Template Studio for WinUI. You can implement navigation with few clicks.

I'm not familiar with the course you mentioned, but I guess is something close to what you are trying to do.

BTW, I'm using the CommunityToolkit.Mvvm NuGet package to implement MVVM design, which I strongly recommend you to check it out.

This is a simple example using buttons to navigate.

MainWindowViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace NavigationDataTemplateSelector;

public partial class MainWindowViewModel : ObservableObject
{
    [ObservableProperty]
    // A "CurrentViewModel" UI-interactive property will be auto-generated.
    private object? currentViewModel;

    [RelayCommand]
    // A "GoToCommand" command will be auto-generated.
    private void GoTo(string viewModelName)
    {
        CurrentViewModel = viewModelName switch
        {
            nameof(CustomerListViewModel) => new CustomerListViewModel(),
            nameof(OrderViewModel) => new OrderViewModel(),
            _ => null,
        };
    }
}

CustomerListViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;

namespace NavigationDataTemplateSelector;

public partial class CustomerListViewModel : ObservableObject
{
    [ObservableProperty]
    public ObservableCollection<string> customers = new()
    {
        "A",
        "B",
        "C",
    };
}

OrderViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using System;

namespace NavigationDataTemplateSelector;

public partial class OrderViewModel : ObservableObject
{
    [ObservableProperty]
    public Guid id = Guid.NewGuid();
}

MainWindow.xaml.cs

using Microsoft.UI.Xaml;

namespace NavigationDataTemplateSelector;

public sealed partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();
    }

    public MainWindowViewModel ViewModel { get; } = new();
}

MainWindow.xaml

As you can see here, I'm using a TemplateSelector to select which DataTemplate to use as a Content.

<Window
    x:Class="NavigationDataTemplateSelector.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:NavigationDataTemplateSelector"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid>
        <Grid.Resources>
            <DataTemplate
                x:Key="CustomerListViewModel"
                x:DataType="local:CustomerListViewModel">
                <Grid>
                    <ListView ItemsSource="{x:Bind Customers, Mode=OneWay}" />
                </Grid>
            </DataTemplate>
            <DataTemplate
                x:Key="OrderViewModel"
                x:DataType="local:OrderViewModel">
                <Grid>
                    <TextBlock Text="{x:Bind Id, Mode=OneWay}" />
                </Grid>
            </DataTemplate>
            <local:ViewTemplateSelector
                x:Key="ViewTemplateSelector"
                CustomerListViewTemplate="{StaticResource CustomerListViewModel}"
                OrderViewTemplate="{StaticResource OrderViewModel}" />
        </Grid.Resources>

        <Grid RowDefinitions="Auto,Auto,*">
            <Button
                x:Name="CustomerListViewButton"
                Grid.Row="0"
                Command="{x:Bind ViewModel.GoToCommand}"
                CommandParameter="CustomerListViewModel"
                Content="Go to customer list view" />
            <Button
                x:Name="OrderViewButton"
                Grid.Row="1"
                Command="{x:Bind ViewModel.GoToCommand}"
                CommandParameter="OrderViewModel"
                Content="Go to order view" />
            <ContentControl
                Grid.Row="2"
                Content="{x:Bind ViewModel.CurrentViewModel, Mode=OneWay}"
                ContentTemplateSelector="{StaticResource ViewTemplateSelector}" />
        </Grid>
    </Grid>

</Window>

ViewTemplateSelector.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;

namespace NavigationDataTemplateSelector;

public class ViewTemplateSelector : DataTemplateSelector
{
    public DataTemplate? CustomerListViewTemplate { get; set; }

    public DataTemplate? OrderViewTemplate { get; set; }

    protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container)
    {
        return item switch
        {
            CustomerListViewModel => CustomerListViewTemplate,
            OrderViewModel => OrderViewTemplate,
            _ => null,
        };
    }
}
Related