65.9K
CodeProject 正在变化。 阅读更多。
Home

从 Prism 开始 - 第 3 部分,共 n 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (9投票s)

2013 年 2 月 17 日

CPOL

9分钟阅读

viewsIcon

24830

介绍 

继续我之前的 Prism 系列(第二部分),在本文中,我将讨论在 Prism 框架的指导下,应用程序的各个组件之间如何进行通信。

背景 

在本系列的先前文章中,我已经提到 Prism 关注的是松耦合和模块化。因此,为了实现这两个方面,我们将应用程序划分为多个模块。现在,当我们谈论模块化时,首先想到的就是通信。这些模块将如何相互对话,它们将如何相互通信,等等,等等。因此,当我们需要模块之间的通信时,我们可以采取几种方法,例如命令、事件聚合、共享服务、区域上下文,可能还有更多。在本文中,我将主要关注这四个概念:  

  • 命令  
  • 事件聚合  
  • 区域上下文 
  • 共享服务   

现在让我们逐一来看,它们如何使我们的应用程序松散耦合 

命令/命令概述 

这是 Prism 应用程序中最常见的通信方法。再次强调,命令并不是 Prism 库特有的。它与 WPF 和 Silverlight 中的命令类似。命令的主要目的是将 UI 手势(例如按钮点击)绑定到需要执行的操作。每个命令都有一个 Execute 方法,该方法在执行命令时被调用。除了 Execute 方法之外,命令还有一个 CanExecute 方法,该方法决定命令是否可以执行。绑定到命令的元素可以根据 CanExecute 方法的结果启用或禁用。

创建命令最常见的方法是使用 **RoutedCommand** 或 **Custom Command**。RoutedCommand 将命令消息传递给 UI 元素。另一方面,Custom Command 可以通过创建自定义类来实现,该类又继承自 **ICommand** 接口。使用自定义命令需要大量额外的工作,并且必须提供命令处理程序以在命令就绪时进行挂钩并进行路由。

请注意,在 Prism 应用程序中,命令处理程序与可视化树中的任何元素都没有关联。但不要惊慌。幸运的是,Prism 为我们提供了两个类,它们使命令更加容易,并提供了更多功能。它提供了 **DelegateCommand** 和 **CompositeCommand**。  

DelegateCommand 是一个命令,允许您在执行命令时调用委托,而 CompositeCommand 允许我们将多个命令组合在一起。  

DelegateCommand   

DelegateCommand 是一种命令,它允许您提供方法作为委托,并在调用命令时调用这些委托。这意味着在代码隐藏中根本不需要事件处理程序。另一个优点是 DelegateCommand 通常是本地存储的,这意味着它们是在 ViewModel 中创建的,并且委托方法的关注点在 ViewModel 的上下文中。现在,Prism 再次为这些 DelegateCommand 提供了一些东西。Prism 实际上为您提供了两个 DelegateCommand:**DelegateCommand** 和 **DelegateCommand<T>**。区别在于 DelegateCommand 的 Execute 和 CanExecute 委托方法不接受参数,而 DelegateCommand<T> 允许您指定 Execute 和 CanExecute 参数的类型。现在,让我们深入代码:  

使用代码

在深入研究之前,我首先想告诉您我想在这里实现什么。


现在,我想要的是,每当我单击“计算”按钮时,都会计算利息金额。所以,让我们继续实现它。 

我们知道计算命令应该在按钮单击时执行。所以,我们需要为 **Button** 添加以下代码行: 

<Button Content="Calculate" Grid.Column="0" Grid.Row="3" HorizontalAlignment="Left" Command="{Binding CalculateInterestCommand}" /> 

到目前为止,我们的 ViewModel 中还没有这个命令。所以,让我们继续在我们的 ViewModel 中添加它: 

public ICommand CalculateInterestCommand { get; private set; } 

现在我们需要创建这个命令的实例。所以,在构造函数中添加这行: 

public InterestCalculatorViewModel()
{
            CalculateInterestCommand = new DelegateCommand(Calculate, () => CanCalculate);
 }  

