如何将 WhenActivated 与 avalonia 中的属性一起使用
How to use WhenActivated with properties in avalonia
我正在尝试将 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 为 null)。
我认为没有简单的解决方法需要大量 hack、手动提升属性等。
所以问题是:
如何在 Avalonia 中使用 OAPH 和 WhenActivated?
选项 #1
可让您解决问题的最明显模式是使用空合并运算符。通过使用此运算符,您可以通过将代码调整为如下所示来实现所需的行为:
private ObservableAsPropertyHelper<TValue>? _myProperty;
public TValue MyProperty => _myProperty?.Value;
在这里,我们使用新的 C# 可空注释将声明的字段显式标记为可空。我们这样做是因为在 WhenActivated
块被调用之前,_myProperty
字段被设置为 null
。另外,我们在这里使用 _myProperty?.Value
语法,因为 MyProperty
getter 在未初始化视图模型时应该 return null
。
选项 #2
另一个 绝对 更好的选择是将 ToProperty
订阅移到 WhenActivated
块之外,并将 ObservableAsPropertyHelper<T>
字段标记为readonly
。如果您的计算 属性 不订阅 到 比视图模型 长寿的外部服务,那么您 不不需要 处理 return 由 ToProperty
编辑的订阅。在 90% 的情况下,您不需要将 ToProperty
调用保留在 WhenActivated
内。请参阅 When should I bother disposing of IDisposable objects? documentation page for more info. See also the 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);
}
}
此外,如果 RaiseAndSetIfChanged
语法对您来说太冗长,请查看 ReactiveUI.Fody。
选项#3(我推荐这个选项)
值得注意的是 Avalonia 支持 binding to Tasks and 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
或者,如果您更喜欢使用 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" />
我们现在有一个 source generator 可能有助于生成 x:Name
引用 btw。
我正在尝试将 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 为 null)。
我认为没有简单的解决方法需要大量 hack、手动提升属性等。
所以问题是:
如何在 Avalonia 中使用 OAPH 和 WhenActivated?
选项 #1
可让您解决问题的最明显模式是使用空合并运算符。通过使用此运算符,您可以通过将代码调整为如下所示来实现所需的行为:
private ObservableAsPropertyHelper<TValue>? _myProperty;
public TValue MyProperty => _myProperty?.Value;
在这里,我们使用新的 C# 可空注释将声明的字段显式标记为可空。我们这样做是因为在 WhenActivated
块被调用之前,_myProperty
字段被设置为 null
。另外,我们在这里使用 _myProperty?.Value
语法,因为 MyProperty
getter 在未初始化视图模型时应该 return null
。
选项 #2
另一个 绝对 更好的选择是将 ToProperty
订阅移到 WhenActivated
块之外,并将 ObservableAsPropertyHelper<T>
字段标记为readonly
。如果您的计算 属性 不订阅 到 比视图模型 长寿的外部服务,那么您 不不需要 处理 return 由 ToProperty
编辑的订阅。在 90% 的情况下,您不需要将 ToProperty
调用保留在 WhenActivated
内。请参阅 When should I bother disposing of IDisposable objects? documentation page for more info. See also the 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);
}
}
此外,如果 RaiseAndSetIfChanged
语法对您来说太冗长,请查看 ReactiveUI.Fody。
选项#3(我推荐这个选项)
值得注意的是 Avalonia 支持 binding to Tasks and 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
或者,如果您更喜欢使用 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" />
我们现在有一个 source generator 可能有助于生成 x:Name
引用 btw。