воскресенье, 29 марта 2009 г.

Behaviors: Как это работает?

В прошлом посте я рассказал о том, что такое поведения, и показал простенький пример, реализующий поведение "перетягивабельности" UIElement'a. Здесь же мы посмотрим, что заставляет поведения работать. Для того, чтобы вы комфортно себя чувствовали при чтении поста, от вас, дорогой читатель, ожидается понимание модели свойств, представленной в WPF/SL: Attached Properties и Dependency Properties. Если же для вас эти названия в новинку - не отчаивайтесь, в скором времени мы пройдемся и по этим товарищам, разметая туман загадочности метлой желания и разума :).

Поехали!


Давайте посмотрим, на пример поведения, из предыдущего сообщения:

<Rectangle Fill="Green" Width="40" Height="40" x:Name="rct">
 <i:Interaction.Behaviors>
  <local:DragBehavior/>
 </i:Interaction.Behaviors>
</Rectangle>
Класс Interaction определяет присоединенное свойство (Attached Property) типа BehaviorCollection:

BehaviorsProperty = DependencyProperty.RegisterAttached(
 "Behaviors",
 typeof (BehaviorCollection),
 typeof (Interaction),
 new PropertyMetadata(new PropertyChangedCallback(Interaction.OnBehaviorsChanged))
 );    
Когда мы прикрепляем к некоторому объекту это свойство, в рамках нашего класса-сервиса Interaction, вызывается метод OnBehaviorsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args). Первым параметром этого метода будет объект, к которому мы присоединили attached property, а во втором мы найдем список определенных поведений. (В нашем примере: obj == прямоугольник rct, а args.NewValue == коллекция, с одним элементом DragBehavior). Имея эти два козыря на руках, класс Interaction, в методе OnBehaviorsChanged() проходится по коллекции поведений, и у каждого из поведений вызывает метод Attach(), передавая объект, к которому был присоединен attached property:

//... Где-то в OnBehaviorsChanged()    
foreach (Behavior behavior in behaviorsCollection)
{
    behavior.Attach(obj); 
}
Дальше остается дело за малым: базовый класс Behavior устанавливает свое свойство AssociatedObject в переданный ему obj, и вызывает виртуальный метод OnAttached(), чтобы известить своих наследников о произошедшем соединении :). Именно этот метод мы использовали в примере из прошлого поста, чтобы подписаться на события от мышки.

public class DragBehavior : Behavior<UIElement>
{
 protected override void OnAttached()
 {
  AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
  AssociatedObject.MouseMove += AssociatedObject_MouseMove;
  AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
 }

 // ...
}

Последний штрих: мы не можем напрямую унаследоваться от  класса Behavior (у него internal конструктор). Вместо этого нам предлагают воспользоваться обобщенным классом Behavior<T>, где T - DependencyObject.

Почему на T накладывается ограничение - быть DependencyObject'ом понятно: attached property можно прикрепить только к DependencyObject'у. Но почему же мы не можем напрямую унаследоваться от Behavior? Зачем делать конструктор внутренним? - Для нашего же блага :).

Например, мы захотим написать поведение применимое только к панелям. Вместо того, чтобы ходить каждый раз после крэша в Graphic Design Department, и объяснять дизайнерам: "Пожалуйста, не перетаскивайте поведение с названием OnlyForPanelsBehavior, на кнопки - это сломает систему", мы просто наследуемся от Behavior<Panel>, и Blend не даст перетянуть поведение на кого-то, отличного от Panel'a :). Удобно, не так ли?

Что дальше?


Айда писать классные, потрясающие и просто захватывающие поведения :)! Поделиться своим произведением всегда можно на сайте Microsoft Expression Gallery.

К сожалению, мой обзор поведений остается неполным без Trigger'ов и Action'ов. В ближайшем будущем (на следующей неделе) я собираюсь избавиться от этого пробела :).

Отличного программирования, друзья!

1 комментарий: