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
是否可执行。