How can I dynamically change the view displayed in a ContentControl based on user actions in a PersonPage?

166 Views Asked by At

I have a MainPage with a MainViewModel. The page consists of a ListView displaying a list of persons, and I want to show different views (templates) in a ContentControl depending on user actions. Specifically, when a user clicks on a ListView item, I want to display a view template for viewing the person's details. If the user clicks an "Edit" button, I want to display a template for editing the person's details. Finally, when the user clicks a "New" button, I want to display a template for creating a new person record.

How can I accomplish this in my PersonPage using WinUI 3?

I try with this code will show the text App1.Core.Models.Person

MainPage.xaml

<Page
    x:Class="App1.Views.MainPage"
    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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="using:App1.ViewModels"
    xmlns:tm="using:App1.Templates"
    xmlns:m="using:App1.Core.Models"
    mc:Ignorable="d">

    <Page.DataContext>
        <vm:MainViewModel />
    </Page.DataContext>
    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="visitEmptyTemplate">
                <StackPanel Orientation="Vertical" Background="Violet">
                    <TextBlock Text="Empty" Foreground="Violet"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="visitEditTemplate">
                <StackPanel Background="blue">
                    <TextBlock Text="Edits" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource SubtitleTextBlockStyle}" Foreground="Blue"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="visitNewTemplate">
                <StackPanel Background="Green">
                    <TextBlock Text="News" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource SubtitleTextBlockStyle}" Foreground="Green"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="visitViewTemplate" x:DataType="vm:MainViewModel">
                <StackPanel Orientation="Vertical" Background="Indigo">
                    <TextBlock Text="{x:Bind SelectedPerson.Name, Mode=OneWay}" Foreground="Indigo"/>
                    <TextBox Text="Hello"/>
                </StackPanel>
            </DataTemplate>

            <tm:VisitTemplateSelector  
            x:Key="visitTemplateSelector"
            VisitEmptyTemplate="{StaticResource visitEmptyTemplate}"
            VisitEditTemplate="{StaticResource visitEditTemplate}"
            VisitNewTemplate="{StaticResource visitNewTemplate}"
            VisitViewTemplate="{StaticResource visitViewTemplate}"/>
        </Grid.Resources>
        
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <CommandBar DefaultLabelPosition="Collapsed">
            <AppBarButton x:Name="NewButton" Icon="Add" Command="{Binding NewVisitCommand}" />
            <AppBarButton x:Name="EditButton" Icon="Edit"  Command="{Binding EditVisitCommand}" />
        </CommandBar>
        <SplitView Grid.Row="1" IsPaneOpen="True" DisplayMode="Inline">
            <SplitView.Pane>
                <ListView
                    ItemsSource="{x:Bind ViewModel.PersonList, Mode=OneWay}"
                    SelectedItem="{x:Bind ViewModel.SelectedPerson, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                    SelectionChanged="{x:Bind ViewModel.ListViewSelectionChanged}">
                    <ListView.ItemTemplate>
                        <DataTemplate x:DataType="m:Person">
                            <Border>
                                <TextBlock Text="{x:Bind Name, Mode=OneWay}"/>
                            </Border>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </SplitView.Pane>
            <Grid>
                <ContentControl x:Name="ContentView"
                        ContentTemplateSelector="{StaticResource visitTemplateSelector}"
                        Content="{x:Bind ViewModel.SelectedPerson, Mode=OneWay}" />
            </Grid>
        </SplitView>        
    </Grid>
</Page>

VisitTemplateSelector.cs

public class VisitTemplateSelector : DataTemplateSelector
{
    public DataTemplate? VisitEditTemplate { get; set; }
    public DataTemplate? VisitEmptyTemplate { get; set; }
    public DataTemplate? VisitNewTemplate { get; set; }
    public DataTemplate? VisitViewTemplate { get; set; }

    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        if (item is MainViewModel mv)
        {
            if (mv.SelectedPerson is not null && mv.SelectedView == MainViewModel.ViewMode.Edit)
            {
                return VisitEditTemplate;
            }

            if (mv.SelectedPerson is null)
            {
                return VisitEmptyTemplate;
            }
            if (mv.SelectedPerson is null && mv.SelectedView == MainViewModel.ViewMode.New)
            {
                return VisitNewTemplate;
            }
            if (mv.SelectedPerson is not null && mv.SelectedView == MainViewModel.ViewMode.View)
            {
                return VisitViewTemplate;
            }
        }

