MVVM – 视图/视图模型通信
在本章中,我们将学习如何向 MVVM 应用程序添加交互性以及如何干净地调用逻辑。您还将看到,所有这一切都是通过维护松散耦合和良好的结构来完成的,这是 MVVM 模式的核心。为了理解这一切,首先让我们了解命令。
通过命令查看/视图模型通信
命令模式已经有很好的文档记录,并且几十年来经常使用设计模式。在此模式中有两个主要参与者:调用者和接收者。
祈求者
调用者是一段可以执行一些命令式逻辑的代码。
通常,它是用户在 UI 框架上下文中与之交互的 UI 元素。
它可能只是应用程序中其他地方的另一块逻辑代码。
接收者
接收器是调用程序触发时要执行的逻辑。
在 MVVM 上下文中,接收器通常是 ViewModel 中需要调用的方法。
在这两者之间,有一个阻塞层,这意味着调用者和接收者不必明确地了解彼此。这通常表示为暴露给调用者的接口抽象,并且该接口的具体实现能够调用接收者。
让我们看一个简单的示例,您将在其中学习命令以及如何使用它们在 View 和 ViewModel 之间进行通信。在本章中,我们将继续使用上一章中的相同示例。
在 StudentView.xaml 文件中,我们有一个 ListBox,它连接 ViewModel 中的学生数据。现在让我们添加一个用于从列表框中删除学生的按钮。
重要的是,在按钮上使用命令非常容易,因为它们有一个命令属性可以连接到 ICommand。
因此,我们可以在 ViewModel 上公开一个具有 ICommand 的属性,并从按钮的命令属性绑定到它,如以下代码所示。
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
让我们在项目中添加一个新类,它将实现 ICommand 接口。以下是ICommand接口的实现。
using System;
using System.Windows.Input;
namespace MVVMDemo {
public class MyICommand : ICommand {
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public MyICommand(Action executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod){
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime
is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod();
}
}
}
}
正如您所看到的,这是 ICommand 的一个简单委托实现,其中我们有两个委托,一个用于executeMethod,另一个用于canExecuteMethod,可以在构造时传入。
在上面的实现中,有两个重载的构造函数,一个仅用于executeMethod,另一个用于两个executeMethod和我可以的canExecuteMethod。
让我们在 StudentView Model 类中添加一个 MyICommand 类型的属性。现在我们需要在 StudentViewModel 中构造一个实例。我们将使用带有两个参数的 MyICommand 的重载构造函数。
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
现在添加 OnDelete 和 CanDelete 方法的实现。
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
我们还需要添加一个新的 SelectedStudent 以便用户可以从 ListBox 中删除所选项目。
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
以下是 ViewModel 类的完整实现。
using MVVMDemo.Model;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
}
}
在 StudentView.xaml 中,我们需要在 ListBox 中添加 SelectedItem 属性,该属性将绑定到 SelectStudent 属性。
<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>
以下是完整的 xaml 文件。
<UserControl x:Class = "MVVMDemo.Views.StudentView"
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:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation = "Horizontal">
<ListBox ItemsSource = "{Binding Students}"
SelectedItem = "{Binding SelectedStudent}"/>
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
</StackPanel>
</Grid>
</UserControl>
编译并执行上述代码后,您将看到以下窗口。
您可以看到删除按钮被禁用。当您选择任何项目时,它将启用。
当您选择任何项目并按删除时。您将看到所选项目列表被删除,并且删除按钮再次被禁用。
我们建议您逐步执行上述示例,以便更好地理解。