[RxUI] 11 – 消息总线

[RxUI] 11 – 消息总线

像许多其它MVVM框架一样,ReactiveUI也包括消息总线模式的实现。这样,就可以在代码的不同部分之间发送和接收消息,而无需彼此直接访问。

ReactiveUI中默认MessageBus(MessageBus.Current)的一个独特属性是它通过UI线程调度消息。这意味着从后台线程发送的消息将自动到达主线程。MessageBus也可用于在代码的不同层之间封送消息(通常是将消息从View发送到ViewModel)。

虽然使用此类是因为有时它是必需的,但是应将MessageBud作为最后的手段。MessageBus实际上是一个全局变量,这意味着它易受内存和事件泄漏的影响,此外,MessageBus的分离性意味着它是一个目的地不可见的goto。其还鼓励不良设计,因为很多人将View事件直接代理到ViewModel层,这使它们不是特别的ViewModelly。

基础

MessageBus非常简单。首先,设置一个listener:

// Listen for anyone sending instances of the KeyUpEventArgs class. Since
// MessageBus simply returns an IObservable, it can be combined or used in
// many different ways
MessageBus.Current.Listen<KeyUpEventArgs>()
    .Where(e => e.KeyCode == KeyCode.Up)
    .Subscribe(x => Console.WriteLine("Up Pressed!"));

现在,通过RegisterMessageSource将IObservable连接到总线:

MessageBus.Current.RegisterMessageSource(RootVisual.Events().KeyUpObs);

或者,手动发送消息:

MessageBus.Current.SendMessage(new KeyUpEventArgs());

避免使用MessageBus

与其它MVVM框架不同,ReactiveUI的独创性,通常会有更多正确的方法来解决问题。即使对象是随时间变化的,WhenAnyWhenAnyObservable也可以用来描述如何到达这些对象。通常在视图中最有用:

public LoginView()
{
    // As soon as the CredentialsAreValid turns to 'true', set the focus
    // to the Ok button.
    this.WhenAny(x => x.ViewModel.CredentialsAreValid, x => x.Value)
        .Where(x => x != false)
        .Subscribe(_ => OkButton.SetFocus());
}

考虑另一种情况,一个包含Document ViewModels列表的用来打开文档的ViewModel—每个Document包含一个Close命令。MVVM的许多传统实现都难以实现此命令,要么保留对列表的引用,要么通过MessageBus。

但是,除此之外,还可以使用Rx的操作符来以更优雅的方式解决此问题。

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;

public class DocumentViewModel : ReactiveObject
{
    public ReactiveCommand<Unit, Unit> Close { get; set; }

    public DocumentViewModel() 
    {
        // Note that we don't actually *subscribe* to Close here or implement
        // anything in DocumentViewModel, because Closing is a responsibility
        // of the document list.
        Close = ReactiveCommand.Create(() => { });
    }
}

public class MainViewModel : ReactiveObject
{
    public ObservableCollection<DocumentViewModel> OpenDocuments { get; protected set; }

    public MainViewModel()
    {
        OpenDocuments = new ObservableCollection<DocumentViewModel>();

        // Whenever the list of documents change, calculate a new Observable
        // to represent whenever any of the *current* documents have been
        // requested to close, then Switch to that. When we get something
        // to close, remove it from the list.
        OpenDocuments
            .ToObservableChangeSet()
            .AutoRefreshOnObservable(document => document.Close)
            .Select(_ => WhenAnyDocumentClosed())
            .Switch()
            .Subscribe(x => OpenDocuments.Remove(x));
    }

    IObservable<DocumentViewModel> WhenAnyDocumentClosed()
    {
        // Select the documents into a list of Observables
        // who return the Document to close when signaled,
        // then flatten them all together.
        return OpenDocuments
            .Select(x => x.Close.Select(_ => x))
            .Merge();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var mainViewModel = new MainViewModel();
        mainViewModel.OpenDocuments.Add(new DocumentViewModel());
        mainViewModel.OpenDocuments.Add(new DocumentViewModel());
        mainViewModel.OpenDocuments.Add(new DocumentViewModel());
        mainViewModel.OpenDocuments.Add(new DocumentViewModel());

        mainViewModel.OpenDocuments.First().Close.Execute().Subscribe();

        Console.WriteLine("Hello World!");
    }
}

原文 https://www.reactiveui.net/docs/handbook/message-bus

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注