Triggers, Actions, and Behaviors

Ideally, we want as little code as necessary in a view's code-behind. Designers especially don't want to have to write and edit code to perform simple tasks. Instead, they want to define as much of the user interface's behavior as possible using simple drag, drop, and set property operations in the Expression Blend designer, with the behavior defined declaratively in XAML. However, you will often find that you need to turn to the code-behind to implement simple actions. What's more, most of these behaviors tend to be fairly generic, often following a common pattern, and are implemented often.

Take this scenario, for example: let's say you have a Save button, and you want to call the SubmitChanges method on a DomainDataSource control when the button is clicked. As discussed in Chapter 5, you can bind a Button control to a command provided by the DomainDataSource control, so that when the button is clicked the DomainDataSource control will submit any changes back to the server. However, let's look at an alternative approach to implementing this behavior. Let's implement a trigger that will listen for the Button control's Click event, and create an action that will call the DomainDataSource control's SubmitChanges method when the button is clicked.

Using Predefined Triggers and Actions

A trigger waits for something to happen, usually by listening for a control event to be raised, and when that event is raised, it invokes one or more actions that do something in response. For the most part, unlike WPF, Silverlight doesn't have the concept of triggers and actions. The EventTrigger is the only trigger available, and it will respond only to the Loaded event of a control and start an animation, meaning that it has limited use (and any use of it is generally discouraged). Because of this lack of triggers and actions in Silverlight and also the inability to extend WPF's triggers and actions, the Expression Blend team created its own triggers and actions implementation.

Although designed for use with Expression Blend, you can also use any of the predefined triggers and actions from the Expression Blend team's implementation in Visual Studio (but keep in mind that there is no designer support as there is in Expression Blend), and you can also write your own.

Adding the Required Assembly References

To use and create triggers and actions, you will need to start by adding a reference to the System.Windows.Interactivity.dll assembly to your project. You probably will want to also add a reference to the Microsoft.Expression.Interactions.dll assembly, as it provides a number of predefined triggers, actions, and behaviors that you probably will want to use. Note that these assemblies are not installed as part of the Silverlight SDK, but are installed as a part of Expression Blend instead.

images Note If you don't have Expression Blend, you can get this assembly by downloading the Expression Blend SDK for free from the Microsoft web site at www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=75e13d71-7c53-4382-9592-6c07c6a00207.

Depending on whether you have installed it as part of Expression Blend or its SDK, you should find it under the Silverlightv5.0Libraries path of the corresponding product.

Understanding How Triggers and Actions Interact

Let's consider the scenario previously described, where clicking a button calls the SubmitChanges method on a given DomainDataSource control, and how we can implement it using triggers and actions. Let's start by breaking down this scenario:

  • The trigger will be the Click event of the button, so to capture this we'll need to use a trigger that can listen for an event raised by a control.
  • In response to the trigger, the action will call the SubmitChanges method on a DomainDataSource control, so we'll need to use an action that can call a method on a given control.

Note that both the trigger and the action that we've extracted from this scenario are very generic. You can keep it at this very generic level if you wish, which has the advantage that you can simply use the predefined generic trigger and action that fill these requirements. However, being so generic has the disadvantage that each time you want to implement the scenario, you will need to configure both the trigger and the action to perform their tasks. That is, you need to assign the event that the trigger needs to listen for, and you need to assign the method to call on the target control for the action. Thus, you may wish to create a custom trigger and action to provide a more targeted solution to the problem and reduce the configuration burden on the developer/designer.

images Note You can find a range of predefined generic triggers and actions in the Microsoft.Expression.Interactions.dll assembly. Like the System.Windows.Interactivity.dll assembly, this is installed with Expression Blend and the Expression Blend SDK, so you can reference it in your project and use the triggers/actions/behaviors that it provides. For example, in this assembly, you will find triggers such as KeyTrigger, DataTrigger, PropertyChangedTrigger, and TimerTrigger. (Note that EventTrigger is actually defined in System.Windows.Interactivity.dll.) In the Microsoft.Expression.Interactions.dll assembly, you will also find a number of actions, including HyperlinkAction, ChangePropertyAction, and CallMethodAction. There are also plenty of sources of triggers, actions, and behaviors available on the Web. The best places to start finding predefined triggers/actions/behaviors are http://expressionblend.codeplex.com and http://gallery.expression.microsoft.com.

