Sha*_*dow 6 c# reactiveui avaloniaui 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;
}
Run Code Online (Sandbox Code Playgroud)
因为WhenActivated在视图绑定到 viewModel 之后调用(因此 _myProperty 为 null)。
我认为没有简单的解决方法需要大量的修改、手动提高属性等等。
所以问题是:
如何在 Avalonia 中使用 OAPH 和 WhenActivated?
选项1
允许您解决该问题的最明显的模式是使用空合并运算符。通过使用此运算符,您可以通过将代码调整为如下所示来实现所需的行为:
private ObservableAsPropertyHelper<TValue>? _myProperty;
public TValue MyProperty => _myProperty?.Value;
Run Code Online (Sandbox Code Playgroud)
在这里,我们使用新的 C# 可空注释将声明的字段显式标记为可空。我们这样做是因为在WhenActivated调用该块之前,该_myProperty字段被设置为null。另外,我们_myProperty?.Value在这里使用语法,因为当视图模型未初始化时MyPropertygetter 应该返回。null
选项#2
另一个肯定更好的选择是将ToProperty订阅移到块之外WhenActivated并将该ObservableAsPropertyHelper<T>字段标记为readonly。如果您的计算属性不订阅比视图模型寿命更长的外部服务,那么您不需要处理ToProperty. 在 90% 的情况下,您不需要将ToProperty呼叫保留在内部WhenActivated。请参阅何时应该处理 IDisposable 对象?文档页面以获取更多信息。另请参阅《热和冷可观察量》文章,该文章也可以对这个主题进行一些阐述。因此,在 90% 的情况下,编写这样的代码是一个好方法:
private readonly ObservableAsPropertyHelper<TValue> _myProperty;
public TValue MyProperty => _myProperty.Value;
// In the view model constructor:
_myProperty = obs.ToProperty(this, x => x.MyProperty);
Run Code Online (Sandbox Code Playgroud)
如果您实际上订阅外部服务,例如通过构造函数注入视图模型,那么您可以MyProperty使用私有设置器转换为读写属性,并编写以下代码:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
另外,如果您觉得语法太冗长,请查看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
}
Run Code Online (Sandbox Code Playgroud)
并在视图中编写以下代码:
<TextBlock Text="{Binding MyProperty^}"/>
Run Code Online (Sandbox Code Playgroud)
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);
});
Run Code Online (Sandbox Code Playgroud)
这里我们假设该xaml文件如下所示:
<TextBlock x:Name="NamedTextBox" />
Run Code Online (Sandbox Code Playgroud)
我们现在有了一个源生成器,它可能有助于生成x:Name引用。