MVVM – 视图/视图模型通信
在本章中,我们将学习如何为 MVVM 应用程序添加交互性以及如何干净地调用逻辑。您还将看到,所有这些都是通过维护松散耦合和良好的结构来实现的,这是 MVVM 模式的核心。要理解这一切,首先让我们了解命令。
通过命令进行视图/视图模型通信
命令模式已有详尽的文档,并且几十年来经常使用设计模式。在此模式中,有两个主要参与者,即调用者和接收者。
调用者
调用者是一段可以执行某些命令式逻辑的代码。
通常,它是 UI 框架上下文中用户与之交互的 UI 元素。
它可能只是应用程序中其他地方的另一段逻辑代码。
接收者
接收者是调用者触发时要执行的逻辑。
在 MVVM 上下文中,接收者通常是 ViewModel 中需要的方法调用。
在这两者之间,有一个阻碍层,这意味着调用者和接收者不必明确了解彼此。这通常表示为暴露给调用者的接口抽象,并且该接口的具体实现能够调用接收者。
让我们看一个简单的例子,您将在其中学习命令以及如何使用它们在 View 和 ViewModel 之间进行通信。在本章中,我们将继续使用上一章中的相同示例。
在 StudentView.xaml 文件中,我们有一个 ListBox,它从 ViewModel 中挂接学生数据。现在让我们添加一个按钮,用于从 ListBox 中删除学生。
重要的是,使用按钮上的命令非常容易,因为它们具有命令属性,可以连接到 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 和 can canExecuteMethod。
让我们在 StudentView Model 类中添加 MyICommand 类型的属性。现在我们需要在 StudentViewModel 中构造一个实例。我们将使用带有两个参数的 MyICommand 重载构造函数。
public MyICommand DeleteCommand { get; set;} public StudentViewModel() { LoadStudents(); DeleteCommand = new MyICommand(OnDelete, CanDelete); }
Now add the implementation of OnDelete and CanDelete methods.
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}"/>
Following is the complete xaml file.
<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>
当编译并执行上述代码时,您将看到以下窗口。
您可以看到删除按钮被禁用。当您选择任何项目时,它将被启用。
当您选择任何项目并按删除键时。您将看到所选项目列表被删除,并且删除按钮再次变为禁用状态。
为了更好地理解,我们建议您逐步执行上述示例。