Let's start by looking at how to implement the generic solution first, and then move on to writing and implementing a custom trigger and action to create a more targeted solution.

Configuring Triggers and Actions Using Expression Blend

The easiest way to configure triggers and actions is via Expression Blend. Triggers and actions (and behaviors) were specifically designed for use by designers so that they didn't need to write and edit code, and thus Expression Blend, a designer-focused product, has extensive support for them. In Expression Blend, you can simply drag an action from the asset library and drop it on a control. You'll find available actions in the Behaviors category, as shown in Figure 10-1.

images

Figure 10-1. Behavior assets in Expression Blend

Blend will automatically create a trigger and assign the action to it. You can then configure the trigger and action via the Properties window. Visual Studio, unfortunately, doesn't have this level of support built in. Instead, you must manually define the trigger and action in XAML by hand. However, after you've created them, you can configure their properties via the Properties window.

If you plan to leverage triggers and actions extensively in your application, using the Expression Blend designer to implement them will make the whole process much easier and faster.

images Note Coverage of Expression Blend is beyond the scope of this book. However, Kirupa Chinnathambi has a great blog post on using behaviors in Expression Blend at http://blog.kirupa.com/?p=351. You assign triggers and actions to controls in Expression Blend in much the same way as he demonstrates with behaviors. In addition, an article on the SilverlightShow.net web site by Andrej Tozon also walks you through using behaviors with Expression Blend; see www.silverlightshow.net/items/Exploring-Silverlight-behaviors-look-Ma-no-code.aspx.

images Workshop: Implementing Triggers and Actions in XAML by Hand

Using the product details view you created in Chapter 7, let's implement a trigger and an action on the Save button to call the SubmitChanges method on the DomainDataSource control, instead of the code-behind currently being used.

  1. Add references to the Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll assemblies to your project as described earlier, if you haven't already done this.
  2. Declare the interactivity and interactions namespace prefixes in your view:
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

    images Note The first namespace is for the core Expression Blend interactivity functions, from which you'll primarily use the EventTrigger, and the second is for the additional predefined interactions from the Microsoft. Expression.Interactions.dll assembly—for example, PropertyChangedTrigger, ChangePropertyAction, and so on.

  3. Find the Save button in the XAML, attach the Interaction.Triggers property to the button, and add an EventTrigger trigger to it to listen for the button's Click event:
    Button Content="Save" Height="23" Width="75">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <!--Assign action(s) here-->
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>

    images Note By default, triggers will listen for an event raised by the control on which they're attached. You can listen for an event raised by a different control instead by binding the SourceObject property of the trigger to that control, by using ElementName binding, as discussed in Chapter 11.

  4. Now that you've configured your trigger, you need to assign one or more actions to it (in place of the “Assign action(s) here” comment) for it to invoke. We'll use CallMethodAction action to call the SubmitChanges method on our DomainDataSource control.
    Button Content="Save" Height="23" Width="75">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <ei:CallMethodAction
                          TargetObject="{Binding ElementName=productsDDS}"
                          MethodName="SubmitChanges"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>

    images Note You use the TargetObject property of the action to specify the control upon which you want the action to act. If the action does not have this property set, the action will act upon the control that it is attached to.

  5. Run your application. You will find that pressing the button will call the SubmitChanges method on the DomainDataSource control—all implemented declaratively, with no code necessary.

