#c# #reactiveui #avaloniaui #avalonia
#c# #реактивныйи #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;
}
Потому WhenActivated
что вызывается после привязки view к ViewModel (следовательно, _myProperty равно null).
Я не вижу простого обходного пути, требующего большого количества взломов, ручного повышения свойств и так далее.
Итак, вопрос заключается в следующем:
Как работать с OAPH и при активации в Avalonia?
Ответ №1:
Вариант #1
Наиболее очевидный шаблон, позволяющий решить проблему, — это использовать оператор объединения null. Используя этот оператор, вы можете добиться желаемого поведения, настроив код так, чтобы он выглядел примерно так:
private ObservableAsPropertyHelper<TValue>? _myProperty;
public TValue MyProperty => _myProperty?.Value;
Здесь мы явно помечаем объявленное поле как обнуляемое, используя новые аннотации C # nullable. Мы делаем это потому, что до тех пор, пока WhenActivated
блок не будет вызван, _myProperty
поле будет иметь значение null
. Кроме того, здесь мы используем _myProperty?.Value
синтаксис, поскольку MyProperty
getter должен возвращаться null
, когда модель представления не инициализирована.
Вариант #2
Другой вариант, который определенно лучше, — переместить ToProperty
подписку за пределы WhenActivated
блока и пометить ObservableAsPropertyHelper<T>
поле как readonly
. Если ваше вычисляемое свойство не подписывается на внешние службы, которые переживают модель представления, то вам не нужно утилизировать подписку, возвращенную by 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);
Если вы на самом деле подписываетесь на внешние сервисы, например, вводимые в модель представления с помощью конструктора, то вы можете преобразовать 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);
}
}
Кроме того, взгляните на ReactiveUI.Извините, если RaiseAndSetIfChanged
синтаксис кажется вам слишком многословным.
Вариант №3 (я бы рекомендовал этот вариант)
Стоит отметить, что Avalonia поддерживает привязку к задачам и наблюдаемым. Это очень полезная функция, которую я бы настоятельно рекомендовал вам опробовать. Это означает, что в Avalonia вы можете просто объявить вычисляемое свойство as 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 довольно хороша в умных расширениях разметки. Так что, если вы ориентируетесь на несколько фреймворков пользовательского интерфейса и пишете модели представления, не зависящие от фреймворка, тогда OAPH хороши. Но если вы нацелены только на Авалонию, тогда просто используйте {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
ссылок, кстати.
Комментарии:
1. Спасибо за подробный ответ! Вариант №1 ужасен, сводит на нет всю цель делать веселые вещи. Вариант № 2 немного бесполезен, так как все дело во внешних зависимостях. Вариант № 3 — это потрясающе, но почему я должен его использовать
^
? Это выглядит странно.2. Знак ^ — это просто синтаксис расширения разметки Avalonia, который позволяет привязывать элементы управления к задачам и наблюдаемым. Это позволяет нам сообщить Avalonia , что значение , к которому мы привязываемся , равно a
Task
или aIObservable
, и что привязки должны вести себя по — разному и отслеживать подписки avaloniaui.net/docs/binding/binding-to-tasks-and-observables Они называют это «оператором привязки потока».3. Да, но разве это не может быть поведением по умолчанию? Если что-то является наблюдаемым, оно должно быть автоматически подписано, это ^ возможно, можно было бы использовать, если бы нужно было передать необработанное наблюдаемое для просмотра.
4. Я думаю, стоит открыть проблему в репозитории Avalonia github.com/avaloniaui/avalonia или начать обсуждение в их чате Gitter gitter.im/AvaloniaUI/Avalonia если вы хотите узнать больше об этой теме. Вероятно, они предпочитают делать привязки к задачам и наблюдаемым более очевидными и строгими, или это работает так из-за некоторых других ограничений внутренних компонентов Avalonia. Я слышал, что основная команда планирует поддержать компиляцию привязок, кстати.
5. Я уже использую скомпилированные привязки. Вы просто добавляете
x:CompileBindings=true
,x:DataType={local:MyViewModel}
и все привязки в этом файле компилируются. Это потрясающе.