Home > Software design >  How to handle Master-Detail screen communication in WPF with MVVM architecture?
How to handle Master-Detail screen communication in WPF with MVVM architecture?

Time:09-17

I'm trying to build my first app with WPF and in order to fully understand MVVM I'm not using any framework, the only helper I use is Microsoft.Toolkit.Mvvm I have thi app with 2 pages, one is the master and the other one is the detail. I did set up navigation as it's explained in WPF MVVM navigate views Now I don't understand how I should tell to the detail screen which data it should display, since I'm not allowed to pass parameters to the viewmodel that I am instantiating in the datacontext.

My MainWindow.xaml

<Window x:Class="AlgsManagerDesktop.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AlgsManagerDesktop"
        xmlns:views="clr-namespace:AlgsManagerDesktop.Views"
        xmlns:viewModel="clr-namespace:AlgsManagerDesktop.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate DataType="{x:Type viewModel:MasterViewModel}">
            <views:MasterView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type viewModel:DetailsViewModel}">
            <views:DetailsView />
        </DataTemplate>
    </Window.Resources>
    <Window.DataContext>
        <viewModel:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <ContentControl Content="{Binding ViewModel}" />
    </Grid>
</Window>

MainWindowViewModel.cs

public class MainWindowViewModel : ObservableObject
    {
        private BaseViewModel viewModel;
        public BaseViewModel ViewModel
        {
            get => viewModel;
            set => SetProperty(ref viewModel, value);
        }

        public RelayCommand SwitchToDetailsCommand { get; }

        public MainWindowViewModel()
        {
            ViewModel = new MasterViewModel();
            SwitchToDetailsCommand = new RelayCommand(SwitchToDetails);
        }

        private void SwitchToDetails()
        {
            ViewModel = new DetailsViewModel();
        }
    }

MasterViewModel.cs

public class MasterViewModel : BaseViewModel
    {
        private ItemModel selectedItem;
        public ItemModel SelectedItem
        {
            get => selectedItem;
            set
            {
                SetProperty(ref selectedItem, value);
                DeleteCommand.NotifyCanExecuteChanged();
            }
        }

        public ObservableCollection<ItemModel> items { get; set; }
        public RelayCommand DeleteCommand { get; }

        public MasterViewModel()
        {
            DeleteCommand = new RelayCommand(RemoveItem, ItemIsSelected);
        }

        private void RemoveItems()
        {
            AlgSets.Remove(SelectedItem);
        }

        private bool ItemIsSelected()
        {
            return SelectedItem != null;
        }
    }

MasterView.xaml

<UserControl x:Class="AlgsManagerDesktop.Views.MasterView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AlgsManagerDesktop.Views"
             xmlns:viewModel="clr-namespace:AlgsManagerDesktop.ViewModel"
             xmlns:root="clr-namespace:AlgsManagerDesktop"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">

    <UserControl.DataContext>
        <viewModel:MasterViewModel/>
    </UserControl.DataContext>

<!-- ListBox here that updates a SelectedItem property -->

<!-- this button handles navigation to details screen, I'd like to pass SelectedItem to the next screen -->
<Button Command="{Binding DataContext.SwitchToDetailsCommand,
        RelativeSource={RelativeSource AncestorType={x:Type root:MainWindow}}, 
        Mode=OneWay}">
        Open Selected
</Button>
</UserControl>

DetailsView.xaml

<UserControl x:Class="AlgsManagerDesktop.Views.DetailsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AlgsManagerDesktop.Views"
             xmlns:viewModel="clr-namespace:AlgsManagerDesktop.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">

    <UserControl.DataContext>
        <viewModel:DetailsViewModel/>
    </UserControl.DataContext>
    <-- Item details here, I'd like to take them from an Item property in the DetailsViewModel -->
</UserControl>

CodePudding user response:

The DetailsView should inherit the DataContext from the ViewModel property of the MainWindowViewModel which it will if you remove the following XAML markup from it, i.e. you should not set the DataContext of the UserControl explicitly somewhere:

<UserControl.DataContext>
    <viewModel:DetailsViewModel/>
</UserControl.DataContext>

It's then up to the MainWindowViewModel to initialize and set the state of the DetailsViewModel.

CodePudding user response:

You created a SelectedItem property in MasterViewModel, presumably to bind to the SelectedItem property of your presumable ListBox that's missing from your XAML, but that is a dead-end view model. In fact I'd argue that you shouldn't split your view model in three (the actual view model, the master one and the details one) because they're all linked together -- they're one view split in a view and 2 sub-views, so logically you should have one view model.

It should be immediately obvious that your approach isn't going to work because when you create the master/details view models in your code you don't link them together at all, you just create throw-aways.

The alternative if you want to keep your 3 view models separate for whatever reason is to keep a property link to the main view model in both of them, and to move the SelectedItem property to the main view model, then bind to it in both sub-views.

  • Related