As you can see, this is a useful feature both for designers and for developers alike. However, implementing this solution required more steps than it would ideally, as both the trigger and the action were somewhat generic. That is, you needed to specify what event that the trigger needs to listen for, as well as the name of the method that the action needs to call. If implementing this scenario repeatedly, you'd benefit from having a custom and more targeted solution. Let's now try to reduce the number of steps required to implement this scenario by creating a custom trigger and action.

images Workshop: Writing Custom Triggers

When writing custom triggers—this applies to actions and behaviors too—you are encapsulating a piece of logic in a reusable interaction component that you can attach to a control. Although you are writing these components in code, you are saving yourself the need to write code when it comes to using them. In this workshop, we'll create a custom trigger that will specifically listen for the Click event to be raised on the Button control it is attached to, and implement it in place of the generic EventTrigger trigger used in the previous workshop.

Part 1: Creating a Custom Trigger
  1. Create a new folder in your AdventureWorks project named Behaviors, and add a new class to this folder named ButtonClickTrigger.
    public class ButtonClickTrigger
    {

    }

    images Note "Behaviors” tends to be a generic term covering triggers and actions, in addition to behaviors, which is why we're calling the folder Behaviors instead of Triggers.

  2. Add the following using statements to the top of the file:
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;
  3. Custom triggers need to inherit from either TriggerBase or TriggerBase<T>, from the System.Windows.Interactivity namespace. By having it inherit from TriggerBase<T>, you can constrain the trigger to apply only to a given control type. Since we want this trigger to only appy to Button controls, it should inherit from TriggerBase<Button>.
    public class ButtonClickTrigger : TriggerBase<Button>
    {

    }
  4. Now you need to override the OnAttached and OnDetaching methods inherited from TriggerBase. In the OnAttached method, you need to add an event handler for the associated control's Click event (or whatever event the trigger should listen for). You get the reference to the control that the trigger should listen to events for from the AssociatedObject property on the base class. To prevent memory leaks, always remember to remove the event handler when the trigger is detaching (in the OnDetaching method).
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.Click -= new RoutedEventHandler(AssociatedObject_Click);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.Click += new RoutedEventHandler(AssociatedObject_Click);
    }

    private void AssociatedObject_Click(object sender, RoutedEventArgs e)
    {

    }
  5. The final step is to invoke the actions that have been associated with the trigger, by calling the InvokeActions method on the base class. This takes a parameter that allows an object to be passed to the actions, but we can assign it to null because we have nothing to pass.
    private void AssociatedObject_Click(object sender, RoutedEventArgs e)
    {
        InvokeActions(null);
    }

    The complete code for the trigger is as follows:

    public class ButtonClickTrigger : TriggerBase<Button>
    {
        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.Click +=
                                new RoutedEventHandler(AssociatedObject_Click);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            AssociatedObject.Click -=
                                new RoutedEventHandler(AssociatedObject_Click);
        }

        private void AssociatedObject_Click(object sender, RoutedEventArgs e)
        {
            InvokeActions(null);
        }
    }
Part 2: Using the Custom Trigger

We can now use this trigger in place of the more generic EventTrigger trigger in our application.

  1. Start by declaring a prefix for the behavior's namespace in the view.
    xmlns:b="clr-namespace:AdventureWorks.Behaviors"
  2. Now you can replace the EventTrigger trigger with our custom ButtonClickTrigger trigger:
    <Button Content="Save" Height="23" Width="75">
        <i:Interaction.Triggers>
            <b:ButtonClickTrigger>
                <ei:CallMethodAction
                              TargetObject="{Binding ElementName=productsDDS}"
                              MethodName="SubmitChanges"/>
            </b:ButtonClickTrigger >
        </i:Interaction.Triggers>
    </Button>

images Note For more complex triggers, you can add properties to the class to enable them to be configured when they are used, and use those values in your logic. This also applies to actions and behaviors. You can implement them as standard CLR properties, but note that they will need to be dependency properties, instead of standard properties, if you want to be able to bind them to something. I discuss implementing dependency properties in Chapter 12.

images Workshop: Creating and Using Custom Actions

