Документация по Avalonia UI
< Все темы
Печать

Создание и привязка присоединенных свойств

Когда вам нужно больше или, скажем, чужие свойства для элементов avalonia, тогда правильно использовать прикрепленные свойства. Их также можно использовать для создания так называемых поведений для общего изменения размещенных компонентов графического интерфейса. Это можно использовать, например, для привязки команды к событию.

Здесь мы представляем пример того, как использовать команду совместимым с MVVM способом и привязать ее к событию.

Возможно, это не идеальное решение, поскольку есть такие проекты, как Avalonia Behaviors, где это сделано должным образом. Но это иллюстрирует следующие два урока:

  • Как создать прикрепленные свойства в AvaloniaUI.
  • Как использовать их в MVVM.

Сначала мы должны создать наше присоединенное свойство. Для этого используется метод AvaloniaProperty.RegisterAttached. Обратите внимание, что общедоступное статическое свойство CLR для присоединенного свойства по соглашению называется XxxxProperty. Также обратите внимание, что по соглашению имя (параметр) присоединяемого свойства — Xxxx без свойства. И, наконец, обратите внимание, что по соглашению необходимо предоставить два общедоступных статических метода с именами SetXxxx (элемент, значение) и GetXxxx (элемент).

Этот вызов гарантирует, что у свойства есть тип, тип владельца и  где его можно использовать.

Метод проверки можно использовать для очистки устанавливаемого значения. Либо возвращая исправленное значение, либо отказываясь от процесса, возвращая AvaloniaProperty.UnsetValue. Или можно выполнять специальные задачи с элементом, на котором размещено свойство. Методы получения и установки всегда должны просто устанавливать значение и никогда не делать ничего сверх этого. На самом деле они обычно никогда не вызываются, поскольку система привязки распознает соглашение и устанавливает свойства непосредственно там, где они хранятся.

В этом примере мы создаем два прикрепленных свойства, которые взаимодействуют друг с другом: свойство Command и CommandParameter, который используется при вызове команды.

/// <summary>
/// Container class for attached properties. Must inherit from <see cref="AvaloniaObject"/>.
/// </summary>
public class DoubleTappedBehav : AvaloniaObject
{
    static DoubleTappedBehav()
    {
        CommandProperty.Changed.Subscribe(x => HandleCommandChanged(x.Sender, x.NewValue.GetValueOrDefault<ICommand>()));
    }

    /// <summary>
    /// Identifies the <seealso cref="CommandProperty"/> avalonia attached property.
    /// </summary>
    /// <value>Provide an <see cref="ICommand"/> derived object or binding.</value>
    public static readonly AttachedProperty<ICommand> CommandProperty = AvaloniaProperty.RegisterAttached<DoubleTappedBehav, Interactive, ICommand>(
        "Command", default(ICommand), false, BindingMode.OneTime);

    /// <summary>
    /// Identifies the <seealso cref="CommandParameterProperty"/> avalonia attached property.
    /// Use this as the parameter for the <see cref="CommandProperty"/>.
    /// </summary>
    /// <value>Any value of type <see cref="object"/>.</value>
    public static readonly AttachedProperty<object> CommandParameterProperty = AvaloniaProperty.RegisterAttached<DoubleTappedBehav, Interactive, object>(
        "CommandParameter", default(object), false, BindingMode.OneWay, null);


    /// <summary>
    /// <see cref="CommandProperty"/> changed event handler.
    /// </summary>
    private static void HandleCommandChanged(IAvaloniaObject element, ICommand commandValue)
    {
        if (element is Interactive interactElem)
        {
            if (commandValue != null)
            {
                // Add non-null value
                interactElem.AddHandler(InputElement.DoubleTappedEvent, Handler);
            }
            else
            {
                // remove prev value
                interactElem.RemoveHandler(InputElement.DoubleTappedEvent, Handler);
            }
        }

        // local handler fcn
        void Handler(object s, RoutedEventArgs e)
        {
            // This is how we get the parameter off of the gui element.
            object commandParameter = interactElem.GetValue(CommandParameterProperty);
            if (commandValue?.CanExecute(commandParameter) == true)
            {
                commandValue.Execute(commandParameter);
            }
        }
    }


    /// <summary>
    /// Accessor for Attached property <see cref="CommandProperty"/>.
    /// </summary>
    public static void SetCommand(AvaloniaObject element, ICommand commandValue)
    {
        element.SetValue(CommandProperty, commandValue);
    }

    /// <summary>
    /// Accessor for Attached property <see cref="CommandProperty"/>.
    /// </summary>
    public static ICommand GetCommand(AvaloniaObject element)
    {
        return element.GetValue(CommandProperty);
    }

    /// <summary>
    /// Accessor for Attached property <see cref="CommandParameterProperty"/>.
    /// </summary>
    public static void SetCommandParameter(AvaloniaObject element, object parameter)
    {
        element.SetValue(CommandParameterProperty, parameter);
    }

    /// <summary>
    /// Accessor for Attached property <see cref="CommandParameterProperty"/>.
    /// </summary>
    public static object GetCommandParameter(AvaloniaObject element)
    {
        return element.GetValue(CommandParameterProperty);
    }
}

В методе проверки мы используем систему маршрутизируемых событий для присоединения нового обработчика. Обратите внимание, что обработчик снова должен быть отсоединен. Значение свойства запрашивается обычными программными механизмами с использованием метода GetValue().

В этом примере пользовательского интерфейса показано, как использовать присоединенное свойство. После того как пространство имен станет известно компилятору XAML, его можно использовать, уточнив его точкой. Затем можно использовать привязки.

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:loc="clr-namespace:MyApp.Behaviors"
             x:Class="MyApp.Views.TestView">
    <ListBox Items="{Binding Accounts}"
             SelectedIndex="{Binding SelectedAccountIdx, Mode=TwoWay}"
             loc:DoubleTappedBehav.Command="{Binding EditCommand}"
             loc:DoubleTappedBehav.CommandParameter="test77"
             >
      <ListBox.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding }" />          
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
</UserControl>

Хотя CommandParameter использует только статическое значение, его также можно использовать с привязкой. При использовании с этой моделью представления EditCommandExecuted будет запускаться после двойного щелчка.

public class TestViewModel : ReactiveObject
{
    public ObservableCollection<Profile> Accounts { get; } = new ObservableCollection<Profile>();

    public ReactiveCommand<object, Unit> EditCommand { get; set; }

    public TestViewModel()
    {
        EditCommand = ReactiveCommand.CreateFromTask<object, Unit>(EditProfileExecuted);
    }

    private async Task<Unit> EditCommandExecuted(object p)
    {
        // p contains "test77"

        return Unit.Default;
    }
}

 

Оглавление