正确使用 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.
因此,如果您 MainWindowModel
中 LoadData()
的代码通常有用并且将被多个 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();
}
以上只是一些没有实现的示例代码,但你明白了。
这里的重点是 TakePhoto
和 Reset
这样的方法如果包含在 Model 中而不是 视图模型。这是一个好主意的原因之一是因为它确保您的模型可以被多个 ViewModel 使用,而不必跨多个 ViewModel 实现逻辑,因为逻辑包含在模型中。
也就是说,在某些情况下模型没有任何行为,仅作为数据对象。
回答你的问题:
Model 只知道自己,因此在您的场景中,加载模型是 ViewModel 的责任。将您的加载方法放在 ViewModel 中是正确的。
更进一步,如果您的模型逻辑加载包含在 Service class 中,您的 ViewModel 可以引用服务以加载适当的模型。这将允许多个 ViewModel 引用服务并减少代码重复。
我希望这个问题不是笼统的。
我曾经在 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.
因此,如果您 MainWindowModel
中 LoadData()
的代码通常有用并且将被多个 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();
}
以上只是一些没有实现的示例代码,但你明白了。
这里的重点是 TakePhoto
和 Reset
这样的方法如果包含在 Model 中而不是 视图模型。这是一个好主意的原因之一是因为它确保您的模型可以被多个 ViewModel 使用,而不必跨多个 ViewModel 实现逻辑,因为逻辑包含在模型中。
也就是说,在某些情况下模型没有任何行为,仅作为数据对象。
回答你的问题:
Model 只知道自己,因此在您的场景中,加载模型是 ViewModel 的责任。将您的加载方法放在 ViewModel 中是正确的。
更进一步,如果您的模型逻辑加载包含在 Service class 中,您的 ViewModel 可以引用服务以加载适当的模型。这将允许多个 ViewModel 引用服务并减少代码重复。