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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM