MVVM - View / ViewModel通信

在本章中,我们将学习如何向MVVM应用程序添加交互性以及如何干净地调用逻辑.您还将看到所有这些都是通过维护松散耦合和良好的结构来完成的,这是MVVM模式的核心.要理解这一切,首先让我们了解命令.

通过命令查看/ViewModel通信

命令模式已有详细记录并经常使用几十年的设计模式.在这种模式中有两个主要的角色,即调用者和接收者.

View and ViewModel Communication

Invoker

  • 调用程序是一段可以执行命令逻辑的代码.

  • 通常,它是用户在UI框架环境中与之交互的UI元素.

  • 它可能只是应用程序中其他地方的另一块逻辑代码.

Receiver

  • 接收者是在调用者触发时执行的逻辑.

  • 在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和我都可以执行一个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中删除Selected Item.

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>

编译并执行上述代码后,您将看到以下窗口.

View和ViewModel Communication MainWindow1

您可以看到删除按钮被禁用.选择任何项目时都会启用它.

View and ViewModel Communication MainWindow2

选择任何项目并按删除.您将看到所选项目列表被删除,删除按钮再次被禁用.

View and ViewModel Communication MainWindow3

我们建议您逐步执行上述示例,以便更好地理解.