Caliburn Micro 第四部分 : 事件聚合

http://www.mindscapehq.com/blog/index.php/2012/02/01/caliburn-micro-part-4-the-event-aggregator/


对于刚刚接触我们这个教程系列的朋友,相信你已经了解了如何使用 Caliburn Micro 去创建一个健壮的 MVVM 架构的 WPF 应用程序。Caliburn Micro 是一种通过使用多种流行的 UI 模型,包括 MVVM、MVP 和 MVC,来帮助创建 .NET 程序(WPF、Silverlight 和 Windows Phone 7)。它提供了多种简洁的方式来减少你创建绑定和连接时间的工作量。Caliburn Micro 的多个模块使得模型对象和 UI之间的联系变得更加简单明了。当然这也意味着我们可以更加轻松的去测试和维护应用程序。

在这周的教程中,我们将会学习如何使用 Caliburn Micro 的事件聚合器。事件聚合器是一种服务,它可以帮助你很轻松在应用的不同部分之间传递消息。它是非常有用的,特别是在你的应用程序是由多个需要通信的 View-Model 组成的情况下。为了实现这个功能,我们必须将对象(比如View Model)订阅到事件聚合器上,以及明确它们应该监听哪种类型的消息。同时,你也要定义当对象接收到信息的时候该进行何种操作。然后,当程序的另一个部分发布消息的时候,事件订阅器会确保合适的订阅对象能够接收到并给出相对应的操作。在整个教程中,我们会扩展之前的创建的应用程序。

尽管事件订阅器在应用程序具有多个 View Model 对象的时候会显得十分有用,我们会尽量保持我们的教程项目尽量的小。需要注意的是,和之前的教程相比,今天的更多的是需要你自己去领会。在今天的教程的末尾,我们会得到一个展示两个View的程序,当然,View拥有各自的ViewModel。其中一个View会展示多个单选按钮,每一个按钮表示展示不同的颜色。当其中任一按钮被选中的时候,我们会发布一个带有颜色信息的消息。另一个 View 会接受到这个消息,并修改矩形相应的颜色。为了实现这个功能,我们分四个步骤,分别是添加 View 到应用程序,实现 IHandle 接口,订阅其中一个View Model到事件聚合器,以及从另一个View Model这发布消息。

添加 View 和 ViewModel 到应用程序

为了更好的说明事件订阅器,我们至少需要建立两个 View Model 对象。在之前,我们已经有了一个 View Model 对象(AppViewModel),所以我们只需要添加另一个即可。是否还记得之前所描述的命名规范呢?添加一个新的类,名字叫做 ColorViewModel,然后添加一个 UserControl 叫做 ColorView 。我们首先改变 ColorView 的背景色以便我们能够在启动程序的时候能够看到。根据可视结构,我们需要将一个新的 ColorView 对象添加到 AppView 中(Views 不需要嵌套,因为我们会使用事件聚合器;一个 ViewModel 对象可以监听到应用程序的所有消息)。为了实现它,AppViewModel 需要添加一个 ColorViewModel 类型的 Property,我们将会在构造函数中给它赋值:

public class AppViewModel : PropertyChangedBase
{
    public AppViewModel(ColorViewModel colorModel)
    {
        ColorModel = colorModel;
    }

    public ColorViewModel ColorModel { get; private set; }
}

在 AppView.xaml 文件中,我们将 Grid 拆分为两列结构,在第一列这展示 ColorView 对象:

<Grid Width="300" Height="300" Background="LightBlue">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <ContentControl Name="ColorModel" Margin="10" />
</Grid> 

看一下会发生什么呢?我们设置了一个名称和刚刚添加到 AppViewModel 中的 Property 相同的 ContentControl。通过这个,Caliburn Micro 和友好的将两者绑定到一起。

如果刚刚我们运行了程序,我们会发现它抛出了一个异常,信息是无法在 AppViewModel 中找到默认的构造函数。这个点很重要,因为我们已经给 AppViewModel 添加了一个需要一个参数的构造函数。为了解决这个异常,我们需要按照以下的代码来更新我们的 AppBootstrapper (确保已经添加了System.ComponentModel.Composition.dll 的引用)。

public class AppBootstrapper : Bootstrapper<AppViewModel>
{
    private CompositionContainer _container;

    protected override void Configure()
    {
        _container = new CompositionContainer(new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));

        CompositionBatch batch = new CompositionBatch();
        batch.AddExportedValue<IWindowManager>(new WindowManager());
        batch.AddExportedValue<IEventAggregator>(new EventAggregator());
        batch.AddExportedValue(_container);

        _container.Compose(batch);

    }

    protected override object GetInstance(Type service, string key)
    {
        string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(service) : key;

        var exports = _container.GetExportedValues<object>(contract);

        if (exports.Count() > 0)
        {
            return exports.First();
        }

        throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));

    }

}

这里和我们下载的 Caliburn Micro 的示例程序所包用到的 bootstrappers 十分的相似。

接下来,我们需要给我们的 View Model 类添加 ExportAttribute 。有了它,AppBootstrapper 的 GetInstance 才能够正常的运行。

[Export(typeof(AppViewModel))]
public class AppViewModel : PropertyChangedBase
{
  ...
}

[Export(typeof(ColorViewModel))]
public class ColorViewModel
{
}

最后,我们给 AppViewModel 的构造函数添加 ImportingConstructorAttribute 特性。

[ImportingConstructor]
public AppViewModel(ColorViewModel colorModel)
{
    ColorModel = colorModel;
}

运行程序,你就会发现 ColorView 展示在了 AppView 中。

添加一个 Rectangle 到第二列,当处理接收到的消息时,会改变相应的颜色,所以它的颜色在 AppViewModel 是以 Property 的形式控制的。

private SolidColorBrush _Color;

public SolidColorBrush Color
{
    get { return _Color; }
    set
    {
        _Color = value;
        NotifyOfPropertyChange(() => Color);
    }
}

第二步:实现 IHandle 接口

接下来,我们要从 ColorViewModel 中发布消息以及在 AppViewModel 中接收到消息。为了实现这一功能,我们需要创建一个携带消息的类,这个类通常很小和简单。它主要包含一些 Property 来保存我们发送的信息。下面是一个示例:

public class ColorEvent
{
    public ColorEvent(SolidColorBrush color)
    {
        Color = color;
    }

    public SolidColorBrush Color { get; private set; }
}

为了使 AppViewModel 能够处理相应的事件,需要实现 IHandle 接口。在我们的示例程序中,我们使用 ColorEvent 作为泛型类。IHandle 接口仅仅包含一个我们需要实现的方法 Handle 。在 AppViewModel 的 Handle 方法中,我们主要查看传递到 ColorEvent 中的 SolidColorBrush 对象,并将它赋值给 Color Property。

public void Handle(ColorEvent message)
{
    Color = message.Color;
}

第三步:订阅

现在,我们需要把 AppViewModel 对象订阅到一个事件聚合器中,只有这样,它才能监听发布的消息。通过给 AppViewModel 的构造函数添加另一个参数 IEventAggregator 我们就可以实现。在需要创建 AppViewModel 的时候,Caliburn Micro 会把我们在 bootstrapper 设置的事件订阅器传递给它。

第四步:发布

通过给 ColorViewModel 添加一个事件订阅器,使得它能够正常的发布消息。给 ColorViewModel 添加一个带有 IEventAggregator 类型的参数的构造函数,并将 IEventAggregator 类型对象保存到一个 Field 中,记得要添加 ImportingConstructor 特性:

private readonly IEventAggregator _events;

[ImportingConstructor]
public ColorViewModel(IEventAggregator events)
{
    _events = events;
}

现在,我们需要向 ColorView 中添加单选按钮,监听它们的 Click 事件并发布相应的消息

<RadioButton Name="Red" Content="Red" Foreground="White"
         VerticalAlignment="Center" HorizontalAlignment="Center" />
<RadioButton Name="Green" Content="Green" Foreground="White"
         VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="1" />
<RadioButton Name="Blue" Content="Blue" Foreground="White"
         VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="2" />

public void Red()
{
    _events.Publish(new ColorEvent(new SolidColorBrush(Colors.Red)));
}

public void Green()
{
    _events.Publish(new ColorEvent(new SolidColorBrush(Colors.Green)));
}

public void Blue()
{
    _events.Publish(new ColorEvent(new SolidColorBrush(Colors.Blue)));
}

结尾

运行程序,当你单击不同的单选按钮,Rectangle 会切换到相对应的颜色。

知识共享许可协议
《Caliburn Micro 第四部分 : 事件聚合》 常伟华 创作。
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议 | 3.0 中国大陆许可协议进行许可。

站内公告