        return base.SelectTemplateCore(item, container);
    }
}

MainViewModel.cs

public partial class MainViewModel : ObservableRecipient
{
    public ObservableCollection<Person> PersonList { get; set; } = new ();

    public enum ViewMode
    {
        Empty,
        Edit,
        New,
        View,
    }

    [ObservableProperty]
    ViewMode selectedView;

    [ObservableProperty]
    Person? selectedPerson;

    public MainViewModel()
    {
        PersonList.Add(new Person() { Name = "Manoj Babu", Phone = "0123456789" });
        PersonList.Add(new Person() { Name = "binoj Babu", Phone = "0987456321" });
        PersonList.Add(new Person() { Name = "Athira Manoj", Phone = "0123456789" });
        PersonList.Add(new Person() { Name = "Laksya Manoj", Phone = "0987456210" });

        SelectedView = ViewMode.Empty;
        SelectedPerson = null;
    }

    [RelayCommand]
    void NewVisit()
    {
        SelectedPerson = new Person();
        SelectedView = ViewMode.New;
    }

    [RelayCommand]
    void EditVisit()
    {
        if (SelectedPerson is not null)
        {
            SelectedView = ViewMode.Edit;
        }
    }

    public void ListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (SelectedPerson is not null)
        {
            SelectedView = ViewMode.View;
        }
    }
}

The Result Preview enter image description here

1

There are 1 best solutions below

6
Andrew KeepCoding On

This is one of the ways to achieve this.

Create ViewModels for each mode:

// Default (View mode?)
public partial class PersonViewModel : ObservableObject
{
    [ObservableProperty]
    private Person? person;
}

// New
public partial class NewPersonViewModel : PersonViewModel
{
}

// Edit
public partial class EdittingPersonViewModel : PersonViewModel
{
}

And in your MainViewModel:

public partial class MainViewModel : ObservableRecipient
{
    [ObservableProperty]
    Person? selectedPerson;

    [OservableProperty]
    private PersonViewModel? targetPerson;

    [RelayCommand]
    void NewVisit()
    {
        PersonList.Add(new Person() { Name = "New Person" });
        SelectedPerson = PersonList[^1];
        TargetPerson = new NewPersonViewModel { Person = SelectedPerson }; 
    };


    [RelayCommand]
    void EditVisit()
    {
        if (SelectedPerson is not null)
        {
            TargetPerson = new EdittingPersonViewModel { Person = SelectedPerson };
        }
    }

    // This method is:
    //     - called when the SelectedPerson is changed.
    //     - auto-generated by the CommunityToolkit.Mvvm.
    partial void OnSelectedPersonChanged(Person? value)
    {
        if (SelectedPerson is not null)
        {
            TargetPerson = new PersonViewModel { Person = value };
        }
    }
}

Change your TemplateSelector:

public class VisitTemplateSelector : DataTemplateSelector
{
    public DataTemplate? VisitEditTemplate { get; set; }
    public DataTemplate? VisitEmptyTemplate { get; set; }
    public DataTemplate? VisitNewTemplate { get; set; }
    public DataTemplate? VisitViewTemplate { get; set; }

    protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container)
    {
        return item switch
        {
            NewPersonViewModel => VisitNewTemplate,
            EdittingPersonViewModel => VisitEditTemplate,
            PersonViewModel => VisitViewTemplate,
            _ => VisitEmptyTemplate,
        };
    }
}

Bind the ContentView to the TargetPerson:

<ContentControl
     x:Name="ContentView"
     Content="{x:Bind ViewModel.TargetPerson, Mode=OneWay}"
     ContentTemplateSelector="{StaticResource visitTemplateSelector}" />