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官方文档。

 

posted @ 2023-09-03 15:21  ZHIZRL  阅读(674)  评论(0编辑  收藏  举报