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 的作用
Execute :定义了命令的具体执行逻辑。当用户触发某个命令时,Execute 方法被调用,执行命令绑定的操作。
CanExecute :决定命令是否可以执行。返回 true 表示命令可以执行,返回 false 表示命令不能执行。WPF 框架在需要决定控件(如按钮)是否启用时会调用 CanExecute。
2. ObservesProperty 的作用
ObservesProperty 方法用于监控某个属性的变更。当指定的属性发生变化时,ObservesProperty 会自动调用 RaiseCanExecuteChanged,从而触发 CanExecuteChanged 事件。WPF 框架会因此再次调用 CanExecute 方法,重新评估命令的可执行性。
关系 :ObservesProperty 与 CanExecute 之间的关系是间接的。ObservesProperty 监听属性变化,然后自动通知 CanExecute 重新执行,从而决定命令是否仍然可以执行。
3. ObservesCanExecute 的作用
ObservesCanExecute 方法不仅观察一个布尔表达式的变化,还将该表达式作为 CanExecute 方法的逻辑。它会自动在表达式变化时触发 RaiseCanExecuteChanged。
关系 :ObservesCanExecute 与 CanExecute 的关系更直接。ObservesCanExecute 的布尔表达式实际上替代了 CanExecute 的逻辑。当表达式的结果变化时,CanExecute 返回的值也会随之变化。
4. 综合关系
Execute :由 DelegateCommand 的构造函数直接设置,决定了命令的执行逻辑。
CanExecute :可以通过直接传递一个 Func<bool> 或者使用 ObservesCanExecute 来定义其逻辑。
ObservesProperty :用于监听某个属性变化,并在属性变化时自动触发 RaiseCanExecuteChanged,间接影响 CanExecute 的评估。
ObservesCanExecute :直接替代 CanExecute 的逻辑,并在相关属性变化时自动触发 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 是否可执行。