正确使用 MVVM 模式

Correct usage of the MVVM-Pattern

我希望这个问题不是笼统的。

我曾经在 MVVM 模式中实现我的 WPF 应用程序。因此,我总是为 View 创建一个文件夹,为 ViewModel 创建一个文件夹,为 Model 创建一个文件夹(以及一些其他文件夹,用于 Behaviours、Converter...)

首先,我将 MainWindowView.xaml 放入 View 文件夹。然后我将 MainWindowViewModel.cs 添加到 ViewModel- 文件夹。在此之后,我添加了一个 MainWindowModel.cs-File 到 Model-Folder。

MainWindowModel.cs-文件是我有问题的地方。

我习惯将此 class 用于我的业务逻辑,例如从数据库加载数据或解析 xml 文件并将结果放入集合中。

例如,如果我想通过单击按钮从 XML-文件中加载数据,我使用类似的方法来实现:

MainWindowView

<Window x:Class="MVVMExample.View.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModel="clr-namespace:MVVMExample.ViewModel"
        Title="MainWindowView" Height="300" Width="300">
    <Window.DataContext>
        <viewModel:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Button Command="{Binding LoadDataCommand}" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10" Content="Load data" Width="120"/>
    </Grid>
</Window>

MainWindowViewModel

internal class MainWindowViewModel : ViewModelBase
{
    private readonly MainWindowModel mainWindowModel;

    public MainWindowViewModel()
    {
        mainWindowModel = new MainWindowModel();
        mainWindowModel.XmlEntriesLoaded += XmlEntriesLoaded;
        LoadDataCommand = new RelayCommand(LoadData);
    }

    private void XmlEntriesLoaded(object sender, GenericEventArgs<List<XmlEntry>> e)
    {
        List<XmlEntry> entries = e.Value;
        // Display entries in ObservableCollection
    }

    private void LoadData(object parameter)
    {
        mainWindowModel.LoadData();
    }

    private ICommand loadDataCommand;
    public ICommand LoadDataCommand
    {
        get { return loadDataCommand; }
        set
        {
            loadDataCommand = value;
            OnPropertyChanged();
        }
    }
}

MainWindowModel

internal class MainWindowModel
{
    public EventHandler<GenericEventArgs<List<XmlEntry>>> XmlEntriesLoaded;

    public void LoadData()
    {
        BackgroundWorker backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += (s, e) =>
        {
            // Do load the data here and assign the result to e.Result
        };
        backgroundWorker.RunWorkerCompleted += (s, e) =>
        {
            EventHandler<GenericEventArgs<List<XmlEntry>>> temp = XmlEntriesLoaded;
            if (temp != null)
            {
                temp(this, new GenericEventArgs<List<XmlEntry>>((List<XmlEntry>) e.Result));
            }
        };
        backgroundWorker.RunWorkerAsync();
    }
}

class XmlEntry 也位于 Model- 文件夹中。

现在有人告诉我,模型层只包含业务对象,没有像我在 MainWindowModel 中那样的逻辑。加载数据的交互应该位于 ViewModel 中。是这样吗?所以我做错了吗?

我不会说你这样做 本身。通常我的模型 classes 是非常基本的东西,它们只是定义了我的程序所需的对象。 ViewModel 包含一组方法和属性,这些方法和属性由 View 绑定,并使用 Model classes。 'normal' MVVM 模式将有 1 个视图到 1 个 ViewModel,然后是许多定义业务对象的模型 classes,然后是支持操作所需的任何补充 classes在 ViewModel 中定义。

我通常会将您的 'LoadData' 方法放入它自己的 class 中,调用类似于 'DataService' 的东西,它将负责您的程序需要的任何外部数据操作。它与您的模型 classes 不同,后者仅定义对象,但不一定属于您的 ViewModel 的严格定义范围。

Model - ViewModel - View 之间的关系不是 1:1:1。 而是M:N:O。 所以多个 Views 可以使用一个 ViewModel 并且一个 ViewModel 可以使用多个 Models.

通常 Model 包含 classes 定义数据结构及其关系,例如 Entity framework classes。我通常有 Model classes 的单独 VS 项目。所以例如如果您的应用程序使用 3 个数据库,您的 VS 解决方案中可以有 3 个模型。模型 classes 名称应遵循您的应用程序的 UML class 模型或您的数据库模型并使用适当的名称。 MainWindowModel 不是模型 class 的好名字 - 它表明它属于 MainWindow,这是错误的 - 它应该包含通常可用的 classes.

因此,如果您 MainWindowModelLoadData() 的代码通常有用并且将被多个 ViewModels 或其他组件使用,那么您应该将其放入您的 Model 在 class 中命名为例如CustomerXMLData但也有可能 ViewModel 根本不使用 Model classes。 IE。您不必为每个 ViewModel 创建 XXXWindowModel class - 这不是 MVVM 的重点。重点是模块化代码的可重用性和没有重复代码的良好设计。

我之前就此进行过几次辩论。您的模型不仅仅是一个哑数据对象,它应该包含与您的 ViewModel.

一样的行为

考虑示例:

class Camera
    {
        BitmapImage CurrentFrame { get; set; }
        BitmapImage CapturedFrame { get; set; }
        VideoCaptureDevice CaptureDevice { get; set; }

        void TakePhoto();
        void ClearFrame();
        void Reset();
    }

以上只是一些没有实现的示例代码,但你明白了。

这里的重点是 TakePhotoReset 这样的方法如果包含在 Model 中而不是 视图模型。这是一个好主意的原因之一是因为它确保您的模型可以被多个 ViewModel 使用,而不必跨多个 ViewModel 实现逻辑,因为逻辑包含在模型中。

也就是说,在某些情况下模型没有任何行为,仅作为数据对象。

回答你的问题:

Model 只知道自己,因此在您的场景中,加载模型是 ViewModel 的责任。将您的加载方法放在 ViewModel 中是正确的。

更进一步,如果您的模型逻辑加载包含在 Service class 中,您的 ViewModel 可以引用服务以加载适当的模型。这将允许多个 ViewModel 引用服务并减少代码重复。