Pull to refresh

WPF: использование Attached Property и Behavior

Reading time 3 min
Views 42K
Пожалуй, любой разработчик WPF знает о механизме Attached Property, но многие даже не слышали о Behavior. Хотя эти механизмы и имеют схожие функциональные возможности, они, все же, имеют совершенно разную смысловую нагрузку, и очень важно правильно различать их и использовать.

Давайте вспомним, что из себя представляют эти механизмы:

Attached Property. Это Dependency Property, которое объявлено не в классе объекта, для которого оно будет использоваться, но ведет себя, как будто является его частью.

Объявляется в отдельном классе, имеет getter и setter в виде статических методов. Можно добавить обработчик на PropertyChanged событие.

public static class UiConfigurator
{
    public static readonly DependencyProperty CustomValueProperty = DependencyProperty.RegisterAttached(
        "CustomValue", typeof(bool), typeof(UiConfigurator), new PropertyMetadata(false));

    public static void SetCustomValue(DependencyObject element, bool value)
    {
        element.SetValue(CustomValueProperty, value);
    }

    public static bool GetCustomValue(DependencyObject element)
    {
        return (bool)element.GetValue(CustomValueProperty);
    }
}

Используется так же как и обычное Dependency Property, только необходимо указать класс, в котором оно определено.

<Button testApp:UiConfigurator.CustomValue="True"/>

Благодаря обработчику PropertyChanged очень часто с помощью этого механизма пытаются добавлять к UI элементу некоторую функциональность. Например, мы хотим запоминать расположение и размер окна между запусками приложения. Делаем Attached Property SaveBounds и добавляем обработчик PropertyChanged. Если установленно в true, то выполняем код по восстановлению/сохранению позиции окна. Правильно ли это? Нет. Давайте посмотрим как Microsoft использует этот механизм у себя. Отличными примерами являются Grid.Column и DockPanel.Dock. Все эти свойства никак не влияют на функциональность объекта, а просто добавляют некоторую информацию, чтобы другие участники дерева отображения смогли более корректно с ним взаимодействовать. Сам же объект об этом ничего не знает и знать не должен. Отсюда следует, что само по себе использование события PropertyChanged для Attached Property уже повод задуматься: а все ли я правильно делаю? Он необходим только в очень редких случаях. Например, так мы может запоминать историю изменения этого свойства, чтобы при необходимости провести более глубокий анализ и взаимодействовать более интеллектуально. Но это из разряда задач “делал один раз в жизни”.

Так как Attached Property не влияет на состояние объекта, то и несколько таких свойств конфликтовать не должны, а значит мы можем навешивать их на него сколько угодно. Например, те же Grid.Column и DockPanel.Dock (хоть это и несколько нелогично) можно легко сочетать. Вот вам и вторая подсказка правильно ли вы используете этот механизм: если вы можете представить код, который может быть написан в целевом объекте или где-либо еще и который будет конфликтовать с вашим свойством – вы что-то сделали не так.

Behavior. В WPF есть абстрактный шаблонный класс Behavior. Сделав наследника, мы можем присоединить его к объекту в дереве отображения. В этом классе есть методы OnAttached и OnDetaching, которые вызовутся в соответствующих случаях.
Ниже пример CloseBehavior, который можно присоединить любой кнопке и сделать ее таким образом кнопкой закрытия приложения.

public class CloseBehavior : Behavior<Button>
{
    protected override void OnAttached()
    {
        AssociatedObject.Click += OnClick;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Click -= OnClick;
    }

    private void OnClick(object sender, RoutedEventArgs e)
    {
        Application.Current.Shutdown();
    }
}

Добавляется следующим кодом:

<Button Content="Close">
    <i:Interaction.Behaviors>
        <testApp:CloseBehavior/>
    </i:Interaction.Behaviors>
 </Button>

Вот Behavior уже в отличии от Attached Property служит для добавления функциональных возможностей UI элементам. В него можно добавлять любые Dependency Property, и, так как он является частью дерева отображения, Binding тут отлично работает. Так как для WPF принято использовать MVVM, а он, в свою очередь, предполагает минимум кода в теле View, то Behavior тут как нельзя кстати. Эти немногочисленные функциональные блоки, которые нельзя выполнить при помощи стандартного Binding, можно выносить и использовать повсеместно комбинирую друг с другом.

Не все объекты Behavior должны быть совместимы друг с другом. Например, мы не сможем совместить CloseButtonBehavior и HelpButtonBehavior. И это, разумеется, нормально.
Tags:
Hubs:
+10
Comments 2
Comments Comments 2

Articles