ReactiveX 学习笔记(31)ReactiveUI 使用笔记
文档
安装
使用 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.