(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Windows 8.1: Behaviors SDK - Part #2

(2 votes)
Andrea Boschin
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
0 comments   /   posted on Oct 07, 2013
Categories:   Windows 8


After the last article where I spoken about the use of behaviors in Windows 8.1, it is time now to go deep inside the SDK and try to create our own behaviors. The SDK in Windows 8.1 includes a number of predefined behaviors that out-of-the box cover a number of cases, both with different activations (event driven and data driven) and with a number of actions, from the simplest property change to the manipulation of states and the invocation of commands. Connecting together predefined activation behaviors and actions suffice for most of the problems you may meet during development, but there are cases when only a specifically-tailored logic can solve an hassle. Here is the need of creating some behaviors and actions with our hands.

Actions or Behaviors?

Once you added the references to the behavior SDK, as I shown in the previous number, you have in your hand not only the existing behaviors but also the toolset you need to create your own. The first is to correctly decide how to choose between behaviors and actions.

For a correct choice you have to carefully watch at the problem you have to solve. It is not true that behaviors are made only to activate actions, but it is for sure that actions need a behavior to be applied to an element. Infact, since an action strongly requires that someone else activate its logic, the behaviors are directly connected to an element in the Visual Tree and can both attach event and properties and manipulate them.

Generally speaking, you should fall to create Actions when the predefined trigger behaviors suffice to activate your logic. So if you can rely on EventTriggerBehavior or on DataTriggerBehavior you can for sure implement and action and leave out of your mind the activation problem.

Differently, if you can go with predefined actions but you are unable to start them with one of the predefined triggers then you have to implement a behavior. This let you too hook up the control and then requires to scan actions and execute them.

Finally, if you need complex logic that require to hook up multiple events and properties, both the get/set states and information, then the better is to use a behavior and forget actions.

Create a simple trigger behavior

Let say we need to implement a new activation trigger. It's hard to figure out what you may need that does not fall into handling events and data changes. The only thing I have imagined is to handle the time dimension. So, for this example I'll show how to create a behavior able to trigger actions on the basis of a periodic timeout, a sort of snooze alarm, that will awake something in our user interface.

Implementing a behavior in Windows 8.1 do not mean extending an abstract class, like it was in Silverlight, but it requires you to implement a simple interface. This interface called IBehavior - it resides under the Microsoft.Xaml.Interactivity namespace - is a lightweight version of the Behavior<T> class that we used in Silverlight, but it has a drawback. It directly applies to every DependencyObject without any distinction, so we cannot apply limitations on the basis of the target type. this makes programming slightly more difficult because you have to double check that the associated object can be manipulated by your logic. For this purpose we can create our own abstraction:

   1: public abstract class Behavior<T> : DependencyObject, IBehavior
   2:      where T : DependencyObject
   3: {
   4:      public T AssociatedObject { get; private set; }
   6:      protected abstract void OnAttached();
   8:      protected abstract void OnDetached();
  10:      DependencyObject IBehavior.AssociatedObject
  11:      {
  12:          get { return this.AssociatedObject; }
  13:      }
  15:      void IBehavior.Attach(DependencyObject associatedObject)
  16:      {
  17:          if (associatedObject != null &&
  18:              !associatedObject.GetType().GetTypeInfo().IsSubclassOf(typeof(T)))
  19:              throw new Exception("Invalid target type");
  21:          this.AssociatedObject = associatedObject as T;
  22:          this.OnAttached();
  23:      }
  25:      void IBehavior.Detach()
  26:      {
  27:          this.OnDetached();
  28:          this.AssociatedObject = null;
  29:      }
  30: }

The class explicitely implements the IBehavior interface, so I'm able to override the AssociatedObject property and publish it to inherited classes. This property is strongly typed using the generic type T and this make the work much more easy. In the Attach methos then I check that the incoming object is compatible with T and in case I throw and exception.

To create a trigger we need to collect some actions and then raise the execution when it is required. It is another point we can handle with a base class. I will call it Trigger<T>:

   1: [ContentProperty(Name = "Actions")]
   2: public abstract class Trigger<T> : Behavior<T>
   3:     where T : DependencyObject
   4: {
   5:     public Trigger()
   6:     {
   7:         this.Actions = new ActionCollection();
   8:     }
  10:     public ActionCollection Actions
  11:     {
  12:         get;
  13:         private set;
  14:     }
  16:     public void Execute(object sender, object parameter)
  17:     {
  18:         foreach (IAction item in this.Actions.OfType<IAction>())
  19:         {
  20:             item.Execute(sender, parameter);
  21:         }
  22:     }
  23: }

The Trigger class exposes an Actions property. This is used to host child actions. Blend will recognize it and the designer should show the hierarchy correctly. The "ContentProperty" attribute on the class is used to allow direct content in the XAML to be mapped to this property. Finally the "Execute method crawls the child actions and calls tha Execute method of the IAction interface. Now the implementation is much more easy:

   1: public class TimeTrigger : Trigger<Shape>
   2: {
   3:     private DateTime PreviousTime { get; set; }
   4:     private Timer Timer { get; set; }
   6:     public int Interval
   7:     {
   8:         get; set;
   9:     }
  11:     protected override void OnAttached()
  12:     {
  13:         this.PreviousTime = DateTime.Now;
  14:         this.Timer = new Timer(OnTick, null, 0, 1000);
  15:     }
  17:     protected override void OnDetached()
  18:     {
  19:         // does nothing...
  20:     }
  22:     private void OnTick(object state)
  23:     {
  24:         DateTime now = DateTime.Now;
  26:         if (now.Subtract(this.PreviousTime).TotalSeconds >= this.Interval)
  27:         {
  28:             this.PreviousTime = now;
  30: #pragma warning disable 4014
  32:             this.AssociatedObject.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
  33:                 () =>
  34:                 {
  35:                     base.Execute(this.AssociatedObject, now);
  36:                 });
  38: #pragma warning restore 4014
  39:         }
  40:     }
  41: }

