浏览
分类: C#

[RxUI] 19 – When Any

[RxUI] 19 – When Any

在交互式UI应用程序中,状态不断变化以响应用户操作和应用程序事件。ReactiveUI可以将程序状态的更改表示为值流,并可以使用功能强大的Reactive Extensions库进行组合和操作。

当考虑它时,动机足够直观。不难想象,对属性的更改可以视为事件 — 这就是INotifyPropertyChanged的工作方式。从这里,在Rx上使用的参数与事件上的相同。特别是在MVVM应用程序设计的上下文中,将属性更改为可观察对象具有以下优点:

  • 可以根据属性更改来定义程序的逻辑
  • 可以使用Rx操作符以声明的方式构建和表达逻辑
  • 由于像时间和异步等概念在可观察对象上下文中得到了一流的处理,因此更易于推理。

ReactiveUI提供了WhenAny的多种变体,来帮助将属性作为可观察流使用。 ···  阅读全文

[RxUI] 18 – When Activated

[RxUI] 18 – When Activated

WhenActivated是一种跟踪可释放对象的方式。除此之外,还可用于延迟ViewModel的设置,直到真正需要为止。WhenActivated还可以启动或停止对“热”可观察对象的响应,例如定期对网络终端执行ping操作或实时更新用户当前位置的后台任务。而且,当ViewModel加载时,可以使用WhenActivated触发启动逻辑。看一个示例:

public class ActivatableViewModel : IActivatableViewModel 
{
    public ViewModelActivator Activator { get; }

    public ActivatableViewModel()
    {
        Activator = new ViewModelActivator();
        this.WhenActivated(disposables => 
        {        
            // Use WhenActivated to execute logic
            // when the view model gets activated.
            this.HandleActivation();

            // Or use WhenActivated to execute logic
            // when the view model gets deactivated.
            Disposable
                .Create(() => this.HandleDeactivation())
                .DisposeWith(disposables);

            // Here we create a hot observable and 
            // subscribe to its notifications. The
            // subscription should be disposed when we'd 
            // like to deactivate the ViewModel instance.
            var interval = TimeSpan.FromMinutes(5);
            Observable
                .Timer(interval, interval)
                .Subscribe(x => { /* do smth every 5m */ })
                .DisposeWith(disposables);

            // We also can observe changes of a
            // property belonging to another ViewModel,
            // so we need to unsubscribe from that
            // changes to ensure we won't have the
            // potential for a memory leak.
            this.WhenAnyValue(...)
                .InvokeCommand(...)
                .DisposeWith(disposables);
        });
    }

    private void HandleActivation() { }

    private void HandleDeactivation() { }
}

如何确保ViewModel被激活?

如果ViewModel实现IVewFor接口,则框架将确保从ViewModel到View的链接。记住要使ViewModel作为DependencyProperty,并且在IVewFor<TVeiwModel>的构造函数中添加对WhenActivated的调用。

视图

每当将一个对象订阅到另一个对象公开的事件时,都会引起内存泄漏。对于基于XAML的平台,尤其是可能不会自动回收依赖属性引用的对象/事件。 ···  阅读全文

[RxUI] 17.1 – 样板代码

[RxUI] 17.1 – 样板代码

如果厌倦了为属性更改通知编写样板代码,可以尝试PropertyChanged.FodyReactiveUI.Fody。这些库都基于Fody(一个编造.NET程序集的扩展工具),它们将在编译时为属性注入INotifyPropertyChanged代码。建议使用ReactiveUI.Fody包,因为该包还处理ObservableAsPropertyHelper属性。

读写属性

通常,属性声明如下:

private string name;
public string Name 
{
    get => name;
    set => this.RaiseAndSetIfChanged(ref name, value);
}

使用ReactiveUI.Fody,就不必为读写属性的getter和setter编写样板代码 — 该包将在编译时自动完成。需要做的就是用[Reactive]属性注释该属性,如下所示: ···  阅读全文

[RxUI] 17 – 视图模型

[RxUI] 17 – 视图模型

每个MVVM框架的核心都是ViewModel — 虽然此类是MVVM模式中最有趣的方面,但也是最容易被误解的部分。正确推断ViewModel是和不是,对于正确应用MVVM模式至关重要。

ViewModel之禅

大多数UI框架在设计框架时就没有考虑单元测试,或者这些问题被认为超出了范围。结果,UI对象往往很难进行单元测试,因为它们不仅仅是普通对象。它们可能依赖于现有的运行循环,或经常在初始化中以某种方式调用静态类/全局变量。

因此,由于UI类是不可测试的,那么新目标就是将尽可能多的敏感的代码放入表现View的类中,但这只是可以创建的常规类。然后,希望View中的实际代码尽可能简单、机械且简短,因为它本质上是不可测试的。 ···  阅读全文

[RxUI] 16.1 – 扩展View支持

[RxUI] 16.1 – 扩展View支持

扩展IViewFor

有时候,需要将ReactiveUI视图类型的功能进行扩展。答案就在于扩展IViewFor接口,以便ReactiveUI将其拾取并将其注册到ViewLocator实例。一旦实现IViewFor<T>,就可以在类上以扩展方法方式使用绑定。

下面是为各种平台提供的一些ReactiveUI基本实现,这不是详尽列表。

注意:在撰写本文时,C#不支持多类继承

Xamarin Forms示例

对于此示例,将使用Rg.Plugins.Popup库,该库显示Xamarin.Forms的弹出样式模态页面。这种情况下,需要扩展基本的实现。另一个选择是从IViewFor<T>显式扩展每个页面。请根据实际情况选择最合适的方式。 ···  阅读全文

[RxUI] 16 – View定位

[RxUI] 16 – View定位

IViewFor、激活和数据绑定

为了在视图中使用绑定,必须先在视图上实现IViewFor<TViewModel>。一旦实现了IViewFor<T>,就可以在类上使用绑定扩展方法了,同时实现了视图和那些与其相关的继承了IActivatableViewModel接口的视图模型的激活和失活功能。有关详细信息和特定于平台的示例,请参见数据绑定部分。

View定位

View定位是ReactiveUI的一项功能,可以将View和ViewModel关联并自动设置它们。

ViewModelViewHost

使用View定位最简单方法是通过ViewModelViewHost控件,该控件是一个View(在Cocoa中,是一个UIVIew/NSView,在基于XAML的平台上是一个控件),该控件仅有一个ViewModel属性。当ViewModel属性被设置后,View定位将查找关联的视图并将其加载到容器中。 ···  阅读全文

[RxUI] 15 – 用户输入验证

[RxUI] 15 – 用户输入验证

ReactiveUI本身提供了一些强大的功能,可以即时验证用户输入。使用WhenAnyValue,可以侦听视图模型属性的更改并控制ReactiveCommand的可执行性。当响应式命令的CanExecute可观察对象返回false时,与该命令绑定的控件将会进入禁用状态。最简单的验证器如下所示:

// Declare name validator as IObservable<bool> which emitts a new value when name changes.
var nameValid = this.WhenAnyValue(x => x.Name, name => !string.IsNullOrWhiteSpace(name));

// The reactive command will stay disabled while name is invalid.
var saveName = ReactiveCommand.CreateFromTask(_ => Save(this.Name), canExecute: nameValid);

这绝对适用于简单的场景,但是对于较大的表单和更复杂的验证,肯定需要尝试ReactiveUI.Validation包。

ReactiveUI.Validation

这是进行验证的主要方式。该包包含了基于ReactiveUI的解决方案的验证帮助,并以响应式来提供。ReactiveUI.Validation源码在GitHub上。该包支持所有平台,包括.NET Framework、.NET Standard、MonoAndroid、Tizen、UAP、Xamarin.iOS、Xamarin.Mac、Xamarin.TVOS。将以下包安装到类库和特定于平台的项目中。 ···  阅读全文

[RxUI] 14 – 调度

[RxUI] 14 – 调度

调度是编写任何使用Reactive Extension程序的核心部分,因为所有操作符都可以被延迟(即在其它线程或UI线程上运行)。调度器允许程序控制上下文代码在其中运行,并很重要的一点是在其它线程上运行代码的库必须能够识别调度器。ReactiveUI提供了两个程序级的调度器,应该用它们替代其它调度器(例如内置的Rx调度器):

  • RxApp.MainThreadScheduler — 此调度器在UI线程上执行。在基于XAML的平台上,等效于Dispatcher.BeginInvoke。
  • RxApp.TaskpoolScheduler — 此调度程序通过TPL任务池执行代码。等效于Task.Run。

要使用这两个内置调度器,需要在Observable链中使用ObserveOn操作符:

this.WhenAnyValue(x => x.MyImportantProperty).ObserveOn(RxApp.MainThreadScheduler).Subscribe(x => ...);

要控制ReactiveControl在哪里运行订阅,可以传入一个调度器。默认情况下,会使用当前线程的调度器,因此,如果从UI线程初始化,就将使用UI线程运行。 ···  阅读全文

[RxUI] 13 – 路由

[RxUI] 13 – 路由

路由使程序可以通过多个视图及其相应视图模型来协调导航,并跟踪用户的导航状态。

ReactiveUI支持以下平台的路由:

  • Avalonia
  • Universal Windows Platform (UWP)
  • Windows Forms
  • Windows Presentation Foundation (WPF)
  • WinRT
  • WP8
  • Xamarin Forms

路由也可以在没有Xamarin.Forms的iOS和Android上运行,但并不总是好用。如果在这些平台上很难实现视图模型路由,则可以使用视图优先路由并自定义它的大多数方面。 ···  阅读全文

[RxUI] 12 – Observable As Property Helper

[RxUI] 12 – Observable As Property Helper

ObservableAsPropertyHelper(OAPH)是一个类,它简化了IObservable和View Model上的属性之间的互操作。它允许你拥有一个属性,该属性反映通过IObservable流推送的最新值。

当可观察对象推送新值时,其将为View Model调用基于INotifyPropertyChangedINotifyPropertyChanging的事件。ObservableAsPropertyHelper<T>与Lazy非常相似,因为它提供了Value成员,该成员提供了Observable的最新值。它们通常是只读的,并反映IObservable流。通常将ObservableAsPropertyHelper与WhenAny扩展组合使用。

示例

首先,使用ObservableAsPropertyHelper<T>类声明一个Output Property: ···  阅读全文