0

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>
Essay97
  • 648
  • 1
  • 9
  • 24

2 Answers2

1

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.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • This is working perfectly, thanks! The only last bit is that the IDE is complaining at some points with "No DataContext found for *whatever*" and is not suggesting code completion when I write the bindings, but everything works great! – Essay97 Aug 26 '21 at 18:26
0

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.

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • I am using UserControl in order to represent different pages, the Window is just kind of a container for the navigation so I thought they should have their own ViewModel... Am I wrong? – Essay97 Aug 26 '21 at 18:21