PrismMVVM功能实现(通知、命令)
常见的MVVM框架,基本围绕ICommand、INotifyPropertyChanged的封装实现绑定、通知等功能;而对于不同框架,在实现相同功能上,只是表现的形式有所不同,下图列举几种常见框架的功能区别:
功能\框架 | Prism | MVVMLight | Micorsoft.Tookit.Mvvm |
通知 | BindableBase | ViewModelBase | ObservableObject |
命令 | DelegateCommand | RelayCommand | Async/RelayCommand |
事件聚合器 | IEventAggregator | IMessenger | IMessenger |
模块化 | √ | × | × |
容器 | √ | × | × |
依赖注入 | √ | × | × |
导航 | √ | × | × |
对话服务 | √ | × | × |
通知(BindableBase)
BindableBase 主要实现 INotifyPropertyChanged 接口,用于监听属性更改时提供通知,实现方式为在属性 SET 中添加 RaisePropertyChanged() 方法,具体过程如下:
基础的通知属性的三种方式
// 基础的通知属性
private string _value;
public string Value
{
get { return _value; }
set
{
// 第一种方式
SetProperty<string>(ref _value, value);
// 第二种方式
_value = value;
this.RaisePropertyChanged(nameof(Value));
// 第三种方式
_value = value;
this.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(nameof(Value)));
}
}
运行效果如下:
单一命令(DelegateCommand)
ViewModel 可以通过将命令接口实现为命令对象 (实现 ICommand 接口的对象)。视图与命令的交互以声明的方式定义,而无需在视图的代码隐藏文件中使用复杂的事件处理代码。实现 ICommand 接口很简单,Prism 提供了 DelegateCommand 此接口的实现,您可以在应用程序中轻松使用。
创建委托命令
Prism DelegateCommand 类封装了两个委托,每个委托都引用了在 ViewModel 类中实现的方法。它通过调用这些委托来实现 ICommand 接口Execute 和方法。 CanExecute 您在 DelegateCommand 类构造函数中指定 ViewModel 方法的委托。例如,以下代码示例显示了如何DelegateCommand 通过指定 OnSubmit 和 CanSubmit ViewModel 方法的委托来构造表示提交命令的实例。然后,该命令通过一个只读属性暴露给视图,该属性返回对 DelegateCommand .
//无参、仅Excute构造方式
public class ArticleViewModel
{
public DelegateCommand SubmitCommand { get; private set; }
public ArticleViewModel()
{
SubmitCommand = new DelegateCommand(Submit);
}
void Submit()
{
//执行方法
}
}
//有参、Execute和CanExecut同时构造方式
public class ArticleViewModel
{
public DelegateCommand SubmitCommand { get; private set; }
public ArticleViewModel()
{
SubmitCommand = new DelegateCommand<object>(Submit, CanSubmit);
}
void Submit(object parameter)
{
//执行方法
}
bool CanSubmit(object parameter)
{
return true;
}
}
//无参、Execute和CanExecut同时构造方式
public class ArticleViewModel
{
public DelegateCommand SubmitCommand { get; private set; }
public ArticleViewModel()
{
SubmitCommand = new DelegateCommand(Submit, CanSubmit);
}
void Submit()
{
//implement logic
}
bool CanSubmit()
{
return true;
}
}
从视图调用命令
使用 CommandParameter 属性定义命令参数。定义的参数类型在 DelegateCommand 泛型声明中指定。当用户与该控件交互时,该控件将自 动调用目标命令,并且命令参数(如果提供)将作为参数传递给命令的 Execute 方法。
<Button Command="{Binding SubmitCommand}" CommandParameter="OrderId"/>
事件转命令
侦听Grid的MouseLeftButtonDown事件,并使用Command绑定
<Window x:Class="Zhaoxi.MvvmBaseObject.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Zhaoxi.MvvmBaseObject"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:p="http://prismlibrary.com/"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Background="Transparent">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<p:InvokeCommandAction Command="{Binding ButtonCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</Window>
public DelegateCommand<object> ButtonCommand { get; set; }
public MainViewModel()
{
ButtonCommand = new DelegateCommand<object>(arg =>
{
});
}
不设置p:InvokeCommandAction的TriggerParameterPath属性,传递参数是{System.Windows.Input.MouseButtonEventArgs}
设置TriggerParameterPath参数会传递所设置的参数,或者传递{System.Windows.Input.MouseButtonEventArgs}中具体的属性比如ButtonState
<Grid Background="Transparent">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<p:InvokeCommandAction Command="{Binding ButtonCommand}" TriggerParameterPath="ButtonState"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
使用WPF原生的InvokeCommandAction默认不传递参数,需要设置PassEventArgsToCommand="True"可以传递{System.Windows.Input.MouseButtonEventArgs}
<Grid Background="Transparent">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding ButtonCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
创建异步委托命令
在当今的 async / await 使用中,在委托中调用异步方法 Execute 是非常普遍的要求。每个人的第一直觉是他们需要一个 AsyncCommand ,但这种假设是错误的。 ICommand 本质上是同步的, Execute 并且 CanExecute 委托应该被视为事件。这意味着这 async void 是用于命令的完全有效的语法。有两种方法可以将异步方法与 DelegateCommand 结合使用:
方式1
public class ArticleViewModel
{
public DelegateCommand SubmitCommand { get; private set; }
public ArticleViewModel()
{
SubmitCommand = new DelegateCommand(Submit);
}
async void Submit()
{
await SomeAsyncMethod();
}
}
方式2
public class ArticleViewModel
{
public DelegateCommand SubmitCommand { get; private set; }
public ArticleViewModel()
{
SubmitCommand = new DelegateCommand(async ()=> await Submit());
}
Task Submit()
{
return SomeAsyncMethod();
}
}
复合命令(CompositeCommand)
在许多情况下,视图模型定义的命令将绑定到关联视图中的控件,以便用户可以直接从视图中调用该命令。但是,在某些情况下,您可能希望能够从应用程序 UI 的父视图中的控件调用一个或多个视图模型上的命令。
Prism 通过 CompositeCommand 类支持这种情况 。
该类 CompositeCommand 表示由多个子命令组成的命令。当调用复合命令时,依次调用它的每个子命令。在您需要将一组命令表示为 UI 中的单个命令或您希望调用多个命令来实现逻辑命令的情况下,它很有用。
该类 CompositeCommand 维护一个子命令( DelegateCommand 实例)列表。类的 Execute 方法 CompositeCommand 简单地依次调用Execute 每个子命令的方法。该 CanExecute 方法类似地调用 CanExecute 每个子命令的方法,但如果任何子命令无法执行,该 CanExecute 方法将返回 false 。也就是说,默认情况下,a CompositeCommand 只有在所有子命令都可以执行时才能执行。
创建复合命令
复合命令绑定到视图
在视图中,将“CompositeButton”按钮与到 TestCompositeCommand 命令绑定。
运行结果如下:
不同页面绑定同一复合命令
跨页面绑定需要全局单例复合命令,可以注册单例复合命令,在注入到各模块中
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);
}
通过依赖注入、静态类来实现 CompositeCommand 全局可用/绑定等用法,请查阅Prism官方文档。
来源:https://www.cnblogs.com/ZHIZRL/p/17675034.html