Prism 行为处理
Prism框架提供了DelegateCommand类型,专门用于进行WPF中的行为处理。
基本使用
一、命令的使用
DelegateCommand(Action executeMethod):DelegateCommand的构造函数,创建DelegateCommand对象。
executeMethod:无参的命令执行函数。
定义命令
public class MainViewModel { public ICommand BtnCommand => new DelegateCommand(Execute); private void Execute() { //做命令执行业务 } }
<Window ......> <Window.DataContext> <local:MainViewModel/> </Window.DataContext> <Grid> <StackPanel> <Button Content="BtnCommand" Command="{Binding BtnCommand}"/> </StackPanel> </Grid> </Window>
带参数的命令
Prism框架中,命令的使用跟MVVMToolkit框架是类似的,命令的执行函数可以有参,也可以无参,具体用法如下:
public ICommand BtnCommand => new DelegateCommand<string>(Execute); private async void Execute(string str) { await Task.Delay(2000); Value = 100; }
二、命令的状态检查
Prism提供了三种命令状态检查方式
1、方式1:调用RaiseCanExecuteChanged方法
可以向DelegateCommand的构造函数中传入状态检查的委托函数。然后在合适的节点去调用DelegateCommand对象的RaiseCanExecuteChanged方法来执行状态检查函数。
DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod):DelegateCommand的构造函数,创建DelegateCommand对象。
executeMethod:无参的命令执行函数。
canExecuteMethod:命令的状态检查函数。
RaiseCanExecuteChanged():调用DelegateCommand对象的命令状态检查函数(构造时传入的函数)。
定义命令
需要注意以下两点:
RaiseCanExecuteChanged():此方法是DelegateCommand对象的方法,ICommand接口中没有,为了后续方便,创建时直接使用DelegateCommand而不是ICommand类型。
DelegateCommand对象的初始化(new)必须放在所在类型的构造函数中进行,否则调用RaiseCanExecuteChanged函数不起作用。
public class MainViewModel { private int _value = 10; public int Value { get { return _value; } set { _value = value; //调用命令状态检查函数 BtnCheckCommand.RaiseCanExecuteChanged(); } } //声明属性,但不做初始化,留在构造函数中做 public DelegateCommand BtnCheckCommand { get; } private bool CheckCanExecute() { return Value == 100; } private void Execute() { //做命令执行业务 } public MainViewModel() { //继续命令初始化,只能放在这里做,放在属性中进行初始化的话,调用状态检查函数无效果。 BtnCheckCommand = new DelegateCommand(Execute, CheckCanExecute); } }
2、方式2:使用ObservesProperty监听属性变化
DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod):DelegateCommand的构造函数,创建DelegateCommand对象。
executeMethod:无参的命令执行函数。
canExecuteMethod:命令的状态检查函数。
DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression):DelegateCommand对象的方法,用于监听指定属性的变化,一旦属性发生变化,就调用构造时传入的状态检查函数。
propertyExpression:一个返回指定监控属性的无参Lambda表达式。
由于调用后就返回监听该属性的DelegateCommand对象,因此可以通过链式调用监听多个属性的变化。
public class MainWindowViewModel : BindableBase { private int _value1; public int Value1 { get { return _value1; } set { SetProperty(ref _value1, value); } } private int _value2; public int Value2 { get { return _value2; } set { SetProperty(ref _value2, value); } } private void Execute() { //做命令执行业务 } public DelegateCommand BtnCheckCommand { get; } private bool CheckCanExecute() { return Value1 + Value2 == 100; } public MainWindowViewModel() { BtnCheckCommand = new DelegateCommand(Execute, CheckCanExecute) .ObservesProperty(() => Value1) .ObservesProperty(() => Value2);//通过链式结构可以同时监听多个属性,进行组合判断 } }
这种方式比第1种方式要更加方便,不用考虑在哪个地方去主动调用命令状态检查函数。
3、方式3:使用ObservesProperty
DelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecuteExpression):DelegateCommand的实例方法,根据传入的Lambda表达式,来检查命令的可执行状态。
canExecuteExpression:一个返回bool类型属性的表达式。
注意
传递给ObservesCanExecute的Lambda表达式中,需要返回bool类型的属性而不是直接的bool值,否则无效。
传递给ObservesCanExecute的Lambda表达式中返回的属性,有两点需要注意:
必须做属性变化通知处理。
在第二次属性变化通知起,才能正确完成命令可执行状态检查,也就是第一次变化通知不会正常完成命令状态检查,需要提前做一下第一次的属性变化。
此属性发生变化时才会触发命令可执行状态检查,因此要在合适的时候去改变属性来触发检查。
命令定义
public class MainViewModel: BindableBase { private int _value = 10; public int Value { get { return _value; } set { _value = value; //在合适的时机去改变Status属性,触发变化通知,从而调用状态检查 Status = !Status; } } public bool status = false; public bool Status { get { return Value == 100; } set { //一定要做属性变化通知,以此来触发状态检查 SetProperty(ref status, value); } } private void Execute() { //做命令执行业务 } public DelegateCommand BtnCheckCommand { get; } public MainViewModel() { //与前两中方式相比,可以不需要传入命令状态检查函数 BtnCheckCommand = new DelegateCommand(Execute).ObservesCanExecute(()=> Status); Status = !Status;//要先触发一次Status的属性变化通知,在第二次属性变化通知时才能顺利的检查命令的状态 } }
三、异步处理
Prism的异步处理没什么新鲜的,就是正常的使用异步函数,具体如下
public ICommand BtnCommand { get => new DelegateCommand(Execute); } private async void Execute() { await Task.Delay(2000); Value = 100; }
一、事件触发命令
定义命令
public class MainViewModel: BindableBase { private int value = 10; public int Value { get { return value; } set { SetProperty(ref this.value, value); } } public ICommand BtnCommand { get => new DelegateCommand<object>(Execute); } private void Execute(object obj) { Value = 100; } }
xaml中绑定命令
注意,这里因为使用了Prism框架的命令,因此在绑定命令时使用了prism:InvokeCommandAction
。如果没有使用Prism框架,可以使用i:InvokeCommandAction
,也就是Behaviors包自带的。
<Window ...... xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:prism="http://prismlibrary.com/" ......> <Window.DataContext> <local:MainViewModel/> </Window.DataContext> <Grid> <StackPanel> <TextBlock Text="{Binding Value}"/> <ComboBox SelectedIndex="0"> <i:Interaction.Triggers> <i:EventTrigger EventName="SelectionChanged"> <prism:InvokeCommandAction Command="{Binding BtnCommand}"/> </i:EventTrigger> </i:Interaction.Triggers> <ComboBoxItem Content="item1"/> <ComboBoxItem Content="item2"/> <ComboBoxItem Content="item3"/> <ComboBoxItem Content="item4"/> </ComboBox> </StackPanel> </Grid> </Window>
注意
默认情况下,上述做法会直接将EventArgs对象作为参数传递给命令执行函数,如果需要进行参数的指定,可以通过prism:InvokeCommandAction元素的TriggerParameterPath属性来将EventArgs中的某个属性成员作为参数传入,或者使用CommandParameter属性,将其他对象作为参数传递给命令执行函数。如果同时使用TriggerParameterPath和CommandParameter属性,则优先传递CommandParameter属性设置的参数。
例如:<prism:InvokeCommandAction Command="{Binding BtnCommand}" TriggerParameterPath="Handled"/>就是将EventArgs对象中的Handled属性作为参数传递给命令执行函数。
二、事件触发方法
在Behaviores包的帮助下,也可以直接让事件来触发指定的方法。
定义执行方法
public class MainWindowViewModel { public void TextBox_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { //输入键 } } }
注意,事件处理方法会接收到几个默认的参数,如果不知道是什么参数对象可以通过在控件中自动生成事件方法来参考一下。在本例中可以通过<TextBox …… KeyDown="TextBox_KeyDown">,自动生成TextBox_KeyDown方法来查看接收什么参数。
XAML中使用
注意,这里使用的是b:CallMethodAction,是Behaviors包中提供的对象。
TargetObject:指定事件处理方法所在的对象。
MethodName:事件处理方法的名称。
<Window ...... xmlns:b="http://schemas.microsoft.com/xaml/behaviors"> <Grid> <TextBox Height="30" Width="100"> <b:Interaction.Triggers> <b:EventTrigger EventName="KeyDown"> <b:CallMethodAction TargetObject="{Binding}" MethodName="TextBox_KeyDown"/> </b:EventTrigger> </b:Interaction.Triggers> </TextBox> </Grid> </Window>
复合命令
复合命令是指一个命令可以同时完成多个命令,Prism提供了CompositeCommand类型专门用于实现复合命令。
注册命令
RegisterCommand(ICommand command):CompositeCommand的实例方法,用于注册命令,也就是将指定命令添加到当前复合命令对象中。
UnregisterCommand(ICommand command):CompositeCommand的实例方法,用于取消注册指定命令,即从当前复合命令对象中移除指定命令。
同一个模块(例如要复合的命令全部都在MainWindowViewModel中)的复合命令是很容易实现的,即使不使用CompositeCommand也可以实现。然而在实际开发中,要用到复合命令的情景往往是需要将多个不同模块中的命令集中起来一次性全部调用。这个过程不仅需要使用到CompositeCommand类,还需要借助IOC容器的依赖注入来实现。
<Window x:Class="WpfApp1.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:WpfApp1" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:prism="http://prismlibrary.com/" Title="MainWindow" Width="800" Height="450" prism:ViewModelLocator.AutoWireViewModel="True" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="150" /> <RowDefinition /> </Grid.RowDefinitions> <UniformGrid Rows="2"> <Button Command="{Binding UnionCommand.Commands}" Content="UnionCommand" /> <UniformGrid Columns="2"> <Button Command="{Binding ShowViewACommand}" Content="ViewA" /> <Button Command="{Binding ShowViewBCommand}" Content="ViewB" /> </UniformGrid> </UniformGrid> <UniformGrid Grid.Row="1" Rows="1"> <ContentControl prism:RegionManager.RegionName="ViewA" /> <ContentControl prism:RegionManager.RegionName="ViewB" /> </UniformGrid> </Grid> </Window>
public class MainWindowViewModel : BindableBase { [Dependency] public IRegionManager regionManager { get; set; } public MainWindowViewModel(IRegionManager regionManager, IUnionCommand UnionCommand) { this.regionManager = regionManager; this.UnionCommand = UnionCommand; } [Dependency] public IUnionCommand UnionCommand { get; set; } public ICommand ShowViewACommand { get => new DelegateCommand(() => { regionManager.RequestNavigate("ViewA", "ViewA"); }); } public ICommand ShowViewBCommand { get => new DelegateCommand(() => { regionManager.RequestNavigate("ViewB", "ViewB"); }); } }
<UserControl x:Class="WpfApp1.Views.ViewA" 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:WpfApp1.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> <Grid> <StackPanel VerticalAlignment="Center"> <TextBox Text="{Binding ValueA}" /> <Button Command="{Binding ValueACommand}" Content="ShouViewA" /> </StackPanel> </Grid> </UserControl>
<UserControl x:Class="WpfApp1.Views.ViewB" 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:WpfApp1.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> <Grid> <StackPanel VerticalAlignment="Center"> <TextBox Text="{Binding ValueB}" /> <Button Command="{Binding ValueBCommand}" Content="ShouViewB" /> </StackPanel> </Grid> </UserControl>
public class ViewAViewModel : BindableBase { private int _valueA = 0; public int ValueA { get { return _valueA; } set { SetProperty(ref _valueA, value); } } public ICommand ValueACommand { get; set; } public ViewAViewModel(IUnionCommand unionCommand) { ValueACommand = new DelegateCommand(() => { ValueA = 100; }); //注册命令 unionCommand.Commands.RegisterCommand(ValueACommand); } }
public class ViewBViewModel : BindableBase { private int _valueB = 0; public int ValueB { get { return _valueB; } set { SetProperty(ref _valueB, value); } } public ICommand ValueBCommand { get; set; } public ViewBViewModel(IUnionCommand unionCommand) { ValueBCommand = new DelegateCommand(() => { ValueB = 200; }); //注册命令 unionCommand.Commands.RegisterCommand(ValueBCommand); } }
public class Startup : PrismBootstrapper { protected override DependencyObject CreateShell() { return Container.Resolve<MainWindow>(); //return Container.Resolve<NavigationLogWindow>(); //return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation<ViewA>(); containerRegistry.RegisterForNavigation<ViewB>(); containerRegistry.RegisterSingleton<IUnionCommand, UnionCommand>(); //containerRegistry.RegisterForNavigation<ViewD>(); //containerRegistry.RegisterForNavigation<ViewE>(); } protected override void ConfigureViewModelLocator() { base.ConfigureViewModelLocator(); ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(MainWindowViewModel)); ViewModelLocationProvider.Register(typeof(NavigationWindow).ToString(), typeof(NavigationWindowVewModel)); ViewModelLocationProvider.Register(typeof(NavigationLogWindow).ToString(), typeof(NavigationLogViewModel)); } }
来源:https://blog.csdn.net/jjailsa/article/details/135732650
不同页面绑定同一复合命令
跨页面绑定需要全局单例复合命令,可以注册单例复合命令,在注入到各模块中
MainWindow
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="显示页面" Command="{Binding ShowPageCommand}"/>
<Button Content="全部保存" Command="{Binding AllSaveCommand}"/>
</StackPanel>
<UniformGrid Columns="2" Grid.Row="1">
<ContentControl p:RegionManager.RegionName="Region1"/>
<ContentControl p:RegionManager.RegionName="Region2"/>
</UniformGrid>
</Grid>
ViewA
<Grid>
<Button Content="A保存" Command="{Binding SaveCommand}"/>
</Grid>
ViewB
<Grid>
<Button Content="B保存" Command="{Binding SaveCommand}"/>
</Grid>
注册页面和单例复合命令
public class Startup : PrismBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<PrismRegion.Composite.MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<PrismRegion.Composite.ViewA>();
containerRegistry.RegisterForNavigation<PrismRegion.Composite.ViewB>();
//注册单例命令
containerRegistry.RegisterSingleton<CompositeCommand>();
}
}
MainWindowViewModel
注入CompositeCommand拿到全局复合命令,并关联实例AllSaveCommand
public class MainWindowViewModel
{
public CompositeCommand AllSaveCommand { get; set; }
public DelegateCommand ShowPageCommand { get; set; }
public MainWindowViewModel(IRegionManager regionManager,
CompositeCommand composite)
{
ShowPageCommand = new DelegateCommand(() =>
{
regionManager.RequestNavigate("Region1", "ViewA");
regionManager.RequestNavigate("Region2", "ViewB");
});
AllSaveCommand = composite;
//AllSaveCommand.RegisterCommand(ShowPageCommand);
}
}
ViewAViewModel
注入CompositeCommand拿到全局复合命令,将SaveCommand加入到复合命令
public class ViewAViewModel
{
public DelegateCommand SaveCommand { get; set; }
public ViewAViewModel(CompositeCommand composite)
{
SaveCommand = new DelegateCommand(() =>
{
});
composite.RegisterCommand(SaveCommand);
}
}
ViewBViewModel
注入CompositeCommand拿到全局复合命令,将SaveCommand加入到复合命令
public class ViewBViewModel
{
public DelegateCommand SaveCommand { get; set; }
public ViewBViewModel(CompositeCommand composite)
{
SaveCommand = new DelegateCommand(() =>
{
});
// 这里有前后关系,很重要
composite.RegisterCommand(SaveCommand);
//composite.UnregisterCommand(SaveCommand);
}
}
运行后按钮先为黑色,因为还没ViewA和ViewB没有初始化,复合命令为空
点击显示页面后,ViewA和ViewB的命令加入到复合命令后按钮可以点击
取消注册命令
如前面的示例所示,子命令是使用该 TestCompositeCommand.RegisterCommand 方法注册的。但是,当您不再希望响应 TestCompositeCommand或者您正在销毁 View/ViewModel 以进行垃圾回收时,您应该使用该 TestCompositeCommand.UnregisterCommand 方法取消注册子命令。
/// 取消命令注册例子
public void Destroy()
{
TestCompositeCommand.UnregisterCommand(TestCommand1);
}