zwvista

导航

ReactiveX 学习笔记(31)ReactiveUI 使用笔记

文档

Handbook

安装

使用 ReactiveUI 需要安装平台所对应的包。
比如开发 WPF 应用程序需要下载 ReactiveUI 和 ReactiveUI.WPF。

ViewModel

自定义的 ViewModel 类应该继承 ReactiveObject 类。

public class ExampleViewModel : ReactiveObject { }

可读可写的属性

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

只读属性

public ReactiveCommand<Object> PostTweet { get; }

PostTweet = ReactiveCommand.Create(/*...*/);

只写属性

private readonly ObservableAsPropertyHelper<string> firstName;
public string FirstName => firstName.Value;

// Name 属性发生改变时
// 如果属性值非空
// 就提取该属性值中第一个空格前面的部分,
// 并将其设置为 FirstName
this.WhenAnyValue(x => x.Name)
    .Where(x => !string.IsNullOrEmpty(x))
    .Select(x => x.Split(' ')[0])
    .ToProperty(this, x => x.FirstName, out firstName);

下载并使用 ReactiveUI.Fody 后代码可以简化
可读可写的属性

[Reactive]
public string Name { get; set; }

只写属性

public string FirstName { [ObservableAsProperty] get; }

this.WhenAnyValue(x => x.Name)
    .Where(x => !string.IsNullOrEmpty(x))
    .Select(x => x.Split(' ')[0])
    .ToPropertyEx(this, x => x.FirstName);

Command

通过调用 ReactiveCommand 类的静态方法创建命令

  • CreateFromObservable()
  • CreateFromTask()
  • Create()
  • CreateCombined()

同步命令

ReactiveCommand<int,Unit> command = ReactiveCommand.Create<int>(
    integer => Console.WriteLine(integer));
command.Execute(42).Subscribe();

异步命令

var command = ReactiveCommand.CreateFromObservable<Unit, int>(
    _ => Observable.Return(42).Delay(TimeSpan.FromSeconds(2)));
command.Execute(Unit.Default).Subscribe();
command.Subscribe(value => Console.WriteLine(value));

命令的可用性

var canExecute = this.WhenAnyValue(
    x => x.UserName, x => x.Password,
    (userName, password) => 
        !string.IsNullOrEmpty(userName) && 
        !string.IsNullOrEmpty(password));
var command = ReactiveCommand.CreateFromTask(LogOnAsync, canExecute);

UI

在 Window, Page, UserControl 类里面创建 ViewModel, 并将其设置为 DataContext。

<Window x:Class="ReactiveDemo.MainWindow"
        ...>
<!-- using traditional XAML markup bindings -->
</Window>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new AppViewModel();
    }
}

DynamicData

数据集合应该采用 DynamicData 库中的集合类型:SourceList 和 SourceCache。

// 内部集合,用于实际操作
SourceList<bool> __items = new SourceList<bool>();
// 内部字段,用于绑定内部集合
ReadOnlyObservableCollection<bool> _items;
// 外部属性,用于绑定控件
public ReadOnlyObservableCollection<bool> Items => _items;
// 处理集合
__items.Add(true);
__items.RemoveAt(0);
__items.Add(false);
// 映射,过滤后再绑定到内部字段
__items.Connect()
    .Transform(x => !x)
    .Filter(x => x)
    .ObserveOn(RxApp.MainThreadScheduler)
    .Bind(out _items)
    .Subscribe();

Validation

数据验证需要额外安装一个 ReactiveUI.Validation 的包。
要进行数据验证,需要实现 IValidatableViewModel 接口或者继承 ReactiveValidationObject 类
ReactiveValidationObject 实现了 IValidatableViewModel 接口和 INotifyDataErrorInfo 接口
IValidatableViewModel 接口包含 ValidationContext 对象
INotifyDataErrorInfo 接口是 WPF 内部用于数据验证的接口,包含 HasErrors 属性,ErrorsChanged 事件以及 GetErrors 方法。

public class SampleViewModel : ReactiveObject, IValidatableViewModel
{    
    public ValidationContext ValidationContext { get; } = new ValidationContext();
    public ValidationHelper ComplexRule { get; }
    public ValidationHelper AgeRule { get; }
    [Reactive] public int Age { get; set; }
    [Reactive] public string Name { get; set; }
    public ReactiveCommand<Unit, Unit> Save { get; }
    public SampleViewModel()
    {
        this.ValidationRule(
            viewModel => viewModel.Name,
            name => !string.IsNullOrWhiteSpace(name),
            "You must specify a valid name");
        AgeRule = this.ValidationRule(
            viewModel => viewModel.Age,
            age => age >= 13 && age <= 100,
            age => $"{age} is a silly age");
        var nameAndAgeValid = this
            .WhenAnyValue(x => x.Age, x => x.Name, (age, name) => new { Age = age, Name = name })
            .Select(x => x.Age > 10 && !string.IsNullOrEmpty(x.Name));
        ComplexRule = this.ValidationRule(
            _ => nameAndAgeValid,
            (vm, state) => !state ? "That's a ridiculous name / age combination" : string.Empty);
        var canSave = this.IsValid();
        Save = ReactiveCommand.CreateFromTask(async unit => { }, canSave);
    }
}
public class SampleViewModel : ReactiveValidationObject<SampleViewModel>
{
    [Reactive]
    public string Name { get; set; } = string.Empty;
    public SampleViewModel()
    {
        this.ValidationRule(
            x => x.Name, 
            name => !string.IsNullOrWhiteSpace(name),
            "Name shouldn't be empty.");
    }
}

Log

输出日志需要另外下载日志专用的包
比如使用 Serilog 将日志输出到文件需要下载以下几个包

  • Serilog
  • Splat.Serilog
  • Serilog.Sinks.File

在使用日志之前需要先创建配置并注册 Logger

using Serilog;
using Splat;
using Splat.Serilog;
using System.Windows;
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        // 创建 Logger
        Log.Logger = new LoggerConfiguration()
            .WriteTo.File("log-.txt", rollingInterval: RollingInterval.Day)
            .CreateLogger();
        // 注册 Logger
        Locator.CurrentMutable.UseSerilogFullLogger();
    }
}

使用 Logger

using Splat;
using System.Windows;
// 输出日志的类需要实现 IEnableLogger 接口
public partial class MainWindow : Window, IEnableLogger
{
    public MainWindow()
    {
        InitializeComponent();
        // 使用 Logger
        this.Log().Info("MainWindow Initialized.");
    }
}

实际输出的日志

2020-06-23 18:47:45.365 +00:00 [INF] MainWindow Initialized.

posted on 2020-03-12 13:13  zwvista  阅读(6611)  评论(0编辑  收藏  举报