• 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.
    
  • 相关阅读:
    std::exception标准和各平台实现的不同
    学习Linux必备的硬件基础一网打尽
    Git安装及SSH Key管理之Windows篇
    简要介绍一下Dos/Windows格式文件和Unix/Linux格式文件(剪不断理还乱的 和 )
    C/C++中的序列点
    STL容器之vector 的下标越界是否报错
    二维数组与指针的联系与区别
    C/C++ strlen函数为什么不能传入空指针NULL?
    棋盘游戏
    Knight Moves
  • 原文地址:https://www.cnblogs.com/zwvista/p/12468579.html
Copyright © 2020-2023  润新知