The TimeTrigger class uses a Timer to raise an event every second. Then it check the time passed after the previous event and when it is greater than the specified interval it raises the actions. Take note that the "#pragma disable/restore" is used to prevent a warning that is raised because the RunAsync method of the dispatcher is asynchronous and we do not use the "async/await" pattern. It is not so important to await the execution here so I simply call it and forget. The call is marshaled to the UI thread just because the Timer represents a different thread and we need to avoid cross threading issues.

To test this trigger you can use a ChangePropertyAction ann you should see the change happening after the interval is passed. Blend should show the new behavior in its assets panel, together with core behaviors.

Camera... Action!

The implementation of a trigger behavior is slightly complex if you start from the IBehavior interface, but once you created your infrastructure classes it becomes rally easy. For actions instead, there is not a good reaso to bouild some infrastructure over the IAction interface. The interface has a single method and it goes directly to the implementation.

Let see a simple action. When our time trigger raises the action I would like to randomly change the color of the shape. So I create a GenerateRandomColor action:

   1: public class GenerateRandomColor : DependencyObject, IAction
   2: {
   3:   private Random generator = new Random(DateTime.Now.Millisecond);
   4:   private List<Color> colors = new List<Color>();
   6:   public GenerateRandomColor()
   7:   {
   8:       Type type = typeof(Colors);
  10:       foreach (var property in type.GetRuntimeProperties())
  11:       {
  12:           if (property.PropertyType == typeof(Color))
  13:               colors.Add((Color)property.GetValue(null));
  14:       }
  15:   }
  17:   public static readonly DependencyProperty PropertyNameProperty =
  18:          DependencyProperty.Register("PropertyName", typeof(string), typeof(GenerateRandomColor), new PropertyMetadata(null));
  21:   public string PropertyName
  22:   {
  23:       get { return (string)GetValue(PropertyNameProperty); }
  24:       set { SetValue(PropertyNameProperty, value); }
  25:   }
  27:   public object Execute(object sender, object parameter)
  28:   {
  29:       FrameworkElement element = sender as FrameworkElement;
  31:       if (element != null & !string.IsNullOrEmpty(this.PropertyName))
  32:       {
  33:           PropertyInfo property = element.GetType().GetRuntimeProperty(this.PropertyName);
  35:           if (property != null && property.PropertyType == typeof(Brush))
  36:           {
  37:               property.SetValue(element, this.GetRandom());
  38:           }
  39:       }
  41:       return null;
  42:   }
  44:   private Brush GetRandom()
  45:   {
  46:       int random = generator.Next(0, 1000) % this.colors.Count;
  47:       return new SolidColorBrush(this.colors[random]);
  48:   }
  49: }

Into the Action, in the ctor I scan the Colors class to create a palette. This copies the default system colors to a list to have a cout of them an a index to retrieve the instance. When the action is called, a number is generated in the range from 0 and the max number of colors in the array. The color is picked and it is assigned to the target property. The code uses some reflection to dynamically pick up the target property and made the assignment. Nothing else.

Now that the action is ready we can use Blend to configure the behavior. Pay attention that you will be required to manually enter the property name. There is not a way to instruct Blend to use the property selector as for ChangePropertyAction. The finally XAML markup should be like this:

   1: <Rectangle Fill="White" Width="100" Height="100">
   2:     <Interactivity:Interaction.Behaviors>
   3:         <code:TimeTrigger Interval="15">
   4:             <code:GenerateRandomColor PropertyName="Fill" />
   5:         </code:TimeTrigger>
   6:     </Interactivity:Interaction.Behaviors>
   7: </Rectangle>

When you run the example, after 15 seconds you will have to see the rectangle change its color randomly. Then again after another 15 seconds and so on.

Why and when?

Behavior are a wonderful tool in your hands but, as it always happens, you have to carefully evaluate when and why to use them. It's definitely wrong to use extensively behaviors, expecially if you do not need to reuse it. Behaviors are tailored to take a recurrent piece of logic, applied to an user interface, and make it reusable across different pages and often across different apps. Another good reason to use behaviors may be to remove parts of codebehind, that may translates values of the view model into operations that cannot be handled by VisualStateManager or Converters. This lead to simplify the application, and mostly to isolate a specific locic that usually become again highly reusable. So please pay always attention on how you use the toolset because a wrong use may be worst of the problem you are trying to address.



No comments

Add Comment

Login to comment:
  *      *       

From this series