Prism
中对ICommand
接口进行了封装,使命令更容易使用。共有三种类别,分别对应三个实现类,分别为DelegateCommand
、DelegateCommand<T>
与CompositeCommand
。
1. DelegateCommand 与 DelegateCommand
DelegateCommand
与DelagateCommand<T>
的使用方式基本相同,区别在于DelagateCommand<T>
能够使用命令参数。在使用命令时,有几种方式可以改变命令的可执行状态
。
- 显示调用RaiseCanExecuteChanged()
- 创建命令时,使用ObservesProperty(Expression<Func> propertyExpression)
- 创建命令时,使用ObservesCanExecute(Expression<Func> canExecuteExpression)
1.1 样例
- 创建一个名称为
PrismCommandApp
的WPF应用程序项目 - 在项目根目录级别下,创建
Views
与ViewModels
文件夹 - 将
MainWindow.xaml
移动到Views
文件夹中,并更改其名称与名称空间分别为MainWindowView
与PrismCommandApp.Views
- 打开
Nuget
,搜索Prism
,安装Prism.Unity
,此处使用的版本为8.1.97
。 - 修改
App.xaml
与App.xaml.cs
分别如下:
<unity:PrismApplication
x:Class="PrismCommandApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:unity="http://prismlibrary.com/">
<Application.Resources />
</unity:PrismApplication>
using System.Windows;
using Prism.Ioc;
using Prism.Modularity;
using PrismCommandApp.Views;
namespace PrismCommandApp
{
public partial class App
{
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
}
protected override Window CreateShell()
{
return this.Container.Resolve<MainWindowView>();
}
}
}
- 由于此样例不止讲解一个知识点,因此需要对主页面进行区域划分。修改
MainWindowView.xaml
与MainWindowView.xaml.cs
分别如下:
<Window
x:Class="PrismCommandApp.Views.MainWindowView"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:viewModels="clr-namespace:PrismCommandApp.ViewModels"
Title="{Binding Title}"
Width="800"
Height="450"
d:DataContext="{d:DesignInstance viewModels:MainWindowViewModel}"
prism:ViewModelLocator.AutoWireViewModel="True"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<UniformGrid Margin="5" Columns="3">
<ContentControl prism:RegionManager.RegionName="DelegateCommandViewRegion" />
<ContentControl prism:RegionManager.RegionName="CompositeCommandViewRegion" />
<ContentControl prism:RegionManager.RegionName="ActiveAwareCommandViewRegion" />
</UniformGrid>
</Window>
using Prism.Regions;
namespace PrismCommandApp.Views
{
public partial class MainWindowView
{
public MainWindowView(IRegionManager regionManager)
{
this.InitializeComponent();
// 使用依赖注入的方式用视图填充命名区域
regionManager.RegisterViewWithRegion("DelegateCommandViewRegion", typeof(DelegateCommandView));
regionManager.RegisterViewWithRegion("CompositeCommandViewRegion", typeof(CompositeCommandView));
regionManager.RegisterViewWithRegion("ActiveAwareCommandViewRegion", typeof(ActiveAwareCommandView));
}
}
}
- 在
ViiewModels
文件夹中,创建MainWindowViewModel
类
using Prism.Mvvm;
namespace PrismCommandApp.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string title = "Prism Command Application";
public string Title
{
get => this.title;
set => this.SetProperty(ref this.title, value);
}
}
}
- 在
Views
创建名称为DelegateCommandView
的WPF用户自定义控件,修改DelegateCommandView.xaml
与DelegateCommandView.xaml.cs
分别如下:
<UserControl
x:Class="PrismCommandApp.Views.DelegateCommandView"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:PrismCommandApp.ViewModels"
d:DataContext="{d:DesignInstance Type=vm:DelegateCommandViewModel}"
mc:Ignorable="d">
<DockPanel Margin="3">
<Border
Background="LightGreen"
CornerRadius="5"
DockPanel.Dock="Top">
<TextBlock
Margin="0,3"
HorizontalAlignment="Center"
FontSize="16"
Foreground="White">
DelegateCommand
</TextBlock>
</Border>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<CheckBox
Margin="10"
Content="Can Execute Command"
IsChecked="{Binding IsEnabled}" />
<Button
Margin="10"
Command="{Binding ExecuteDelegateCommand}"
Content="DelegateCommand" />
<Button
Margin="10"
Command="{Binding GenericDelegateCommand}"
CommandParameter="{Binding CommandParameter}"
Content="DelegateCommand Generic" />
<Button
Margin="10"
Command="{Binding DelegateCommandObservesProperty}"
Content="DelegateCommand ObservesProperty" />
<Button
Margin="10"
Command="{Binding DelegateCommandObservesCanExecute}"
Content="DelegateCommand ObservesCanExecute" />
<!--#region Task 异步执行-->
<Button
Margin="10"
Command="{Binding AsyncMethodCommandOne}"
Content="DelegateCommand AsyncMethodOne" />
<Button
Margin="10"
Command="{Binding AsyncMethodCommandTwo}"
Content="DelegateCommand AsyncMethodTwo" />
<!--#endregion-->
<TextBlock
Margin="10"
FontSize="22"
Text="{Binding UpdateText}" />
</StackPanel>
</DockPanel>
</UserControl>
namespace PrismCommandApp.Views
{
public partial class DelegateCommandView
{
public DelegateCommandView()
{
this.InitializeComponent();
}
}
}
- 在
ViewModels
文件夹中,创建名称为DelegateCommandViewModel
类,内容如下:
using System;
using System.Threading;
using System.Threading.Tasks;
using Prism.Commands;
using Prism.Mvvm;
namespace PrismCommandApp.ViewModels
{
public class DelegateCommandViewModel : BindableBase
{
private bool isEnabled;
public bool IsEnabled
{
get => this.isEnabled;
set
{
this.SetProperty(ref this.isEnabled, value);
// 命令对象主动调用RaiseCanExecuteChanged()用以告知绑定的空间,命令状态已发生改变
this.ExecuteDelegateCommand.RaiseCanExecuteChanged();
this.GenericDelegateCommand.RaiseCanExecuteChanged();
}
}
private string updateText;
public string UpdateText
{
get => this.updateText;
set => this.SetProperty(ref this.updateText, value);
}
private string commandParameter = "DefaultParameter";
public string CommandParameter
{
get => this.commandParameter;
set => this.SetProperty(ref this.commandParameter, value);
}
public DelegateCommand ExecuteDelegateCommand { get; }
public DelegateCommand<string> GenericDelegateCommand { get; }
public DelegateCommand DelegateCommandObservesProperty { get; }
public DelegateCommand DelegateCommandObservesCanExecute { get; }
public DelegateCommand AsyncMethodCommandOne { get; }
public DelegateCommand AsyncMethodCommandTwo { get; }
public DelegateCommandViewModel()
{
// 命令的无参实现,在该实现中需要在命令可用状态发生改变时显示调用RaiseCanExecuteChanged()用以告知绑定的控件命令状态已发生改变
// 以便控件做出正确的状态显示
ExecuteDelegateCommand = new DelegateCommand(this.Execute, this.CanExecute);
// 该方式与命令的无参实现几乎相同,除了该命令可带有一个object类型的参数
GenericDelegateCommand = new DelegateCommand<string>(s => this.UpdateText = s, s => this.isEnabled);
// 该方式是Prism提供的一种封装,不需要显示调用RaiseCanExecuteChanged()方法,而是在【被观测的属性】发生变化时,自动调用
// 需要注意,RaiseCanExecuteChanged()方法【不会直接】对命令是否可执行做出判断,而是转嫁给了命令构造时指定的可执行判断委托
// 因此,在使用该方式时,依然需要在命令的构造函数中指定【可执行判断委托】
DelegateCommandObservesProperty = new DelegateCommand(this.Execute, this.CanExecute).ObservesProperty(() => this.IsEnabled);
// 该模式时上一种模式的简化。使用于【可执行委托】的内容是一个简单的Bool属性,则可以在命令的构造函数中省略【可执行委托】
DelegateCommandObservesCanExecute = new DelegateCommand(this.Execute).ObservesCanExecute(() => this.IsEnabled);
// Task异步支持
AsyncMethodCommandOne = new DelegateCommand(this.ExecuteCommandNameOne).ObservesCanExecute(() => this.IsEnabled);
AsyncMethodCommandTwo = new DelegateCommand(async () => await Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
this.Execute();
})).ObservesCanExecute(() => this.IsEnabled);
}
private void Execute()
{
this.UpdateText = $"Updated: {DateTime.Now}";
}
private bool CanExecute()
{
return this.IsEnabled;
}
private async void ExecuteCommandNameOne()
{
await Task.Factory.StartNew(() => { Thread.Sleep(3000); });
this.Execute();
}
}
}
- 运行程序,可以看到如下界面。开始时,所用按钮都处于不可执行状态,当复选框被选中后,按钮被变为可执行状态。点击按钮时,会更新信息。
关于本小节开始时介绍的
可执行状态
的几种使用方式,请参加代码中的注释。
2. CompisteCommand
在使用VS
等多页面可编辑的软件中,一般情况下都有一键全部保存
所有改动的功能。CompisteCommand
就是为了实现这种功能而设计的。
使用方式:
- 创建一个全局可以用的
CompisteCommand
命令对象 - 在每个需要关联的命令中,使用上一步创建的全局对象进行注册
2.1 样例
本节样例在上一节样例程序中继续开发。虽然是在一个项目中,但这两部分是独立的,可拆开。
这里使用依赖注入容器构建全局唯一的
CompisteCommand
对象。
- 项目根目录中创建名称为
ApplicationCommands
的类,修改其内容如下:
using Prism.Commands;
namespace PrismCommandApp
{
public interface IApplicationCommands
{
CompositeCommand SaveCommand { get; }
}
public class ApplicationCommands : IApplicationCommands
{
public CompositeCommand SaveCommand { get; } = new CompositeCommand();
}
}
- 在
Views
文件夹中创建名称为CompositeCommandView
的WPF用户自定义控件。CompositeCommandView.xaml
与CompositeCommandView.xaml.cs
的内容如下:
<UserControl
x:Class="PrismCommandApp.Views.CompositeCommandView"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:vm="clr-namespace:PrismCommandApp.ViewModels"
d:DataContext="{d:DesignInstance Type=vm:CompositeCommandViewModel}"
mc:Ignorable="d">
<UserControl.Resources>
<Style TargetType="TabItem">
<!-- ReSharper disable once Xaml.BindingWithContextNotResolved -->
<Setter Property="Header" Value="{Binding DataContext.Title}" />
</Style>
</UserControl.Resources>
<DockPanel Margin="3">
<Border
Background="LightGreen"
CornerRadius="5"
DockPanel.Dock="Top">
<TextBlock
Margin="0,3"
HorizontalAlignment="Center"
FontSize="16"
Foreground="White">
CompositeCommand
</TextBlock>
</Border>
<DockPanel>
<Button
Margin="10"
Command="{Binding ApplicationCommands.SaveCommand}"
Content="Save"
DockPanel.Dock="Top" />
<!-- 指定区域名称 -->
<TabControl Margin="10" prism:RegionManager.RegionName="ModuleForCompositeCommandViewRegion" />
</DockPanel>
</DockPanel>
</UserControl>
namespace PrismCommandApp.Views
{
public partial class CompositeCommandView
{
public CompositeCommandView()
{
this.InitializeComponent();
}
}
}
- 在
ViewModels
文件夹下创建名称为CompositeCommandViewModel
的类,内容如下:
using Prism.Mvvm;
namespace PrismCommandApp.ViewModels
{
public class CompositeCommandViewModel : BindableBase
{
private IApplicationCommands applicationCommands;
public CompositeCommandViewModel(IApplicationCommands applicationCommands)
{
this.ApplicationCommands = applicationCommands;
}
/// <summary>
/// 复合命令按钮绑定的命令
/// </summary>
public IApplicationCommands ApplicationCommands
{
get => this.applicationCommands;
set => this.SetProperty(ref this.applicationCommands, value);
}
}
}
页面的具体内容通过Prism Model
进行实现。
- 在
Views
文件夹中,创建名称为ModuleForCompositeCommandView
的WPF用户自定义控件,ModuleForCompositeCommandView.xaml
与ModuleForCompositeCommandView.xaml.cs
的内容分别如下:
<UserControl
x:Class="PrismCommandApp.Views.ModuleForCompositeCommandView"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:vm="clr-namespace:PrismCommandApp.ViewModels"
d:DataContext="{d:DesignInstance Type=vm:ModuleForCompositeCommandViewModel}"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Message}" />
<TextBlock
Margin="5"
FontSize="18"
Text="{Binding Title}" />
<CheckBox
Margin="5"
Content="Can Execute"
IsChecked="{Binding CanUpdate}" />
<Button
Margin="5"
Command="{Binding UpdateCommand}"
Content="Save" />
<TextBlock Margin="5" Text="{Binding UpdateText}" />
</StackPanel>
</UserControl>
namespace PrismCommandApp.Views
{
public partial class ModuleForCompositeCommandView
{
public ModuleForCompositeCommandView()
{
this.InitializeComponent();
}
}
}
- 在
ViewModels
文件夹中,创建名称为ModuleForCompositeCommandViewModel
的类,内容如下:
using System;
using Prism.Commands;
using Prism.Mvvm;
namespace PrismCommandApp.ViewModels
{
public class ModuleForCompositeCommandViewModel : BindableBase
{
private bool canUpdate = true;
private string message = "ModuleForCompositeCommand";
private string title;
private string updatedText;
public ModuleForCompositeCommandViewModel(IApplicationCommands applicationCommands)
{
this.UpdateCommand = new DelegateCommand(this.Update).ObservesCanExecute(() => this.CanUpdate);
// 将本地命令注册为复合命令
applicationCommands.SaveCommand.RegisterCommand(this.UpdateCommand);
}
public string Message
{
get => this.message;
set => this.SetProperty(ref this.message, value);
}
public string Title
{
get => this.title;
set => this.SetProperty(ref this.title, value);
}
public bool CanUpdate
{
get => this.canUpdate;
set => this.SetProperty(ref this.canUpdate, value);
}
public string UpdateText
{
get => this.updatedText;
set => this.SetProperty(ref this.updatedText, value);
}
public DelegateCommand UpdateCommand { get; }
private void Update()
{
this.UpdateText = $"Updated: {DateTime.Now}";
}
}
}
- 在项目根目录下,创建名称为
ModuleForCompositeCommandModule
的类,内容如下:
using System.Windows;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
using PrismCommandApp.ViewModels;
using PrismCommandApp.Views;
namespace PrismCommandApp
{
public class ModuleForCompositeCommandModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
var region = regionManager.Regions["ModuleForCompositeCommandViewRegion"];
var tabA = containerProvider.Resolve<ModuleForCompositeCommandView>();
SetTitle(tabA, "Tab A");
region.Add(tabA);
var tabB = containerProvider.Resolve<ModuleForCompositeCommandView>();
SetTitle(tabB, "Tab B");
region.Add(tabB);
var tabC = containerProvider.Resolve<ModuleForCompositeCommandView>();
SetTitle(tabC, "Tab C");
region.Add(tabC);
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
private static void SetTitle(FrameworkElement tab, string title)
{
((ModuleForCompositeCommandViewModel) tab.DataContext).Title = title;
}
}
}
- 修改
App.xaml.cs
的内容如下:
using System.Windows;
using Prism.Ioc;
using Prism.Modularity;
using PrismCommandApp.Views;
namespace PrismCommandApp
{
public partial class App
{
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>();
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ModuleForCompositeCommandModule>();
}
protected override Window CreateShell()
{
return this.Container.Resolve<MainWindowView>();
}
}
}
- 运行程序,显示如下界面。初始情况下,命令相应消息都为空,单独点击每个页面的内容时,更新各自的消息,点击顶部按钮是,更新所有也页面的消息。
3. 带IsActive的CompoisiteCommand
有时,我们希望点击CompoisiteCommand
命令按钮时,仅处于激活状态
的页面被更新,非激活页面不参与功能相应。例如,在上例中,如果Tab A
页面处于显示状态,Tab B
与Tab C
处于非显示状态。此时点击顶部按钮,则仅更新Tab A
中的内容。
实现方式与基本的CompositeCommand
有两点主要的不同:
- 在构建全局唯一的
CompositeCommand
命令时,传入一个true
参数 - 实现模块的绑定数据类实现
IActiveAware
接口
具体实现详见源代码程序