In this workshop, we'll create a custom action that we'll call the SubmitChanges event on the target DomainDataSource control, in response to a trigger, and implement it in place of the generic CallMethodAction action used in the previous workshop.

Part 1: Creating a Custom Action
  1. Add a new class to the Behaviors folder in your project (created in the previous workshop), and name it SubmitChangesAction.
    public class SubmitChangesAction
    {

    }
  2. Add the following using statements to the top of the file:
    using System.Windows.Controls;
    using System.Windows.Interactivity;
  3. Custom actions need to inherit from TriggerAction, TargetedTriggerAction, or their generic counterparts from the System.Windows.Interactivity namespace. The difference between TriggerAction and TargetedTriggerAction is that TriggerAction acts on the control to which the action is assigned, while the TargetedTriggerAction acts on a different control, as specified by the designer/developer. For this example, we are attaching the action to to a Button control, but we actually need the action to act on a DomainDataSource control. Therefore, our action needs to inherit from TargetedTriggerAction<DomainDataSource>.
    public class SubmitChangesAction : TargetedTriggerAction<DomainDataSource>
    {

    }

    images Note Something to be aware of is that the generic parameter <T> for the TriggerAction<T> action is used to specify the type of control that it can be attached to and to designate the type of the AssociatedObject property on the base class. However, the generic parameter <T> for the TargetedTriggerAction<T> action is instead used to specify the type of control that it can target, and it will designate the type of the Target property on the base class. If you need to constrain what type of control that a TargetedTriggerAction action is attached to, you can decorate the class with the TypeConstraint attribute, passing this attribute the type of control it can be attached to as a parameter.

  4. The next step is to override the Invoke method, and implement the action's logic in this method. Note that this method has a single parameter, which will pass into the method whatever object was passed to the parameter of the trigger's InvokeActions method, enabling triggers to pass data to actions. The following code calls the SubmitChanges method on the target control (i.e., the DomainDataSource) when it is invoked:
    protected override void Invoke(object parameter)
    {
        Target.SubmitChanges();
    }

images Note There are additional methods from the base class that you can override if you require. All the base trigger/action classes have OnAttached and OnDetaching methods that you can override. If your class inherits from TargetedTriggerAction or its generic counterpart, you can also override its OnTargetChanged method.

Part 2: Using the Custom Action

We can now use this trigger in place of the more generic EventTrigger trigger in our application.

  1. If you haven't already done so, declare a prefix for the behavior's namespace in the view.
    xmlns:b="clr-namespace:AdventureWorks.Behaviors"
  2. Now you can replace the CallMethodAction action with our custom SubmitChangesAction action:
    Button Content="Save" Width="75">
       <i:Interaction.Triggers>
          <b:ButtonClickTrigger>
             <b:SubmitChangesAction
                      TargetObject="{Binding ElementName=productsDDS}" />
          </b:ButtonClickTrigger>
       </i:Interaction.Triggers>
    </Button>
Additional Notes: Default Trigger

To make this trigger/action pair even easier to use in Expression Blend, you can specify a default trigger that the action will use when it is dropped onto a control. Our SubmitChangesAction action has been designed for use in conjunction with a ButtonClickTrigger trigger, so ideally a ButtonClickTrigger trigger should be created automatically when the action is dropped onto a control, and it used to invoke the SubmitChangesAction action. We can tell Expression Blend which trigger the action should create by decorating the action class with the DefaultTrigger attribute, like so:

[DefaultTrigger(typeof(Button), typeof(ButtonClickTrigger))]
public class SubmitChangesAction : TargetedTriggerAction<DomainDataSource>

The first parameter specifies the type of control that this action should use the default trigger for, and the second parameter specifies the type of trigger to use when attached to a control of that type. For example, in the preceding example, we are specifying that when the action is dropped onto a Button control, it should use the ButtonClickTrigger trigger by default. You can apply this attribute multiple times to the class if you wish, enabling you to specify a different default trigger for each type of control the action is attached to.

images Note Generally, if a trigger and an action are tightly coupled, it's often better to use a behavior instead.

This is a very simple action that we've created, but you can create much more complex actions depending on the needs of your user interface. Note that they are discrete atomic blocks of interaction logic only; they do something and then exit. You can think of them as being much like a method in code. Actions cannot wait for another event to be raised, nor can they preserve their state between trigger invocations. If you need to provide a more persistent interaction, implementing behaviors may be a more suitable alternative instead.

Behaviors

Triggers and actions often come under the banner of behaviors, so when you read about behaviors, often you will find that the author is actually referring to triggers and actions rather than behaviors, which can make the concept somewhat confusing. Even Expression Blend includes actions in the Behaviors category in the Asset Library. Let's take a look at behaviors now.

What Is a Behavior?

Whereas triggers and actions are defined separately and can be mixed and matched, behaviors combine a trigger and an action into a single component, tightly coupling them. Behaviors are attached and detached from a control in the same way as triggers are and, thus, are alive for the lifetime of the control they are attached to. However, unlike a trigger, which expects to have one or more associated actions to perform the interaction logic, the behavior handles all the interaction logic internally.

At a conceptual level, you can think of a behavior as being designed to add additional functionality to the control that it is attached to. This may be a simple trigger/action combination, or may implement a more involved interaction such as a drag-and-drop operation.

Behaviors inherit from either the Behavior class or the generic Behavior<T> class, both of which are found in the System.Windows.Interactivity namespace.

One of the big advantages of combining triggers and actions into a single component is that it enables a preservation of state between the “trigger” events. A typical scenario in which you might want to maintain state between events is implementing a drag-and-drop operation. To implement this type of operation, you need to handle multiple events, including the MouseDown, MouseMove, and MouseUp events, and maintain the state of the drag-and-drop operation between them.

Behaviors typically define additional behavior and logic for the control that they are attached to, as opposed to interacting with other controls, unlike the SubmitChangesAction custom action we created earlier, which interacts with a DomainDataSource control when a button is clicked. For example, a typical behavior you might add to a text box might be one that automatically selects all the text when the text box gets the focus. This behavior entirely revolves around the text box it is attached to, without interacting with any other controls.

However, there's nothing stopping you from adding the ability to interact with other controls in a behavior; there's simply no built-in provision for it. That is, there's no TargetedBehavior base class provided.

images Workshop: Creating and Using a Behavior

Let's revisit our previous example of calling the SubmitChanges method on a DomainDataSource control when a button is clicked, but this time, we will implement it as a behavior.

