Как использовать при активации со свойствами в avalonia

#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 или a IObservable , и что привязки должны вести себя по — разному и отслеживать подписки 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} и все привязки в этом файле компилируются. Это потрясающе.