MVVM设计模式将应用分为三层,Model(模型层/实体层)、 ViewModel(视图模型层/UI模型层)、 View(视图层/UI层)。目前主流的开发都习惯用ORM框架进行数据库的设计,这里的模型层,存放的实体类,与数据表的字段一一对应。UI模型层,可以将模型层的数据进行选择性的读取(数据塑形),避免隐私数据泄露,同时作为UI层的数据类,与视图实现双向绑定,完成UI开发与业务开发的解耦,扩展性大大提高。
MVVM架构 View与ViewModel之间可以绑定两种属性,分别是数据属性和命令属性,数据属性可以双向绑定,命令属性只能由UI层调用视图模型层,也就是单向绑定。
这里展示了数据属性的双向绑定
1 2 ViewModel . Property  <-  Binding  Engine  ->  UI . Control . Property       ( INotifyPropertyChanged )         ( TwoWay  Mode )  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 graph TD     subgraph Backend         A[Backend System]     end     subgraph Model         B[Data Properties]         A --> B     end     subgraph ViewModel         C[Command Properties]         D[Data Properties]         B --> D         D --> B     end     subgraph View         E[TextBox]         F[Button]         E --> D         D --> E         F --> C     end     style Backend fill:#f9f,stroke:#333,stroke-width:4px     style Model fill:#bbf,stroke:#333,stroke-width:4px     style ViewModel fill:#bfb,stroke:#333,stroke-width:4px     style View fill:#ffb,stroke:#333,stroke-width:4px 
手动实现过程 先创建View,Model,ViewModel三个文件夹,存放对应的类,这里主要展示MVVM的基本实现。
NotIfiactionObject:所有ViewModel的基类,RaisePropertyChanged方法是实现双向绑定的核心,负责管理数据属性,由WPF 绑定引擎 订阅事件,当相关的属性被修改的时候,会调用事件,WPF 绑定引擎 接受到后,更新UI层的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using  System;using  System.Collections.Generic;using  System.ComponentModel;using  System.Linq;using  System.Text;using  System.Threading.Tasks;namespace  WPF_MVVM_Learning.ViewModels {                    class  NotIfiactionObject  : INotifyPropertyChanged      {         public  event  PropertyChangedEventHandler? PropertyChanged;         public  void  RaisePropertyChanged (string  propertyName         {             if  (this .PropertyChanged != null )             {                 this .PropertyChanged.Invoke(this , new  PropertyChangedEventArgs(propertyName));             }         }     } } 
DelegateCommand:负责处理命令属性。放在Command文件夹里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 using  System;using  System.Collections.Generic;using  System.Linq;using  System.Text;using  System.Threading.Tasks;using  System.Windows.Input;namespace  WPF_MVVM_Learning.Commands {     class  DelegateCommand  : ICommand      {         public  Action<object > ExecuteAction { get ; set ; }         public  Func<object , bool > CanExecuteAction { get ; set ; }         public  event  EventHandler? CanExecuteChanged;         public  bool  CanExecute (object ? parameter         {             if  (this .CanExecuteAction == null )             {                 return  true ;             }             return  this .CanExecuteAction.Invoke(parameter);         }         public  void  Execute (object ? parameter         {             if  (this .ExecuteAction == null )             {                 return ;             }             this .ExecuteAction?.Invoke(parameter);         }     } } 
MainWindowsViewModel:WPF主窗体对应的视图模型,每一次设置属性的值,都会执行 this.RaisePropertyChanged(nameof(Input1));,WPF 绑定引擎 收到事件的发布后,更新页面的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 using  Microsoft.Win32;using  System;using  System.Collections.Generic;using  System.Globalization;using  System.Linq;using  System.Text;using  System.Threading.Tasks;using  WPF_MVVM_Learning.Commands;namespace  WPF_MVVM_Learning.ViewModels {     class  MainWindowsViewModel  : NotIfiactionObject      {         private  decimal  input1;         public  decimal  Input1         {             get  { return  input1; }             set              {                 input1 = value ;                 this .RaisePropertyChanged(nameof (Input1));             }         }         private  decimal  input2;         public  decimal  Input2         {             get  { return  input2; }             set              {                 input2 = value ;                 this .RaisePropertyChanged(nameof (Input2));             }         }         private  decimal  result;         public  decimal  Result         {             get  { return  result; }             set              {                 result = value ;                 this .RaisePropertyChanged(nameof (Result));             }         }         public  DelegateCommand AddCommand { get ; set ; }         public  DelegateCommand SaveCommand { get ; set ; }         private  void  Add (object  parameter         {             this .Result = 0 ;             this .Result = this .Input1 + this .Input2;         }         private  void  Save (object  parameter         {             SaveFileDialog saveFileDialog = new  SaveFileDialog();             saveFileDialog.ShowDialog();         }         public  MainWindowsViewModel ()         {             this .AddCommand = new  DelegateCommand();             this .AddCommand.ExecuteAction = new  Action<object >(this .Add);             this .SaveCommand = new  DelegateCommand();             this .SaveCommand.ExecuteAction = new  Action<object >(this .Save);         }     } } 
MainWindow.xaml.cs:通过DataContext拿到视图模型,UI层绑定使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 using  System.Text;using  System.Windows;using  System.Windows.Controls;using  System.Windows.Data;using  System.Windows.Documents;using  System.Windows.Input;using  System.Windows.Media;using  System.Windows.Media.Imaging;using  System.Windows.Navigation;using  System.Windows.Shapes;using  WPF_MVVM_Learning.ViewModels;namespace  WPF_MVVM_Learning {                    public  partial  class  MainWindow  : Window      {         public  MainWindow ()         {             InitializeComponent();             this .DataContext = new  MainWindowsViewModel();         }     } } 
MainWindow.xaml实现,通过Binding绑定,数据属性主要用Text标签,命令属性主要用Command标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <Window     x:Class="WPF_MVVM_Learning.MainWindow"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     xmlns:local="clr-namespace:WPF_MVVM_Learning"     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     Title="MainWindow"     Width="500"     Height="350"     mc:Ignorable="d">     <Grid>         <Grid.RowDefinitions>             <RowDefinition Height="Auto" />             <RowDefinition Height="*" />         </Grid.RowDefinitions>         <Button Command="{Binding SaveCommand}" Content="Save" />         <Grid Grid.Row="1">             <Grid.RowDefinitions>                 <RowDefinition Height="Auto" />                 <RowDefinition Height="Auto" />                 <RowDefinition Height="Auto" />                 <RowDefinition Height="*" />             </Grid.RowDefinitions>             <TextBox                 x:Name="tb1"                 Grid.Row="0"                 Margin="4"                 Background="LightBlue"                 FontSize="24"                 Text="{Binding Input1, Mode=TwoWay}" />             <TextBox                 x:Name="tb2"                 Grid.Row="1"                 Margin="4"                 Background="LightBlue"                 FontSize="24"                 Text="{Binding Input2}" />             <TextBox                 x:Name="tb3"                 Grid.Row="2"                 Margin="4"                 Background="LightBlue"                 FontSize="24"                 Text="{Binding Result}" />             <Button                 x:Name="addButton"                 Grid.Row="3"                 Width="120"                 Height="80"                 Command="{Binding AddCommand}"                 Content="Add"                 FontSize="26" />         </Grid>     </Grid> </Window> 
在上述提到的MainWindowsViewModel类中,只有向UI层传递数据的操作,Text="{Binding Input1, Mode=TwoWay}" />,这里有一个模式(Mode)选择,其中TwoWay(双向绑定),就是UI层能将数据属性回传到UI模型层的配置,对于数据属性来说,这是默认配置,当我们将Mode的值改为OneWay(单向绑定),我们会发现UI层无法将数据回传到UI模型层。
借助Prism框架实现MVVM MainWindowViewModel类其中SetProperty方法间接调用了RaisePropertyChanged,但是在Prism框架的实现过程中,将事件的发布放到了另一个函数中,名称为OnPropertyChanged,RaisePropertyChanged的函数体,实际调用了OnPropertyChanged函数,从而完成事件发布。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 using  Microsoft.Win32;using  Prism.Commands;using  Prism.Mvvm;using  Prism.Regions;using  System.DirectoryServices;namespace  BlankAppTest.ViewModels {     public  class  MainWindowViewModel  : BindableBase      {         private  string  _title = "简单加法" ;         public  string  Title         {             get  { return  _title; }             set  { SetProperty(ref  _title, value ); }         }         private  decimal  input1;         public  decimal  Input1         {             get  { return  input1; }             set  { SetProperty(ref  input1, value ); }         }         private  decimal  input2;         public  decimal  Input2         {             get  { return  input2; }             set  { SetProperty(ref  input2, value ); }         }         private  decimal  result;         public  decimal  Result         {             get  { return  result; }             set  { SetProperty(ref  result, value ); }         }         private  DelegateCommand addCommand;         public  DelegateCommand AddCommand =>             addCommand ?? (addCommand = new  DelegateCommand(Add));         private  void  Add ()         {             this .Result = 0 ;             this .Result = this .Input1 + this .Input2;         }         private  DelegateCommand saveCommand;         public  DelegateCommand SaveCommand =>             saveCommand ?? (saveCommand = new  DelegateCommand(Save));         private  void  Save ()         {             SaveFileDialog saveFileDialog = new  SaveFileDialog();             saveFileDialog.ShowDialog();         }         private  DelegateCommand _NavigationCommand;         private  readonly  IRegionManager _manger;         public  DelegateCommand NavigationCommand =>             _NavigationCommand ?? (_NavigationCommand = new  DelegateCommand(Navigation));         private  void  Navigation ()         {             _manger.RequestNavigate("ContentRegion" , "HomeControl" );         }         public  MainWindowViewModel (IRegionManager regionManager )         {             _manger = regionManager;         }     } } 
在Prism框架中,BindableBase是所有ViewModel的基类,和NotIfiactionObject一样,在Prism 5.0之后,NotIfiactionObject被BindableBase替换,二者都实现了INotifyPropertyChanged接口。新基类的实现过程过程中,对于形参添加了[CallerMemberName]特性,如protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null),该特性可以在方法中自动获取调用该方法的成员(如属性或方法)的名称,而不需要手动传递这个名称,因此在使用Prism框架的时候,无需手动传递实参,由编译器自动获取注入。
MainWindow.xaml视图prism:ViewModelLocator.AutoWireViewModel="True"开启后会自动寻找ViewModels 文件夹下与当前UI对应的ViewModel,并将其绑定到当前UI的DataContext上,直白点说就就是执行了对应的代码,如this.DataContext = new MainWindowsViewModel();。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 <Window     x:Class="BlankAppTest.Views.MainWindow"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:prism="http://prismlibrary.com/"     Title="{Binding Title}"     Width="700"     Height="550"     prism:ViewModelLocator.AutoWireViewModel="True">     <Grid>         <Grid.RowDefinitions>             <RowDefinition Height="Auto" />             <RowDefinition Height="Auto" />             <RowDefinition Height="*" />         </Grid.RowDefinitions>         <Button             x:Name="SaveButton"             Grid.Row="0"             Command="{Binding SaveCommand}"             Content="Save" />         <Grid Grid.Row="1">             <Grid.RowDefinitions>                 <RowDefinition Height="Auto" />                 <RowDefinition Height="Auto" />                 <RowDefinition Height="Auto" />                 <RowDefinition Height="*" />             </Grid.RowDefinitions>             <TextBox                 x:Name="Input1"                 Grid.Row="0"                 Margin="10"                 FontSize="24"                 Text="{Binding Input1}" />             <TextBox                 x:Name="Input2"                 Grid.Row="1"                 Margin="10"                 FontSize="24"                 Text="{Binding Input2}" />             <TextBox                 x:Name="Result"                 Grid.Row="2"                 Margin="10"                 FontSize="24"                 Text="{Binding Result}" />             <Grid Grid.Row="3">                 <Grid.ColumnDefinitions>                     <ColumnDefinition />                     <ColumnDefinition />                 </Grid.ColumnDefinitions>                 <Button                     x:Name="AddButton"                     Grid.Column="0"                     Width="100"                     Height="60"                     Command="{Binding AddCommand}"                     Content="Add"                     FontSize="24" />                 <Button                     x:Name="NavigationButton"                     Grid.Column="1"                     Width="100"                     Height="60"                     Command="{Binding NavigationCommand}"                     Content="Navigation"                     FontSize="24" />             </Grid>         </Grid>         <ContentControl             Grid.Row="2"             Width="500"             Height="200"             prism:RegionManager.RegionName="ContentRegion" />     </Grid> </Window> 
局部页面跳转 HomeControl.xaml视图
1 2 3 4 5 6 7 8 9 10 11 12 13 <UserControl     x:Class="BlankAppTest.Views.HomeControl"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:prism="http://prismlibrary.com/"     prism:ViewModelLocator.AutoWireViewModel="True">     <Grid>         <TextBlock             Width="200"             Height="40"             Text="{Binding Title}" />     </Grid> </UserControl> 
HomeControlViewModel类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using  Prism.Commands;using  Prism.Mvvm;using  System;using  System.Collections.Generic;using  System.Linq;namespace  BlankAppTest.ViewModels {     public  class  HomeControlViewModel  : BindableBase      {         private  string  _title = "HomeControl" ;         public  string  Title         {             get  { return  _title = "HomeControl" ; }             set  { SetProperty(ref  _title, value ); }         }         public  HomeControlViewModel ()         {         }     } } 
App.xaml.cs文件,注册跳转页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using  BlankAppTest.ViewModels;using  BlankAppTest.Views;using  Prism.Ioc;using  System.Windows;namespace  BlankAppTest {                    public  partial  class  App      {         protected  override  Window CreateShell ()         {             return  Container.Resolve<MainWindow>();         }         protected  override  void  RegisterTypes (IContainerRegistry containerRegistry )         {             containerRegistry.RegisterForNavigation<HomeControl, HomeControlViewModel>();         }     } } 
渲染位置,在MainWindows.xaml上,利用IRegionManager进行跳转。
1 2 3 4 5 <ContentControl             Grid.Row="2"             Width="500"             Height="200"             prism:RegionManager.RegionName="ContentRegion" /> 
实现局部页面跳转的部分代码,位于MainWindowViewModel类中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private  readonly  IRegionManager _manger;public  DelegateCommand NavigationCommand =>    _NavigationCommand ?? (_NavigationCommand = new  DelegateCommand(Navigation)); private  void  Navigation (){     _manger.RequestNavigate("ContentRegion" , "HomeControl" ); } public  MainWindowViewModel (IRegionManager regionManager ){     _manger = regionManager; } 
Prism框架部分分析 DelegateCommand简单分析:
在使用单个参数的构造函数时,检查命令是否可以执行默认为True. 
ObservesProperty和ObservesCanExecute可以用来评估命令属性的可执行状态。DelegateCommand 派生自 DelegateCommandBase, DelegateCommandBase又派生自ICommand,在ICommand中有一个事件CanExecuteChanged,WPF 绑定引擎监听事件,当命令状态变化的时候自动更新命令属性的启用。Execute 和 CanExecute 方法通常是由 WPF 绑定机制  或 XAML 中的命令绑定  来调用的。用户点击按钮时,WPF 会调用 SaveCommand.Execute(),继而调用 DelegateCommand.Execute() 方法。在按钮状态变化时(例如数据变更后),WPF 会调用 SaveCommand.CanExecute() 来确定按钮是否应被启用。 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 using  System;using  System.Linq.Expressions;using  System.Windows.Input;using  Prism.Properties;namespace  Prism.Commands {                              public  class  DelegateCommand  : DelegateCommandBase      {         Action _executeMethod;         Func<bool > _canExecuteMethod;                                             public  DelegateCommand (Action executeMethod )             : this (executeMethod, ( )  => true )        {         }                                                               public  DelegateCommand (Action executeMethod, Func<bool > canExecuteMethod )             : base ()         {             if  (executeMethod == null  || canExecuteMethod == null )                 throw  new  ArgumentNullException(nameof (executeMethod), Resources.DelegateCommandDelegatesCannotBeNull);             _executeMethod = executeMethod;             _canExecuteMethod = canExecuteMethod;         }                                    public  void  Execute ()         {             _executeMethod();         }                                             public  bool  CanExecute ()         {             return  _canExecuteMethod();         }                                             protected  override  void  Execute (object  parameter         {             Execute();         }                                                      protected  override  bool  CanExecute (object  parameter         {             return  CanExecute();         }                                                               public  DelegateCommand ObservesProperty <T >(Expression<Func<T>> propertyExpression )         {             ObservesPropertyInternal(propertyExpression);             return  this ;         }                                                      public  DelegateCommand ObservesCanExecute (Expression<Func<bool >> canExecuteExpression )         {             _canExecuteMethod = canExecuteExpression.Compile();             ObservesPropertyInternal(canExecuteExpression);             return  this ;         }     } } 
ObservesProperty 和 ObservesCanExecute 简单分析:
根据注释来看,二者都具有更改命令熟悉可执行状态的能力,都对 RaiseCanExecuteChanged 的调用改为了自动触发。但是ObservesProperty 更适合有多个数据属性影响命令属性的执行,而 ObservesCanExecute 适合单个数据属性影响命令属性的执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private  DelegateCommand calculateCommand;public  DelegateCommand CalculateCommand =>    calculateCommand ?? (calculateCommand = new  DelegateCommand(Calculate, CanCalculate)     .ObservesProperty(() => Input1)     .ObservesProperty(() => Input2)); private  void  Calculate (){     SaveFileDialog saveFileDialog = new  SaveFileDialog();     saveFileDialog.ShowDialog(); } private  bool  CanCalculate (){     return  Input1 != 0  && Input2 != 0 ; } 
ObservesCanExecute 示例:只有在 CanSaveFlag 为 true 时,才能执行保存命令。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private  bool  canSaveFlag;public  bool  CanSaveFlag{     get  { return  canSaveFlag; }     set  { SetProperty(ref  canSaveFlag, value ); } } private  DelegateCommand saveCommand;public  DelegateCommand SaveCommand =>    saveCommand ?? (saveCommand = new  DelegateCommand(Save)     .ObservesCanExecute(() => CanSaveFlag)); private  void  Save (){      } 
ObservesProperty启停的详细解释:真正决定命令是否可以执行的是 CanCalculate 方法,而 ObservesProperty 的作用是触发 RaiseCanExecuteChanged,从而让 CanCalculate 方法在相关属性变化时重新评估命令的可执行状态。为了更清晰地解释 ObservesProperty 的作用,可以按以下方式详细说明其在命令状态管理中的角色。
详细解释 ObservesProperty 
ObservesProperty 的作用
观察属性 :ObservesProperty 方法用于监听一个属性的变化。自动触发 RaiseCanExecuteChanged :当被观察的属性发生变化时,ObservesProperty 自动调用 RaiseCanExecuteChanged,这会触发 CanCalculate 方法来重新评估 CalculateCommand 是否可以执行。 
为什么需要 ObservesProperty :
在不使用 ObservesProperty 的情况下,每次 Input1 或 Input2 改变时,需要手动调用 RaiseCanExecuteChanged 来更新命令的状态。 
使用 ObservesProperty 后,这个过程被自动化了,每当 Input1 或 Input2 改变时,SaveCommand 会自动检测它是否可以执行,无需手动干预。 
 
 
举个详细例子 假设没有使用 ObservesProperty,需要手动更新命令状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  decimal  Input1{     get  { return  input1; }     set      {         if  (SetProperty(ref  input1, value ))         {             SaveCommand.RaiseCanExecuteChanged();         }     } } public  decimal  Input2{     get  { return  input2; }     set      {         if  (SetProperty(ref  input2, value ))         {             SaveCommand.RaiseCanExecuteChanged();         }     } } 
使用 ObservesProperty 后的简化 使用 ObservesProperty,可以简化为:
1 2 3 4 5 private  DelegateCommand calculateCommand;public  DelegateCommand CalculateCommand =>    calculateCommand ?? (calculateCommand = new  DelegateCommand(Calculate, CanCalculate)     .ObservesProperty(() => Input1)     .ObservesProperty(() => Input2)); 
ObservesProperty 自动监听 Input1 和 Input2 的变化RaiseCanExecuteChanged,无需手动调用。这样,当 Input1 或 Input2 发生变化时,CanSave 会被自动调用来重新评估 SaveCommand 是否可以执行。 
 
结论 ObservesProperty 的作用在于自动化属性变化与命令状态更新之间的关联。虽然真正决定命令是否可以执行的是 CanCalculate 方法,但 ObservesProperty 确保了属性变化时会自动触发状态更新,从而减少了手动管理命令状态的繁琐工作。
ObservesCanExecute、ObservesProperty函数与CanExecute、Execute函数之间的关系ObservesCanExecute、ObservesProperty 函数与 CanExecute、Execute 之间的关系体现在 命令状态管理  和 执行逻辑  两个方面。它们共同作用,确保命令的可执行状态与相关属性的变化保持一致。
1. Execute 和 CanExecute 的作用 
ExecuteExecute 方法被调用,执行命令绑定的操作。CanExecutetrue 表示命令可以执行,返回 false 表示命令不能执行。WPF 框架在需要决定控件(如按钮)是否启用时会调用 CanExecute。 
2. ObservesProperty 的作用 
ObservesPropertyObservesProperty 会自动调用 RaiseCanExecuteChanged,从而触发 CanExecuteChanged 事件。WPF 框架会因此再次调用 CanExecute 方法,重新评估命令的可执行性。关系 :ObservesProperty 与 CanExecute 之间的关系是间接的。ObservesProperty 监听属性变化,然后自动通知 CanExecute 重新执行,从而决定命令是否仍然可以执行。 
3. ObservesCanExecute 的作用 
ObservesCanExecuteCanExecute 方法的逻辑。它会自动在表达式变化时触发 RaiseCanExecuteChanged。关系 :ObservesCanExecute 与 CanExecute 的关系更直接。ObservesCanExecute 的布尔表达式实际上替代了 CanExecute 的逻辑。当表达式的结果变化时,CanExecute 返回的值也会随之变化。 
4. 综合关系 
ExecuteDelegateCommand 的构造函数直接设置,决定了命令的执行逻辑。CanExecuteFunc<bool> 或者使用 ObservesCanExecute 来定义其逻辑。ObservesPropertyRaiseCanExecuteChanged,间接影响 CanExecute 的评估。ObservesCanExecuteCanExecute 的逻辑,并在相关属性变化时自动触发 RaiseCanExecuteChanged。 
示例 1 2 3 public  DelegateCommand SaveCommand =>    saveCommand ?? (saveCommand = new  DelegateCommand(Save)     .ObservesCanExecute(() => Input1 > 0  && Input2 > 0 )); 
在这个例子中:
Save 是 Execute 的逻辑。Input1 > 0 && Input2 > 0 是 CanExecute 的逻辑,通过 ObservesCanExecute 直接定义。每当 Input1 或 Input2 变化时,ObservesCanExecute 会自动触发 RaiseCanExecuteChanged,WPF 会重新检查 CanExecute 的返回值来决定 SaveCommand 是否可执行。