11-13 Commands Prism官网案例学习

声明

原文出处如下:

作者:RyzenAdorer 内容简介:Prism医疗案例

作者:可是我爱你啊 内容简介:Prism官方案例的学习分享

Prism官网开发文档

容器相关知识的学习

Microsoft官方文档

这是一篇记录作者学习Prism的随笔,该随笔的内容是作者通过5个资源学习汇总而成,主要是为了方便自己以后拾遗温习所用,如果文中内容看不懂,推荐直接阅读相关原文。

11-13 Commands

DelegateCommand委托命令

复习案例

Xaml

<Button Content="{Binding Click}" Command="{Binding ClickCommand}" CommandParameter="{Binding CheckName}" Margin="5" Background="LightSalmon" FontSize="28"/>

CS

private DelegateCommand<string> _clickCommand;
        public DelegateCommand<string> ClickCommand =>
            _clickCommand ?? (_clickCommand = new DelegateCommand<string>(ExecuteClickCommand).ObservesCanExecute(() => IsCheck));

        void ExecuteClickCommand(string parameter)
        {        
            this.Time =parameter+ DateTime.Now.ToString();
        }

        public MainWindowViewModel()
        {
            this.CheckName = "请选择是否开始";
            this.Click = "点我点我点我";
        }

基础

  1. 命令属性 继承自ICommand接口,并且有三个函数成员
    1. CanExecuteChanged,事件CanExecuteChanged
    2. CanExecute,一个返回值bool的,且带一个参数为object的CanExecute方法
    3. Execute,一个无返回值且带一个参数为object的Execute方法
  2. RaiseCanExecuteChanged方法就是内部调用ICommand接口下的CanExecuteChanged事件去调用CanExecute方法

