Пожалуй, любой разработчик 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. И это, разумеется, нормально.
Автор: AndyD