选项 #1
最明显的模式是使用 null 合并运算符来解决此问题。通过使用该运算符,您可以通过将代码调整为以下内容来实现所需的行为:
private ObservableAsPropertyHelper<TValue>? _myProperty;
public TValue MyProperty => _myProperty?.Value;
在这里,我们使用新的C#可空注释,将声明的字段明确标记为可空。我们这样做是因为在调用
WhenActivated
块之前,
_myProperty
字段被设置为
null
。此外,在这里我们使用
_myProperty?.Value
语法,因为当视图模型未初始化时,
MyProperty
getter应返回
null
。
另一种更好的选择是将ToProperty
订阅移到WhenActivated
块之外,并将ObservableAsPropertyHelper<T>
字段标记为readonly
。如果您的计算属性不会订阅超出视图模型生命周期的外部服务,则无需处理由ToProperty
返回的订阅。在90%的情况下,您不需要将ToProperty
调用放在WhenActivated
内部。有关更多信息,请参见何时应该处理IDisposable对象?文档页面。还可以查看热和冷的可观察对象文章,这也可能会对此主题有所启示。因此,在90%的情况下,编写如下代码是一个很好的选择:
private readonly ObservableAsPropertyHelper<TValue> _myProperty;
public TValue MyProperty => _myProperty.Value;
_myProperty = obs.ToProperty(this, x => x.MyProperty);
如果您实际上订阅了外部服务,例如通过构造函数注入到视图模型中,则可以将
MyProperty
转换为具有私有setter的可读写属性,并编写以下代码:
class ViewModel : IActivatableViewModel
{
public ViewModel(IDependency dependency)
{
this.WhenActivated(disposables =>
{
dependency
.ExternalHotObservable
.Subscribe(value => MyProperty = value)
.DisposeWith(disposables);
});
}
private TValue _myProperty;
public TValue MyProperty
{
get => _myProperty;
private set => this.RaiseAndSetIfChanged(ref _myProperty, value);
}
}
另外,如果RaiseAndSetIfChanged
语法让你感觉过于冗长,请看一下ReactiveUI.Fody。
选项#3(我建议这个选项)
值得注意的是,Avalonia支持绑定到任务和可观察对象。这是一个非常有用的功能,我强烈推荐您尝试一下。这意味着,在Avalonia中,您可以简单地将计算属性声明为IObservable<TValue>
,而Avalonia会为您管理订阅的生命周期。因此,在视图模型中执行以下操作:
class ViewModel : IActivatableViewModel
{
public ViewModel()
{
MyProperty =
this.WhenAnyValue(x => x.AnotherProperty)
.Select(value => $"Hello, {value}!");
}
public IObservable<TValue> MyProperty { get; }
}
在视图中,编写以下代码:
<TextBlock Text="{Binding MyProperty^}"/>
OAPHs是为不能执行此类技巧的平台而发明的,但Avalonia非常擅长聪明的标记扩展。因此,如果您针对多个UI框架并编写与框架无关的视图模型,则OAPHs可以使用。但如果您仅针对Avalonia,则只需使用{Binding ^}
。
选项#4
或者,如果您喜欢使用 code-behind ReactiveUI bindings,请将选项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
引用。
x:CompileBindings=true
和x:DataType={local:MyViewModel}
,这个文件中的所有绑定都会被编译。这太棒了。 - Shadow