在尝试基于 ListView 中的项目创建新的 window 时,这是否正确使用了 MVVM?

Is this proper use of MVVM when trying to create a new window based on a item in a ListView?

所以我目前正在研究 WPF 和 MVVM,我一直在尝试找到一种方法来 select 列表中的项目并将其显示在新的 window 中。我想出了一个我个人喜欢的解决方案,但我不确定它是否遵循有效的 MVVM 架构。

所以我有我的 MainWindow.xaml

<Window x:Class="Views.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:Views" 
        xmlns:viewmodel="clr-namespace:Views.MVVM.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        Background="#252525">

    <Window.DataContext>
        <viewmodel:MainViewModel/>
    </Window.DataContext>

    <StackPanel Orientation="Horizontal">
        <ListView ItemsSource="{Binding NetworkObjects}"
                  Style="{StaticResource ListStyle}"
                  Name="MainList"/>
    </StackPanel>
</Window>

它使用此样式绑定集合中的每个项目,还应用 MouseBinding 允许我 LeftDoubleClick 项目调用命令。 我将整个对象作为命令参数传递,因为那是 DataContext。 这是新 Window.

所需要的
<Style TargetType="ListView" x:Key="ListStyle">
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="BorderThickness" Value="0"/>

    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="ListViewItem">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListViewItem">
                            <ContentPresenter />
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>

    <Setter Property="ItemTemplate">
        <Setter.Value>
            <DataTemplate>
                <DockPanel Margin="2">
                    <DockPanel.InputBindings>
                        <MouseBinding Gesture="LeftDoubleClick" 
                                      Command="{Binding DisplayItemCommand}"
                                      CommandParameter="{Binding Path=.}"/>
                    </DockPanel.InputBindings>
                    <DockPanel.Style>
                        <Style TargetType="DockPanel">
                            <Style.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter Property="Background" Value="#303030"/>
                                </Trigger>
                                <Trigger Property="IsMouseOver" Value="False">
                                    <Setter Property="Background" Value="Transparent"/>
                                </Trigger>
                                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                               AncestorType={x:Type ListBoxItem}}, 
                                                               Path=IsSelected}" Value="True">
                                    <Setter Property="Background" Value="MediumSpringGreen"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </DockPanel.Style>
                    <TextBlock Text="{Binding NetworkModel.Address}" Foreground="Black"/>
                </DockPanel>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

MainViewModel 除了在构造函数中生成一些虚拟数据外什么都不做。这是我开始怀疑这是否是有效的 MVVM 的地方,因为我生成的对象不是基于 Model 而是基于整个 ViewModel ,这在理论上是有道理的,但我是不确定。

public ObservableCollection<NetworkObjectViewModel> NetworkObjects { get; set; }
public MainViewModel()
{
    NetworkObjects = new ObservableCollection<NetworkObjectViewModel>();
    for (int i = 0; i < 10; i++)
    {
        NetworkObjects.Add(new NetworkObjectViewModel() { NetworkModel = new NetworkModel { Address = $"Address {i}", Port = i } });
    }
}

并且 NetworkObjectViewModel 包含一个 RelayCommandNetworkModel

public class NetworkObjectViewModel
{
    public NetworkModel NetworkModel { get; set; }
    public RelayCommand DisplayItemCommand { get; set; }

    public NetworkObjectViewModel()
    {
        DisplayItemCommand = new RelayCommand(o =>
        {
            WindowService.ShowWindow(o);
        });
    }
}

WindowService很简单,就是新建一个GenericWindow,然后设置DataContext,这样它就可以根据UserControl做出判断并显示正确的UserControl DataContext

internal class WindowService
{
    public static void ShowWindow(object DataCtx)
    {
        var t  = new GenericWindow();
        t.DataContext = DataCtx;
        t.Show();
    }
}

这是 GenericWindow.xaml

    <Window.Resources>
        <DataTemplate DataType="{x:Type vms:NetworkObjectViewModel}">
            <v:TestView/>
        </DataTemplate>
    
        <DataTemplate DataType="{x:Type vms:NetworkObjectViewModel2}">
            <v:TestView2/>
        </DataTemplate>
    </Window.Resources>
    <ContentPresenter Content="{Binding .}" />

TestView.xaml 只是一个简单的 UserControl,看起来像这样

<Grid>
    <TextBox Text="{Binding NetworkModel.Address, UpdateSourceTrigger=PropertyChanged}"
               Width="100"
               Height="25"
               IsEnabled="True"/>
</Grid>

This is where I start doubting whether or not this is valid MVVM, because I'm generating objects that are not based on a Model but rather an entire ViewModel which in theory makes sense, but I'm not sure.

这很有道理。

MVVM 中的模型通常指的是您不想直接绑定到的类型,例如数据传输对象 (DTO)、包含业务逻辑甚至服务或存储库的域对象。

创建并公开“子”视图模型的集合以从“父”视图模型绑定到 class 是一种非常好的通用方法。