Part 1: Creating a Custom Behavior
  1. Add a new class to the Behaviors folder in your project (created in the Custom Triggers workshop), and name it SubmitChangesBehavior.
    public class SubmitChangesBehavior
    {

    }
  2. Add the following using statements to the top of the file:
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;
  3. Custom triggers need to inherit from either Behavior or Behavior <T> from the System.Windows.Interactivity namespace. By having it inherit from Behavior<T>, you are able to constrain the behavior to only apply to a given control type. Since we want this behavior to only apply to Button controls, it should inherit from Behavior<Button>.
    public class SubmitChangesBehavior : Behavior<Button>
    {

    }
  4. Now you need to override the OnAttached and OnDetaching methods inherited from the Behavior class, and use them to add/remove an event handler for the associated control's Click event, in exactly the same way as you did when creating the custom trigger:
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.Click += new RoutedEventHandler(AssociatedObject_Click);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.Click -= new RoutedEventHandler(AssociatedObject_Click);
    }

    private void AssociatedObject_Click(object sender, RoutedEventArgs e)
    {
        
    }
  5. Whereas our custom trigger called InvokeActions in its Click event handler, which invoked the trigger's associated actions, in a custom behavior we actually implement the logic we want executed when the button is clicked instead. However, we don't know which DomainDataSource control to call SubmitChanges on, as the Behavior class doesn't have a TargetObject property like actions that inherit from TargetedTriggerAction do. Therefore, we need to create our own property for this task. This needs to be a dependency property in order for it to accept a data binding expression for element name binding to the DomainDataSource control. (Dependency properties will be discussed in Chapter 12, so we won't go into depth here.) Note how, as a nicety, we're decorating the property with the CustomPropertyValueEditor attribute, which displays the element-binding picker to edit the property at design time:
    [CustomPropertyValueEditor(CustomPropertyValueEditor.ElementBinding)]
    public DomainDataSource TargetObject
    {
        get { return (DomainDataSource)GetValue(TargetObjectProperty); }
        set { SetValue(TargetObjectProperty, value); }
    }

    public static readonly DependencyProperty TargetObjectProperty =
        DependencyProperty.Register("TargetObject", typeof(DomainDataSource),
                                    typeof(SubmitChangesBehavior), null);
  6. You can now call the SubmitChanges method on the DomainDataSource control bound to the TargetObject property of the behavior:
    private void AssociatedObject_Click(object sender, RoutedEventArgs e)
    {
        TargetObject.SubmitChanges();
    }

    The complete code for the behavior follows:

    public class SubmitChangesBehavior : Behavior<Button>
    {
        [CustomPropertyValueEditor(CustomPropertyValueEditor.ElementBinding)]
        public DomainDataSource TargetObject
        {
            get { return (DomainDataSource)GetValue(TargetObjectProperty); }
            set { SetValue(TargetObjectProperty, value); }
        }

        public static readonly DependencyProperty TargetObjectProperty =
            DependencyProperty.Register("TargetObject", typeof(DomainDataSource),
                                        typeof(SubmitChangesBehavior), null);
                    
        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.Click +=
                                new RoutedEventHandler(AssociatedObject_Click);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            AssociatedObject.Click -=
                                new RoutedEventHandler(AssociatedObject_Click);
        }

        private void AssociatedObject_Click(object sender, RoutedEventArgs e)
        {
            TargetObject.SubmitChanges();
        }
    }
Part 2: Using the Custom Behavior

We can now use this trigger in place of the custom trigger and action in our application.

  1. If you haven't already done so, declare a prefix for the behavior's namespace in the view.
    xmlns:b="clr-namespace:AdventureWorks.Behaviors"
  2. Now you can replace the ButtonClickTrigger trigger and SubmitChangesAction action with our custom SubmitChangesBehavior behavior instead:
    Button Content="Save" Width="75">
       <i:Interaction.Behaviors>
          <b:SubmitChangesBehavior Target="{Binding ElementName=productsDDS}" />
       </i:Interaction.Behaviors>
    </Button>

images Note We're assigning the behavior to the Interaction.Behaviors attached property on the button, rather than the Interaction.Triggers attached property, as we were before.

Which Should I Use?

So when should you use a behavior over a trigger/action combination? Ultimately, there are no fixed rules, and in some cases, there is a bit of overlap where either means is appropriate, as was demonstrated with our button click/submit changes example.

If the trigger and action are intrinsically tied together, and you want to minimize the amount of configuration necessary when using them, a behavior is probably the ideal candidate to implement the interaction logic. If you have complex interaction logic where state needs to be preserved between event handlers, such as a drag-and-drop operation, a behavior is essentially your only choice.

However, if you want to provide a more flexible use story and have interaction logic that could be triggered by different types of controls (and/or in different types of ways), implementing a separate trigger and action may be a better plan of action.

The possibilities are endless with triggers, actions, and behaviors, and going back through the examples provided throughout this book until now, you could replace almost all of the code-behind that we used with a trigger/action combination or a behavior. Now that you understand these concepts, every time you consider adding code to a view's code-behind, first consider whether encapsulating the logic in a trigger/action pair or a behavior might be a better alternative.

Triggers, actions, and behaviors are very powerful tools at your disposal, promoting both code reuse and interaction logic modularization and encapsulation. They are testable, and their biggest advantage is that they reduce and remove the need for designers to write interaction logic code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset