[RxUI] 11 – 消息总线

[RxUI] 11 – 消息总线

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

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

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

[RxUI] 10 – 日志

[RxUI] 10 – 日志

ReactiveUI具有自己的日志框架,该框架可用于调试程序以及ReactiveUI本身。你可能会问:“真的吗,又一个日志框架?”。RxUI这么做的原因是出于可移植性—没有一个通用的流行日志框架支持ReactiveUI支持的所有平台,并且很多都是面向服务器的框架,不适用于简单的移动应用程序日志记录。

this.Log() 和 IEnableLogger

ReactiveUI的日志工作原理与其它框架有所不同—其设计灵感来自Rails的‘logger’。要使用它,就应该实现IEnableLogger接口:

public class MyClass : IEnableLogger
{
    // IEnableLogger doesn't actually require anything of us
}

现在,就可以在类上调用Log方法了。由于扩展方法的工作方式,必须在方法前添加this: ···  阅读全文

[RxUI] 9.1 – 绑定交互

[RxUI] 9.1 – 绑定交互

处理手动注册Interaction处理程序外,还有一组视图扩展方法来设置绑定,这两种方法都模仿RegisterHandler重载的handler参数:

IDisposable BindInteraction<TViewModel, TView, TInput, TOutput>(
    this TView view,
    TViewModel? viewModel,
    Expression<Func<TViewModel, Interaction<TInput, TOutput>>> propertyName,
    Func<InteractionContext<TInput, TOutput>, Task> handler);

IDisposable BindInteraction<TViewModel, TView, TInput, TOutput, TDontCare>(
    this TView view,
    TViewModel? viewModel,
    Expression<Func<TViewModel, Interaction<TInput, TOutput>>> propertyName,
    Func<InteractionContext<TInput, TOutput>, IObservable<TDontCare>> handler);

手动注册处理程序适用于简单情况。但是,例如,如果你希望自己是Interaction或其祖先之一,那么忧郁需要取消订阅旧版本并订阅新版本,复杂性就会开始增加:

var interactionDisposable = new SerialDisposable();

this
    .WhenAnyValue(x => x.ViewModel.MyInteraction)
    .Where(x => x != null)
    .Do(x => interactionDisposable.Disposable = x.RegisterHandler(context => /*Do Something*/))
    .Finally(() => interactionDisposable?.Dispose())
    .Subscribe();

将其与绑定方法进行比较… ···  阅读全文

[RxUI] 9 – 交互

[RxUI] 9 – 交互

有时,视图模型代码需要向用户请求确认。例如,在删除文件之前或发生错误之后。

从视图模型显示交互对话框是一个简单的解决方案,但是它将视图模型与特定的UI框架联系在一起,使程序难以测试或无法测试。

交互是ReactiveUI的解决方案,用于解决在用户提供某些输入之前挂起视图模型的执行路径问题。 ···  阅读全文

[RxUI] 8 – 事件

[RxUI] 8 – 事件

在程序中安装适当的ReactiveUI.Events.*包。有关更多信息,请参见安装指南。可以独立使用事件包,而无需引用ReactiveUI。ReactiveUI.Events.*将始终是与ReactiveUI无关的独立包。

该包使用Pharmacist生成平台内的事件可观察值。在将来的某个时间,Pharmacist将会替代这些包。永远不要使用EventHandlers,请使用生成的Observable.FromEventPattern版本。组合多个Observable.FromEventPattern在一起可以获得惊人的合成效果。要记得使用Reactive Extensions提供的功能取消订阅

var codes = new[]
{
    Key.Up,
    Key.Up,
    Key.Down,
    Key.Down,
    Key.Left,
    Key.Right,
    Key.Left,
    Key.Right,
    Key.A,
    Key.B
};

// convert the array into an sequence
var koanmi = codes.ToObservable();

this.Events().KeyUp

    // we want the keycode
    .Select(x => x.Key)
    .Do(key => Debug.WriteLine($"{key} was pressed."))

    // get the last ten keys
    .Window(10)

    // compare to known konami code sequence
    .SelectMany(x => x.SequenceEqual(koanmi))
    .Do(isMatch => Debug.WriteLine(isMatch))

    // where we match
    .Where(x => x)
    .Do(x => Debug.WriteLine("Konami sequence"))
    .Subscribe(y => { });

在WhenActivated中使用事件

如果对视图中推送的事件作出响应并在可观察序列中引用视图模型,那么请记得取消订阅。如果视图模型的寿命超过了视图的寿命,反之亦然,则可能造成内存泄漏,而WhenActivated可以帮助避免这种情况。更多信息请,参见WhenActivated文档。 ···  阅读全文

[RxUI] 7 – 设计时

[RxUI] 7 – 设计时

ReactiveUI绑定

对于使用ReactiveUI类型安全绑定的解决方案,ReactiveUI提供了更好的设计时数据系统。this.Bind系列方法将覆盖XAML中已放置的所有内容。如果有如下所示XAML:

<TextBlock 
    x:Name="ExampleTextBlock" 
    Text="This is design time text" />

将会在设计时看到This is design time text字符串,然后在运行时被绑定代码所覆盖:

this.WhenActivated(disposable => 
{
    // ReactiveUI will bind ExampleString ViewModel property 
    // to ExampleTextBlock.Text property (defined above)
    this.Bind(ViewModel, 
        viewModel => viewModel.ExampleString, 
        view => view.ExampleTextBlock.Text)
        .DisposeWith(disposable);
});

常规绑定

如果使用常规绑定或UWP上的类型安全{x:Bind}标记扩展,则可以导入d指令并设置设计时DataContext。为易于实现,可以从ViewModel中提取一个接口,并为其创建两个实现,一个实现仅显示设计时数据,另一个实现为实际视图模型。看下面一个例子。 ···  阅读全文

[RxUI] 6 – 依赖反转

[RxUI] 6 – 依赖反转

依赖注入

依赖关系解析对于将那些通常必须在特定于平台代码中的逻辑转移到共享平台代码中非常有用。首先,需要为想要使用的东西定义一个接口—这个例子不是最佳实践,但是其具有说明性。

依赖关系解析是核心架构中内置的一项功能,该功能允许库和ReactiveUI本身使用其它库找那个的类,而无需直接引用它们。这对于跨平台程序非常有用,因为允许可移植代码使用非移植API,只要它们可以通过接口进行描述即可。

ReactiveUI对依赖关系解析的使用可以更恰当地称为服务定位器模式。如果定位器模式不适合,请查看如何进行合成根。 ···  阅读全文

[RxUI] 5 – 默认异常处理

[RxUI] 5 – 默认异常处理

在具有ThrownExceptions属性的对象没有被订阅时,ReactiveUI的默认行为是使程序崩溃。

可以通过连接可观察对象到RxApp.DefaultExceptionHandler来覆盖默认行为或挂接调试器或分析客户端:

public class MyCoolObservableExceptionHandler : IObserver<Exception>
{
    public void OnNext(Exception value)
    {
        if (Debugger.IsAttached) Debugger.Break();

        Analytics.Current.TrackEvent("MyRxHandler", new Dictionary<string, string>()
                                                {
                                                    {"Type", value.GetType().ToString()},
                                                    {"Message", value.Message},
                                                });

        RxApp.MainThreadScheduler.Schedule(() => { throw value; }) ;
    }

    public void OnError(Exception error)
    {
        if (Debugger.IsAttached) Debugger.Break();

        Analytics.Current.TrackEvent("MyRxHandler Error", new Dictionary<string, string>()
        {
            {"Type", error.GetType().ToString()},
            {"Message", error.Message},
        });

        RxApp.MainThreadScheduler.Schedule(() => { throw error; });
    }

    public void OnCompleted()
    {
        if (Debugger.IsAttached) Debugger.Break();
        RxApp.MainThreadScheduler.Schedule(() => { throw new NotImplementedException(); });
    }
}

原文 https://www.reactiveui.net/docs/handbook/default-exception-handler  ··· 

[RxUI] 4 – 数据持久化

[RxUI] 4 – 数据持久化

使用经典的ViewModel时,需要在程序终止/恢复时保存一些重要数据。这里不需要保存命令的状态,因为它们是由构造函数重新创建的。是否保留搜索结果是有争议的,也许与Akavache的实现有关。但是,在需要将ViewModel恢复到其确切状态时,就必须要保存SearchQuery。

[DataContract]
public class SearchViewModel : ReactiveObject, ISearchViewModel
{
    private readonly ObservableAsPropertyHelper<IEnumerable<SearchResults>> _searchResults;
    private readonly ISearchService searchService;
    private string searchQuery;

    public SearchViewModel(ISearchService searchService = null) 
    { 
        this.searchService = searchService ?? Locator.Current.GetService<ISearchService>();

        var canSearch = this
            .WhenAnyValue(x => x.SearchQuery)
            .Select(query => !string.IsNullOrWhiteSpace(query));

        Search = ReactiveCommand.CreateFromTask(
            () => this.searchService.Search(SearchQuery),
            canSearch);

        _searchResults = Search.ToProperty(this, x => x.SearchResults);
    }

    public IEnumerable<SearchResults> SearchResults => _searchResults.Value;

    public ReactiveCommand<Unit, IEnumerable<SearchResults>> Search { get; }

    [DataMember]
    public string SearchQuery 
    {
        get => searchQuery;
        set => this.RaiseAndSetIfChanged(ref searchQuery, value);
    }
}

当然,可以使用Newtonsoft.Json悬挂驱动及其属性,例如[JsonProperty][JsonIgnore],但请注意,在这种情况下,Newtonsoft.Json使用的是选择退出方法,而DataContractSerializer只用的是选择加入。选择退出意味着所有公共字段和属性都将被序列化,除非通过[IgnoreDataMember][JsonIgnore]属性明确忽略它们,而选择加入则相反。Newtonsoft.Json完全支持DataContract属性,但是当使用[DataContract]属性时,回退到选择加入方法,类似于DataContractSerializer

如果序列化采用了选择退出方法,请确保使用[IgnoreDataMember][JsonIgnore]属性,否则会造成循环引用。应该将上述模式应用于需要序列化和还原的每个ViewModel。ViewModel序列化是棘手的业务,必须确定要序列化的内容和要重新创建的内容。当应用唤醒后,应该重新计算/重新加载某些内容,而不是尝试将其保存。 ···  阅读全文

[RxUI] 3.5 – Windows Presentation Foundation(WPF)

[RxUI] 3.5 – Windows Presentation Foundation(WPF)

手动实现IViewFor<T>,并确保ViewModel是DependencyProperty。另外,应始终通过WhenActivated取消绑定,否则会导致内存泄漏。如果不使用WhenActivated,那么XAML的DependencyProperty系统会导致内存泄漏。有几条规则需要注意,最重要的一条是:如果对除此之外的其它任何操作执行WhenAny,则需要将其放在WhenActivated中。有关详细信息,请参见WhenActivated

本示例中的目标是将ViewModel的TheText属性双向绑定到TextBox,并将TheText属性单向绑定到TextBlock,因此当用户在TextBox中键入文本时,TextBlock就会更新。

public class TheViewModel : ReactiveObject
{
    private string theText;
    public string TheText
    {
        get => theText;
        set => this.RaiseAndSetIfChanged(ref this.theText, value);
    }

    public ReactiveCommand<Unit,Unit> TheTextCommand { get; }

    public TheViewModel()
    {
        TheTextCommand = ReactiveCommand
            .CreateFromObservable(ExecuteTextCommand);
    }

    private IObservable<Unit> ExecuteTextCommand()
    {
        TheText = "Hello ReactiveUI";
        return Observable.Return(Unit.Default);
    }
}
<Window /* snip */>
  <StackPanel>
    <TextBox x:Name="TheTextBox" />
    <TextBlock x:Name="TheTextBlock" />
    <Button x:Name="TheTextButton" />
  </StackPanel>
</Window>

在此示例中,将手动实现IViewFor<TViewModel>接口,但对于UserControl,应该使用封装了IViewFor实现的ReactiveUserControl。 ···  阅读全文