Prism框架提供了DelegateCommand
类型,专门用于进行WPF中的行为处理。
DelegateCommand(Action executeMethod)
:DelegateCommand
的构造函数,创建DelegateCommand
对象。
定义命令
public class MainViewModel
{
public ICommand BtnCommand => new DelegateCommand(Execute);
private void Execute()
{
//做命令执行业务
}
}
xaml中绑定命令
<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提供了三种命令状态检查方式
可以向DelegateCommand
的构造函数中传入状态检查的委托函数。然后在合适的节点去调用DelegateCommand
对象的RaiseCanExecuteChanged
方法来执行状态检查函数。
DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
:DelegateCommand
的构造函数,创建DelegateCommand
对象。
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);
}
}
DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
:DelegateCommand
的构造函数,创建DelegateCommand
对象。
DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression)
:DelegateCommand
对象的方法,用于监听指定属性的变化,一旦属性发生变化,就调用构造时传入的状态检查函数。
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种方式要更加方便,不用考虑在哪个地方去主动调用命令状态检查函数。
DelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
:DelegateCommand
的实例方法,根据传入的Lambda表达式,来检查命令的可执行状态。
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;
}
默认的命令触发事件是鼠标左键单击,如果希望通过其他事件来触发指定的命令,除了需要用到Prism的控件外,还需要借助Behaviors
库的控件。
Nuget中安装Behaviors库
PS:Prism框架中自带了这个包,因此在Prism框架下可以直接使用。
定义命令
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容器的依赖注入来实现。
①、创建复合命令类型
创建复合命令接口与实现子类,内含CompositeCommand
对象属性。(这里是在程序集中创建Base文件夹,在Base中创建对应的IUnionCommand和UnionCommands)
接口
public interface IUnionCommand
{
CompositeCommand Commands { get; }
}
实现子类
需要注意,这里CompositeCommand
的创建必须放在字段上进行,如果放在属性中,每次get
再创建,有可能每次从IOC中获取的都是一个新的实例。
public class UnionCommands : IUnionCommands
{
private CompositeCommand _doCommand = new CompositeCommand();
public CompositeCommand DoCommands
{
get => _doCommand;
}
}
②、注册复合命令类型
在App后台代码的RegisterTypes
函数中,注册类型,然后通过IOC依赖注入从而在整个项目中使用同一个CompositeCommand
对象。
注意,必须注册为单例的。
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<Views.MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//必须设置为单例,保证全局中都是同一个对象
containerRegistry.RegisterSingleton<Base.IUnionCommand, Base.UnionCommand>();
}
}
③、ViewModel层
这里分别在ViewModels文件夹中创建了MainWindowViewModel、ViewAViewModel和ViewBViewModel
MainWindowViewModel
public class MainWindowViewModel:BindableBase
{
[Dependency]
public IRegionManager regionManager { get; set; }
[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");
});
}
}
ViewAViewModel
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);
}
}
ViewBViewModel
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);
}
}
④、View层
这里在Views文件夹中放置了MainWindow,新建了ViewA和ViewB用户控件。
MainWindow
<Window ......
xmlns:prism="http://prismlibrary.com/">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="150"/>
<RowDefinition/>
</Grid.RowDefinitions>
<UniformGrid Rows="2">
<Button Content="UnionCommand" Command="{Binding UnionCommand.Commands}"/>
<UniformGrid Columns="2">
<Button Content="ViewA" Command="{Binding ShowViewACommand}"/>
<Button Content="ViewB" Command="{Binding ShowViewBCommand}"/>
</UniformGrid>
</UniformGrid>
<UniformGrid Grid.Row="1" Rows="1">
<ContentControl prism:RegionManager.RegionName="ViewA"/>
<ContentControl prism:RegionManager.RegionName="ViewB"/>
</UniformGrid>
</Grid>
</Window>
ViewA
<UserControl ......>
<Grid>
<StackPanel VerticalAlignment="Center">
<TextBox Text="{Binding ValueA}"/>
<Button Content="ShouViewA" Command="{Binding ValueACommand}"/>
</StackPanel>
</Grid>
</UserControl>
ViewB
<UserControl ......>
<Grid>
<StackPanel VerticalAlignment="Center">
<TextBox Text="{Binding ValueB}"/>
<Button Content="ShouViewB" Command="{Binding ValueBCommand}"/>
</StackPanel>
</Grid>
</UserControl>
⑤、注册Region视图对象
public partial class App : PrismApplication
{
......
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<Views.ViewA>();
containerRegistry.RegisterForNavigation<Views.ViewB>();
//必须设置为单例,保证全局中都是同一个对象
containerRegistry.RegisterSingleton<Base.IUnionCommand, Base.UnionCommand>();
}
}