如何使用 MVVM 架构处理 WPF 中的 Master-Detail 屏幕通信?

How to handle Master-Detail screen communication in WPF with MVVM architecture?

我正在尝试使用 WPF 构建我的第一个应用程序,为了完全理解 MVVM 我没有使用任何框架,我使用的唯一帮助程序是 Microsoft.Toolkit.Mvvm 我有 2 个页面的应用程序,一个是主页面,另一个是细节页面。 我确实按照 WPF MVVM navigate views 中的说明设置了导航 现在我不明白我应该如何告诉详细信息屏幕它应该显示哪些数据,因为我不允许将参数传递给我在数据上下文中实例化的视图模型。

我的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>

您在 MasterViewModel 中创建了一个 SelectedItem 属性,大概是为了绑定到您可能丢失的 ListBoxSelectedItem 属性来自您的 XAML,但这是一个死胡同的视图模型。事实上,我认为您不应该将视图模型一分为三(实际视图模型、主视图模型和细节视图模型),因为它们都 linked 在一起——它们是一个视图拆分为一个视图和 2 个子视图,因此从逻辑上讲,您应该有一个视图模型。

很明显您的方法不会奏效,因为当您在代码中创建 master/details 视图模型时,您根本不会 link 它们,您只是创建一次性产品。

如果你出于任何原因想要将 3 个视图模型分开,另一种方法是将 属性 link 保留到主视图模型中,并将 SelectedItem 属性 到主视图模型,然后在两个子视图中绑定到它。

DetailsView 应该从 MainWindowViewModelViewModel 属性 继承 DataContext 如果你删除下面的 XAML 标记,即你不应该在某处明确设置 UserControlDataContext

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

然后由 MainWindowViewModel 初始化并设置 DetailsViewModel 的状态。