在上面的命令中,第一个参数将是 Execute 方法(在本例中是 Calculate 方法),它将在命令执行时被调用;下一个参数将是 CanExecute(在本例中是 CanCalculate),它将告诉命令是否可以执行。 

现在继续创建您的 Execute 和 CanExecute 部分:  

private void Calculate()
{
     InterestAmount = PrincipalAmount * InterestRate / 100;
}
 
private bool CanCalculate 
{
       get
       {
           // you can add your condition to enable or disable the button
           return true;
       }
} 

运行应用程序,您就完成了。是不是很简单???好了,现在快速转到 CompositeCommand。   

CompositeCommand  

这些命令是全局作用域的,存在于我们应用程序的公共部分(在本例中是 Infrastructure 项目)中,并且包含多个子命令。当我们需要对多个视图执行相同的逻辑,但只使用一个命令时(在本例中,假设我们有一个选项卡控件,并且在每个选项卡上我都计算利息金额),就可以使用这些命令。在这种情况下,每个视图都将在 ViewModel 中拥有绑定到 CompositeCommand 的本地命令。因此,每当调用 CalculateAll 命令时,所有子命令也将被调用。同样,即使单个子项的 CanExecute 返回 false,CalculateAll 也不会被调用。

为了演示 CompositeCommand,首先让我们打开 Shell.xaml。在 Shell 中,我添加了另一个区域,其中包含 CalculateAll 按钮。我们还添加了一个 TabControl,以便注入视图的多个实例。您的 Shell.xaml 将如下所示:

然后我修改了 InterestCalculatorModule.cs 来创建视图的多个实例,如下所示:

此时,如果您启动应用程序并单击 CalculateAll 按钮,您需要单击该按钮三次,因为每个视图都需要一次。所以,为了让 CalculateAll 正常工作,让我们添加一个名为 CompositeCommands 的新类,如下所示:

public class CompositeCommands
    {
        public static readonly CompositeCommand CalculateAllCommand = new CompositeCommand();
    } 

现在转到 CalculateAllModule 并打开 CalculateAllView.xaml 并添加对 Infrastructure 项目的引用,如下所示:

<UserControl x:Class="CalculateAllModule.Views.CalculateAllView"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:cc="clr-namespace:PrismDemo.Infrastructure;assembly=PrismDemo.Infrastructure"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
           mc:Ignorable="d" 
           d:DesignHeight="300" d:DesignWidth="300">
    <Button Command="{x:Static cc:CompositeCommands.CalculateAllCommand}" Content="Calculate All"/> 
</UserControl> 

下一步是为我们的 CompositeCommand 注册 ViewModel。打开我们 ViewModel 的构造函数并添加以下行:

CompositeCommands.CalculateAllCommand.RegisterCommand(CalculateInterestCommand); 

现在启动应用程序,它将按预期工作。只需单击 CalculateAll 按钮,所有视图都将完成其计算。  

事件聚合概述 

这是 Prism 的一个常见模式。事件聚合以非常松散耦合的方式提供基于事件的通信。它由 **发布者** 和 **订阅者** 组成。在此,发布者触发事件,订阅者监听事件。请注意,订阅者不需要对发布者有强引用。 

Prism 内置了对事件聚合的支持,因为它通过 IEventAggregator 接口提供了一个核心服务。EventAggregator 负责定位和加载事件。它还负责维护系统中事件的集合。请记住,发布者和订阅者需要 EventAggregator 的实例,并且为了获取该事件并执行此操作,他们需要容器的帮助。EventAggregator 还提供多播发布/订阅功能,这意味着可以有多个发布者引发同一事件,并且有多个订阅者监听该事件。

使用 Prism 库创建的事件是类型化事件,这意味着在启动应用程序之前可以进行编译时检查。Prism 提供了一个名为 **CompositePresentationEvent<T>** 的类来创建此类事件。此类维护所有订阅者的列表并处理与事件分派相关的所有事情。请注意,这是一个泛型类,因此我们需要指定负载类型 T。T 是我们在发布事件时想要发送给订阅者的数据。EventAggregator 为我们提供了一些服务,例如:

  • 事件的发布 - 使用 Publish 方法
  • 事件的订阅 - 使用 Subscribe 方法
  • 使用强引用订阅 - 使用 Subscribe 方法上的 keepSubscriberReferenceAlive 参数。请谨慎使用此参数,因为使用此参数时,在关闭订阅者时,您必须手动取消订阅事件。
  •  过滤事件 - 在订阅时使用 filter delegate 
  •  事件的取消订阅 - 使用 UnSubscribe 方法 

现在,让我们再次回到代码。我们的要求是,每当计算完成时,用户都应该在状态栏上收到一条指示计算完成的消息。为了实现这一点,让我们添加一个名为 UpdateCalculationStatusEvent 的新类,如下所示:

public class UpdateCalculationStatusEvent:CompositePresentationEvent<String>
{   } 

现在我们需要在 ViewModel 中编写一个发布者事件。所以,添加一个 IEventAggregator 的实例,并将其传递给我们的 ViewModel 的构造函数,如下所示:

IEventAggregator mEventAggregator;
public InterestCalculatorViewModel(IInterestCalculatorView view,IEventAggregator eventAggregator)
   {
        this.mEventAggregator=eventAggregator;
    }

现在,当单击 Calculate 按钮时,我们希望将消息发送到状态栏。所以,为了实现这一点,我们需要修改我们的 Calculate 方法,如下所示: 

private void Calculate()  { 
    InterestAmount = PrincipalAmount * InterestRate / 100;
    mEventAggregator.GetEvent<UpdateCalculationStatusEvent>().Publish(String.Format("Interest amount is {0}", InterestAmount));        
 } 

根据上面的代码行,每当执行 Calculate 方法时,结果都将作为“利息金额为……”发布。 

接下来,我们需要为此事件创建一个订阅者,这里的订阅者将是 StatusBar。所以,让我们打开我们的 StatusBar ViewModel,如下所示:   

   public class StatusBarViewModel : ViewModelBase,IStatusBarViewModel
    {
        IEventAggregator mEventAggregator;
        public StatusBarViewModel(IStatusBarViewModel view, IEventAggregator eventAggregator)
            :base(view)
        {
            mEventAggregator = eventAggregator;
            mEventAggregator.GetEvent<UpdateCalculationStatusEvent>().Subscribe(CalculationCompleted);
        }
 
        private string message;
        public string Message
        {
            get { return message; }
            set
            {
                message = value;
                OnPropertyChanged("Message");
            }
        }
 
        private void CalculationCompleted(Object obj)
        {
            Message = "Updated";
        }
    } 

现在,让我们继续运行我们的应用程序,您就完成了 Smile | <img src= " />  

共享服务 

共享服务是 Prism 应用程序中另一种通信方式。它是一个自定义类,以非常松散耦合的方式为其他模块提供功能。该服务通常放置在一个单独的模块中,并可以使用服务定位器进行注册。当我们注册服务时,它被注册为一个公共接口。这使得其他模块可以使用我们的服务,而无需获取该模块的静态引用。但是,使用此公共接口有一个副作用,即具体实现不必共享。将您的服务注册为共享服务非常简单,可以通过 ContainerControlledLifetimeManager 完成。 

区域上下文 

有时会出现需要共享托管区域的视图与区域内的视图之间的上下文信息的情况。例如,您可能有一个主从详细信息类型的场景,其中有一个订单视图,它公开一个区域来显示订单详细信息。为了支持这种情况,Prism 提供了 **区域上下文**。通过使用区域上下文,可以在父视图和托管在区域中的子视图之间共享一个对象。 

请注意,Prism 仅支持从区域内的视图使用区域上下文,前提是该视图是依赖项对象。因此,如果您的视图不是依赖项对象,则需要创建一个自定义区域行为。要在此处注意的一点是不要使用 DataContext,因为 DataContext 主要用于将 ViewModel 绑定到视图。因此,这意味着 DataContext 存储了视图的整个 ViewModel。因此,除非视图非常简单,否则不建议使用 DataContext 来实现松散耦合视图之间的通信。 

一旦准备好示例代码,我将发布它。 

© . All rights reserved.