1

我正在尝试将 ReactiveUI 与 Avalonia 一起使用。由于 Avalonia 0.10 预览版中的初始化顺序,以下代码失败:

class ViewModel : IActivatableViewModel
{
    public ViewModel(){
        this.WhenActivated(disposables => {
            _myProperty = observable.ToProperty(this, nameof(MyProperty)).DisposeWith(disposables).
        });
    }

    private ObservableAsPropertyHelper<object> _myProperty = null!;
    public object MyProperty => _myProperty.Value;
}

因为WhenActivated在视图绑定到 viewModel 后调用(因此 _myProperty 为空)。

我认为没有简单的解决方法需要大量的技巧、手动提升属性等等。

所以问题是:

如何在 Avalonia 中使用 OAPH 和 WhenActivated?

4

1 回答 1

2

选项1

允许您解决问题的最明显的模式是使用空合并运算符。通过使用此运算符,您可以通过将代码调整为如下所示来实现所需的行为:

private ObservableAsPropertyHelper<TValue>? _myProperty;
public TValue MyProperty => _myProperty?.Value;

在这里,我们使用新的 C# 可空注释将声明的字段显式标记为可空。我们这样做是因为在WhenActivated调用块之前,该_myProperty字段设置为null。此外,我们在_myProperty?.Value这里使用语法,因为当视图模型未初始化时, MyPropertygetter 应该返回。null

选项#2

另一个绝对更好的选择是将ToProperty订阅移出WhenActivated块并将ObservableAsPropertyHelper<T>字段标记为readonly. 如果您的计算属性没有订阅比视图模型寿命更长的外部服务,那么您不需要处理ToProperty. 在 90% 的情况下,您不需要将ToProperty呼叫保留在WhenActivated. 请参阅我应该何时处理 IDisposable 对象?文档页面了解更多信息。另请参阅Hot and Cold observables文章,该文章也可以阐明该主题。因此,在 90% 的情况下编写这样的代码是一个好方法:

private readonly ObservableAsPropertyHelper<TValue> _myProperty;
public TValue MyProperty => _myProperty.Value;

// In the view model constructor:
_myProperty = obs.ToProperty(this, x => x.MyProperty);

如果您实际上订阅了外部服务,例如通过构造函数注入到视图模型中,那么您可以MyProperty使用私有 setter 转换为读写属性,并编写以下代码:

class ViewModel : IActivatableViewModel
{
    public ViewModel(IDependency dependency)
    {
        this.WhenActivated(disposables =>
        {
            // We are using 'DisposeWith' here as we are
            // subscribing to an external dependency that
            // could potentially outlive the view model. So
            // we need to dispose the subscription in order
            // to avoid the potential for a memory leak. 
            dependency
                .ExternalHotObservable
                .Subscribe(value => MyProperty = value)
                .DisposeWith(disposables);
        });
    }

    private TValue _myProperty;
    public TValue MyProperty 
    {
        get => _myProperty;
        private set => this.RaiseAndSetIfChanged(ref _myProperty, value);
    }
}

此外,如果您觉得语法过于冗长,请查看ReactiveUI.Fody 。RaiseAndSetIfChanged

选项#3(我推荐这个选项)

值得注意的是,Avalonia 支持绑定到 Tasks 和 Observables。这是一个非常有用的功能,我强烈建议您尝试一下。这意味着,在 Avalonia 中,您可以简单地声明一个计算属性,IObservable<TValue>并且 Avalonia 将为您管理订阅的生命周期。所以在视图模型中这样做:

class ViewModel : IActivatableViewModel
{
    public ViewModel()
    {
        MyProperty =
          this.WhenAnyValue(x => x.AnotherProperty)
              .Select(value => $"Hello, {value}!");
    }

    public IObservable<TValue> MyProperty { get; }
    
    // lines omitted for brevity
}

并在视图中编写以下代码:

<TextBlock Text="{Binding MyProperty^}"/>

OAPH 是为无法实现此类技巧的平台而发明的,但 Avalonia 非常擅长巧妙的标记扩展。因此,如果您的目标是多个 UI 框架并编写与框架无关的视图模型,那么 OAPH 是不错的选择。但是,如果您仅针对 Avalonia,则只需使用{Binding ^}.

选项#4

或者,如果您更喜欢使用代码隐藏 ReactiveUI 绑定,请将选项 3 中的视图模型代码与文件中视图侧的以下代码隐藏结合起来xaml.cs

this.WhenActivated(cleanup => {
    this.WhenAnyObservable(x => x.ViewModel.MyProperty)
        .BindTo(this, x => x.NamedTextBox.Text)
        .DisposeWith(cleanup);
});

这里我们假设xaml文件看起来像这样:

<TextBlock x:Name="NamedTextBox" />

我们现在有一个源代码生成器,它可能有助于生成x:Name参考。

于 2020-12-04T18:42:06.743 回答