Prism中的委托命令

  1. 引入名称空间using Prism.Commands,我们所定义的DelegateCommand类型就在该命名空间下

  2. 语法糖

    1. cmd-使用Execute方法创建DelegateCommand属性
    2. cmdfull-使用Execute和CanExecute方法创建DelegateCommand属性
    3. cmdg-创建通用的DelegateCommand 属性
    4. cmdgfull-创建通用的DelegateCommand 具有Execute和CanExecute方法的属性
  3. 实现简述

    1. 普通实现

      using Prism.Commands;
      using Prism.Mvvm;
      using System;
      using System.Windows.Controls;
      
      namespace CommandSample.ViewModels
      {
         public class MainWindowViewModel: BindableBase
          {
              private bool _isCanExcute;
              public bool IsCanExcute
              {
                  get { return _isCanExcute; }
                  set 
                  { 
                      SetProperty(ref _isCanExcute, value);
                      //
                      GetCurrentTimeCommand.RaiseCanExecuteChanged();
                  }
              }
      
              private string _currentTime;
              public string CurrentTime
              {
                  get { return _currentTime; }
                  set { SetProperty(ref _currentTime, value); }
              }
      
              private DelegateCommand _getCurrentTimeCommand;
              public DelegateCommand GetCurrentTimeCommand =>
                  _getCurrentTimeCommand ?? (_getCurrentTimeCommand = new DelegateCommand(ExecuteGetCurrentTimeCommand, CanExecuteGetCurrentTimeCommand));
      
              void ExecuteGetCurrentTimeCommand()
              {
                  this.CurrentTime = DateTime.Now.ToString();
              }
      
              bool CanExecuteGetCurrentTimeCommand()
              {
                  return IsCanExcute;
              }
          }
      }
      
      

    在代码中,我们通过using Prism.Mvvm引入继承BindableBase,因为我们要用到属性改变通知方法SetProperty,这在我们上一篇就知道了,再来我们using Prism.Commands,我们所定义的DelegateCommand类型就在该命名空间下,我们知道,ICommand接口是有三个函数成员的,事件CanExecuteChanged,一个返回值bool的,且带一个参数为object的CanExecute方法,一个无返回值且带一个参数为object的Execute方法,很明显我们实现的GetCurrentTimeCommand命令就是一个不带参数的命令

    还有一个值得注意的是,我们通过Checkbox的IsChecked绑定了一个bool属性IsCanExcute,且在CanExecute方法中return IsCanExcute,我们都知道CanExecute控制着Execute方法的是否能够执行,也控制着Button的IsEnable状态,而在IsCanExcute的set方法我们增加了一句:

    GetCurrentTimeCommand.RaiseCanExecuteChanged();
    

    其实通过prism源码我们可以知道RaiseCanExecuteChanged方法就是内部调用ICommand接口下的CanExecuteChanged事件去调用CanExecute方法

    其实上述prism还提供了一个更简洁优雅的写法:

    [复制代码](javascript:void(0)😉

     private bool _isCanExcute;
     public bool IsCanExcute
     {
        get { return _isCanExcute; }
        set { SetProperty(ref _isCanExcute, value);}
     }
    
     private DelegateCommand _getCurrentTimeCommand;
     public DelegateCommand GetCurrentTimeCommand =>
        _getCurrentTimeCommand ?? (_getCurrentTimeCommand = new  DelegateCommand(ExecuteGetCurrentTimeCommand).ObservesCanExecute(()=> IsCanExcute));
    
     void ExecuteGetCurrentTimeCommand()
     {
        this.CurrentTime = DateTime.Now.ToString();
     }
    

    [复制代码](javascript:void(0)😉

    其中用了ObservesCanExecute方法,其实在该方法内部中也是会去调用RaiseCanExecuteChanged方法

    我们通过上面代码我们可以会引出两个问题:

    • 如何创建带参数的DelegateCommand?
    • 假如控件不包含依赖属性Command,我们要用到该控件的事件,如何转为命令?
  4. 具体实现

    SetProperty(ref _isEnabled, value);
    

    属性变更的通知,当视图状态更新后,会通知VM更改_isEnabled

    ExecuteDelegateCommand.RaiseCanExecuteChanged();
    

    这段代码,则会通知ExecuteDelegateCommand的可执行状态更改了,让他重新获取下可执行状态,那他是怎么获取可执行状态的呢?我们看下这个Command:

    ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
    

    *new 的时候,有两个参数,第一个是Action(无返回类型的方法)Execute(需要执行的方法),第二个是一个Func,就是一个返回布尔型的方法CanExecute来获取command的可执行状态,当上面通知他可执行状态变更后,他就会重新调用CanExecute方法来获取目前的可执行状态(也就是按钮的可按下状态),来看下这个方法:

    private bool CanExecute()
    {
         return IsEnabled;
    }
    

    很简单,直接返回了IsEnabled,而他是跟视图的CheckBox的IsChecked绑定的,当然也可以返回_isEnabled,而我更倾向后面这个,Public那个是给外人用的,蛤蛤。

    当然可执行状态,还有其他的更优雅的写法,也就不用写ExecuteDelegateCommand.RaiseCanExecuteChanged();了,具体代码如下:

                DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute)
                    .ObservesProperty(() => IsEnabled);
    
                DelegateCommandObservesCanExecute = new DelegateCommand(Execute)
                    .ObservesCanExecute(() => IsEnabled);
    

    下面这个是带参数的命令(command),他的回调函数需要一个string类型的参数,在new的时候要指定入参类型:

                ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric)
                    .ObservesCanExecute(() => IsEnabled);
    

    回调函数ExecuteGeneric:

            private void ExecuteGeneric(string parameter)
            {
                UpdateText = parameter;
            }
    
  5. 带参数的绑定

    那么,其实已经很明显了,我们之前创建DelegateCommand不是泛型版本,当创建一个泛型版本的DelegateCommand,那么T就是我们要传入的命令参数的类型,那么,我们现在可以把触发命令的Button本身作为命令参数传入

    Button Command="{Binding ExecuteGenericDelegateCommand}" CommandParameter="Passed Parameter"
    //博客案例
    <Button  x:Name="mybtn"  FontSize="30"  Content="Click Me" Margin="10" Height="60" Command="{Binding GetCurrentTimeCommand}"  CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}"/>
    

复合命令 CompositeCommands

官方案例

创建全局复合命令

using Prism.Commands;

namespace UsingCompositeCommands.Core
{
    public interface IApplicationCommands
    {
        CompositeCommand SaveCommand { get; }
    }

    public class ApplicationCommands : IApplicationCommands
    {
        private CompositeCommand _saveCommand = new CompositeCommand();
        public CompositeCommand SaveCommand
        {
            get { return _saveCommand; }
        }
    }
}

2.通过IOC容器注册其为单例

using System.Windows;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Unity;
using UsingCompositeCommands.Core;
using UsingCompositeCommands.Views;

namespace UsingCompositeCommands
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        //注册子窗体模块
        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
            moduleCatalog.AddModule<ModuleA.ModuleAModule>();
        }

        //通过IOC容器注册IApplicationCommands为单例
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>();
        }
    }
}

模块中---给复合命令注册子命令

using Prism.Commands;
using Prism.Mvvm;
using System;
using UsingCompositeCommands.Core;

namespace ModuleA.ViewModels
{
    public class TabViewModel : BindableBase
    {
        IApplicationCommands _applicationCommands;

        private string _title;
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        private bool _canUpdate = true;
        public bool CanUpdate
        {
            get { return _canUpdate; }
            set { SetProperty(ref _canUpdate, value); }
        }

        private string _updatedText;
        public string UpdateText
        {
            get { return _updatedText; }
            set { SetProperty(ref _updatedText, value); }
        }

        public DelegateCommand UpdateCommand { get; private set; }

        public TabViewModel(IApplicationCommands applicationCommands)
        {
            _applicationCommands = applicationCommands;

            UpdateCommand = new DelegateCommand(Update).ObservesCanExecute(() => CanUpdate);

            _applicationCommands.SaveCommand.RegisterCommand(UpdateCommand);
        }

        private void Update()
        {
            UpdateText = $"Updated: {DateTime.Now}";
        }       
    }
}

主窗口中的绑定

using Prism.Mvvm;
using UsingCompositeCommands.Core;

namespace UsingCompositeCommands.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Unity Application";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        private IApplicationCommands _applicationCommands;
        public IApplicationCommands ApplicationCommands
        {
            get { return _applicationCommands; }
            set { SetProperty(ref _applicationCommands, value); }
        }

        public MainWindowViewModel(IApplicationCommands applicationCommands)
        {
            ApplicationCommands = applicationCommands;
        }
    }
}

博客案例

prism提供CompositeCommand类支持复合命令,什么是复合命令,我们可能有这种场景,一个主界面的不同子窗体都有其各自的业务,假如我们可以将上面的例子稍微改下,我们分为三个不同子窗体,三个分别来显示当前年份,月日,时分秒,我们希望在主窗体提供一个按钮,点击后能够使其同时显示,这时候就有一种关系存在了,主窗体按钮依赖于三个子窗体的按钮,而子窗体的按钮不依赖于主窗体的按钮

下面是创建和使用一个prism标准复合命令的流程:

  • 创建一个全局的复合命令
  • 通过IOC容器注册其为单例
  • 给复合命令注册子命令
  • 绑定复合命令

1.创建一个全局的复合命令#

首先,我们创建一个类库项目,新增ApplicationCommands类作为全局命令类,代码如下:

[复制代码](javascript:void(0)😉

public interface IApplicationCommands
{
    CompositeCommand GetCurrentAllTimeCommand { get; }
}

public class ApplicationCommands : IApplicationCommands
{
   private CompositeCommand _getCurrentAllTimeCommand = new CompositeCommand();
   public CompositeCommand GetCurrentAllTimeCommand
   {
        get { return _getCurrentAllTimeCommand; }
   }
}

[复制代码](javascript:void(0)😉

其中我们创建了IApplicationCommands接口,让ApplicationCommands实现了该接口,目的是为了下一步通过IOC容器注册其为全局的单例接口

2.通过IOC容器注册其为单例#

我们创建一个新的项目作为主窗体,用来显示子窗体和使用复合命令,关键部分代码如下:

App.cs代码:

[复制代码](javascript:void(0)😉

using Prism.Unity;
using Prism.Ioc;
using System.Windows;
using CompositeCommandsSample.Views;
using Prism.Modularity;
using CompositeCommandsCore;

namespace CompositeCommandsSample
{

 public partial class App : PrismApplication
 {
     protected override Window CreateShell()
     {
         return Container.Resolve<MainWindow>();
     }

     //通过IOC容器注册IApplicationCommands为单例
     protected override void RegisterTypes(IContainerRegistry containerRegistry)
     {
        containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>();
     }

     //注册子窗体模块
     protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
     {
        moduleCatalog.AddModule<CommandSample.CommandSampleMoudle>();
     }
  }
}

[复制代码](javascript:void(0)😉

3.给复合命令注册子命令#

我们在之前的CommandSample解决方案下面的Views文件夹下新增两个UserControl,分别用来显示月日和时分秒,在其ViewModels文件夹下面新增两个UserControl的ViewModel,并且将之前的MainWindow也改为UserControl,大致结构如下图:

img

关键部分代码:

GetHourTabViewModel.cs:

[复制代码](javascript:void(0)😉

IApplicationCommands _applicationCommands;

public GetHourTabViewModel(IApplicationCommands applicationCommands)
{
    _applicationCommands = applicationCommands;
    //给复合命令GetCurrentAllTimeCommand注册子命令GetHourCommand
    _applicationCommands.GetCurrentAllTimeCommand.RegisterCommand(GetHourCommand);
}

private DelegateCommand _getHourCommand;
public DelegateCommand GetHourCommand =>
   _getHourCommand ?? (_getHourCommand = new DelegateCommand(ExecuteGetHourCommand).ObservesCanExecute(() => IsCanExcute));

void ExecuteGetHourCommand()
{
   this.CurrentHour = DateTime.Now.ToString("HH:mm:ss");
}

[复制代码](javascript:void(0)😉

GetMonthDayTabViewModel.cs:

[复制代码](javascript:void(0)😉

 IApplicationCommands _applicationCommands;

 public GetMonthDayTabViewModel(IApplicationCommands applicationCommands)
 {
     _applicationCommands = applicationCommands;
     //给复合命令GetCurrentAllTimeCommand注册子命令GetMonthCommand
     _applicationCommands.GetCurrentAllTimeCommand.RegisterCommand(GetMonthCommand);
 }

 private DelegateCommand _getMonthCommand;
 public DelegateCommand GetMonthCommand =>
      _getMonthCommand ?? (_getMonthCommand = new DelegateCommand(ExecuteCommandName).ObservesCanExecute(()=>IsCanExcute));

 void ExecuteCommandName()
 {
    this.CurrentMonthDay = DateTime.Now.ToString("MM:dd");
 }

[复制代码](javascript:void(0)😉

MainWindowViewModel.cs:

[复制代码](javascript:void(0)😉

IApplicationCommands _applicationCommands;

public MainWindowViewModel(IApplicationCommands applicationCommands)
{
    _applicationCommands = applicationCommands;
    //给复合命令GetCurrentAllTimeCommand注册子命令GetYearCommand
    _applicationCommands.GetCurrentAllTimeCommand.RegisterCommand(GetYearCommand);       
}

private DelegateCommand _getYearCommand;
public DelegateCommand GetYearCommand =>
   _getYearCommand ?? (_getYearCommand = new DelegateCommand(ExecuteGetYearCommand).ObservesCanExecute(()=> IsCanExcute));

void ExecuteGetYearCommand()
{
   this.CurrentTime =DateTime.Now.ToString("yyyy");
}

[复制代码](javascript:void(0)😉

CommandSampleMoudle.cs:

[复制代码](javascript:void(0)😉

using CommandSample.ViewModels;
using CommandSample.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;

namespace CommandSample
{
  public class CommandSampleMoudle : IModule
  {
    public void OnInitialized(IContainerProvider containerProvider)
    {
       var regionManager = containerProvider.Resolve<IRegionManager>();
       IRegion region= regionManager.Regions["ContentRegion"];

       var mainWindow = containerProvider.Resolve<MainWindow>();
       (mainWindow.DataContext as MainWindowViewModel).Title = "GetYearTab";
       region.Add(mainWindow);

       var getMonthTab = containerProvider.Resolve<GetMonthDayTab>();
       (getMonthTab.DataContext as GetMonthDayTabViewModel).Title = "GetMonthDayTab";
       region.Add(getMonthTab);

       var getHourTab = containerProvider.Resolve<GetHourTab>();
       (getHourTab.DataContext as GetHourTabViewModel).Title = "GetHourTab";
       region.Add(getHourTab);
    }

    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
            
    }
  }
}

[复制代码](javascript:void(0)😉

4.绑定复合命令#

主窗体xaml代码:

[复制代码](javascript:void(0)😉

<Window x:Class="CompositeCommandsSample.Views.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:prism="http://prismlibrary.com/"
        xmlns:local="clr-namespace:CompositeCommandsSample"
        mc:Ignorable="d" prism:ViewModelLocator.AutoWireViewModel="True"
        Title="MainWindow" Height="650" Width="800">
    <Window.Resources>
        <Style TargetType="TabItem">
            <Setter Property="Header" Value="{Binding DataContext.Title}"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Button Content="GetCurrentTime" FontSize="30" Margin="10" Command="{Binding ApplicationCommands.GetCurrentAllTimeCommand}"/>
        <TabControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion"/>
    </Grid>
</Window>

[复制代码](javascript:void(0)😉

MainWindowViewModel.cs:

[复制代码](javascript:void(0)😉

using CompositeCommandsCore;
using Prism.Mvvm;

namespace CompositeCommandsSample.ViewModels
{
  public  class MainWindowViewModel:BindableBase
  {
    private IApplicationCommands _applicationCommands;
    public IApplicationCommands  ApplicationCommands
    {
       get { return _applicationCommands; }
       set { SetProperty(ref _applicationCommands, value); }
    }

    public MainWindowViewModel(IApplicationCommands applicationCommands)
    {
        this.ApplicationCommands = applicationCommands;
    }
  }
}

IActiveAware命令

using Prism;
using Prism.Commands;
using Prism.Mvvm;
using System;
using UsingCompositeCommands.Core;

namespace ModuleA.ViewModels
{
    public class TabViewModel : BindableBase, IActiveAware
    {
        IApplicationCommands _applicationCommands;

        private string _title;
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        private bool _canUpdate = true;
        public bool CanUpdate
        {
            get { return _canUpdate; }
            set { SetProperty(ref _canUpdate, value); }
        }

        private string _updatedText;

        public string UpdateText
        {
            get { return _updatedText; }
            set { SetProperty(ref _updatedText, value); }
        }

        public DelegateCommand UpdateCommand { get; private set; }

        public TabViewModel(IApplicationCommands applicationCommands)
        {
            _applicationCommands = applicationCommands;

            UpdateCommand = new DelegateCommand(Update).ObservesCanExecute(() => CanUpdate);

            _applicationCommands.SaveCommand.RegisterCommand(UpdateCommand);
        }

        private void Update()
        {
            UpdateText = $"Updated: {DateTime.Now}";
        }
        
//*****************************
        bool _isActive;
        public bool IsActive
        {
            get { return _isActive; }
            set
            {
                _isActive = value;
                OnIsActiveChanged();
            }
        }
        private void OnIsActiveChanged()
        {
            UpdateCommand.IsActive = IsActive;

            IsActiveChanged?.Invoke(this, new EventArgs());
        }


        public event EventHandler IsActiveChanged;
    }
}

博客实现基于Task的命令

首先我们在界面新增一个新的按钮,用来绑定新的基于Task的命令,我们将要做的就是点击该按钮后,第一个Textbox的在5秒后显示"Hello Prism!",且期间UI界面不阻塞

xaml界面新增按钮代码如下:

<Button  x:Name="mybtn1"  FontSize="30"  Content="Click Me 1" Margin="10" Height="60" Command="{Binding AsyncCommand}" />

MainWindowViewModel新增代码:

[复制代码](javascript:void(0)😉

private DelegateCommand _asyncCommand;
  public DelegateCommand AsyncCommand =>
     _asyncCommand ?? (_asyncCommand = new DelegateCommand(ExecuteAsyncCommand));

  async void ExecuteAsyncCommand()
  {
     await ExampleMethodAsync();
  }

  async Task ExampleMethodAsync()
  {           
     await Task.Run(()=> 
     {
        Thread.Sleep(5000);
        this.CurrentTime = "Hello Prism!";
     } );
  }

[复制代码](javascript:void(0)😉

也可以更简洁的写法:

[复制代码](javascript:void(0)😉

 private DelegateCommand _asyncCommand;
 public DelegateCommand AsyncCommand =>
    _asyncCommand ?? (_asyncCommand = new DelegateCommand( async()=>await ExecuteAsyncCommand()));

 Task ExecuteAsyncCommand()
 {
    return Task.Run(() =>
    {
       Thread.Sleep(5000);
       this.CurrentTime = "Hello Prism!";
    });
  }

[复制代码](javascript:void(0)😉

posted @ 2020-08-10 01:47  AJun816  阅读(243)  评论(0)    收藏  举报