Triggers And TriggerActions

Windows Phone Custom Triggers and TriggerActions

Over the past couple of weeks I have been working on a Windows Phone app in my spare time. I’ve been developing this app using the MVVM architecture with some assistance from the MVVM Light Toolkit. I was working on a search screen where criteria would be entered into a textbox, and when the enter key was pressed, a method in the viewmodel would be executed to perform the search. I try to keep the code-behind of my views as clean as possible and define as much of the appearance and behavior as I can in XAML and custom classes. I knew a key trigger already existed in the expression interactions assembly (Microsoft.expression.interactions.dll) that I could use to catch the enter key being pressed. I also knew that there was a TriggerAction available to invoke my search command in the windows interactivity assembly (system.windows.interactivity). I just wasn’t sure if an action existed to set focus to another control to get the soft keyboard to close when the enter was pressed and the search command was executed. Custom Triggers and TriggerActions to the rescue!

If you are new to triggers and actions, below is the basic structure of how a trigger and action look in XAML. This example I applied to my search PhoneApplicationPage (not shown for brevity) to clear any search results when the user pressed the back button. This markup adds a Triggers collection to the PhoneApplicationPage object. It then specifies that an EventTrigger is added to the collection that is watching for the BackKeyPress event to be fired. The InvokeCommandAction is then added to the actions collection of the trigger so that when the BackKeyPress event fires, the ClearSearchResultsCommand on the viewmodel is invoked and the search results collection is cleared. The “i” namespace in the example is mapped to the System.Windows.Interactivity assembly.

<i:Interaction.Triggers>
   <i:EventTrigger EventName="BackKeyPress">
      <i:InvokeCommandAction Command="{Binding Path=ClearSearchResultsCommand}"  />
   </i:EventTrigger>
</i:Interaction.Triggers>

Triggers

A trigger is an object that listens for something specific to happen on the object that it is attached to. That something specific could be a property changing or an event firing off. When this happens, the trigger invokes any actions that are contained within its actions collection. The triggers that are available out of the box from Microsoft will probably get you through most of the common scenarios that you are going to run into. If they don’t, creating a custom trigger to accomplish what you need isn’t difficult.

Let’s look at an example of creating a custom trigger. For this example we will be creating a trigger that catches when the enter key is pressed on the keyboard. Microsoft already provides a “Key” trigger to handle this situation (as mentioned above), but this makes for a good example so that is what we’ll create. The code listing below is the completed custom trigger. The key things to notice are that the new trigger class inherits from TriggerBase, it overrides OnAttached and OnDetaching, and it makes a call to the InvokeActions method. Let’s discuss each of these parts of the trigger in more detail.

public class TextBoxEnterKeyTrigger : TriggerBase<UIElement>
{
   protected override void OnAttached()
   {
      base.OnAttached();
            
      if (this.AssociatedObject != null && this.AssociatedObject is TextBox)
      {
         this.AssociatedObject.KeyUp += new KeyEventHandler(TextBox_KeyUp);
      }
      else
      {
         throw new InvalidOperationException("This trigger only works on TextBox controls");
      }
   }

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

      AssociatedObject.KeyUp -= new KeyEventHandler(TextBox_KeyUp);
   }

   private void TextBox_KeyUp(object sender, KeyEventArgs e)
   {
      if (e.Key == Key.Enter)
      {
          TextBox textBox = AssociatedObject as TextBox;

          if (textBox != null)
          {
             BindingExpression expression = textBox.GetBindingExpression(TextBox.TextProperty);
             if (expression != null) expression.UpdateSource();

             this.InvokeActions(textBox.Text);
          }
       }
    }
}

All custom triggers should inherit from the abstract class TriggerBase. TriggerBase provides a property “AssociatedObject” that is a reference to the object that the trigger was attached to. In our case, it will be a reference to the TextBox that we add our custom trigger to. Override the OnAttached method and use the AssociatedObject property to wire up an event handler to the KeyUp event on the TextBox. Also override the OnDetaching method to remove the event handler when the trigger is being removed from the control (Whenever possible, you should remove event handlers when they are no longer needed. This will help to prevent memory leaks). All the logic for our trigger lives in the event handler method “TextBox_KeyUp”. It checks to see if the key pressed was the enter key. If so, it updates the binding source and then calls the InvokeActions method from TriggerBase. The InvokeActions method will fire off every action in the collection on the trigger. Notice that you can pass in a parameter to InvokeActions that will in turn get passed into each action as it is invoked.

That’s about it for our Trigger. Next let’s take a look at TriggerActions!

TriggerActions

As you probably have guessed, actions are objects that do something or perform a task.

There are two types of actions that you can create. There is the standard TriggerAction and then there is the TargetedTriggerAction. TriggerAction has an “AssociatedObject” property like we talked about for triggers. Through this AssociatedObject property, TriggerActions have access to the object that they are attached to. TargetedTriggerAction, on the other hand, has some additional properties that allows you to specify an element in addition to the AssociatedObject to execute some task against.

The example TriggerAction we are going to look at will be a TargetedTriggerAction. Its purpose is to set focus to a specified element on the screen when the action is invoked. The TriggerAction we’re creating is even more simple than the Trigger we created in the previous section. TriggerAction and TargetedTriggerAction have some of the same properties and methods to override as what we saw earlier on Trigger. These include OnAttached, OnDetaching and AssociatedObject but our action is quite simple and only requires us to override the Invoke method. The Invoke method on an action gets called by the InvokeActions method on the trigger. Notice that the code below inherits from TargetedTriggerAction. As mentioned previously TargetedTriggerAction has additional properties that allow another element to be specified for the action to operate on. The code below is expecting the TargetObject property of TargetedTriggerAction to be set and for it to reference an element that is based on the System.Windows.Controls.Control type. If TargetObject is of type Control, Focus is set to that TargetObject.

There are additional properties that can be set to specify the target other than TargetObject. You can read up on these over on the MSDN site.

public class SetFocusToTargetAction : TargetedTriggerAction<DependencyObject>
{
   protected override void Invoke(object parameter)
   {
      if (TargetObject != null && TargetObject is Control)
      {
         ((Control)TargetObject).Focus();
      }
      else
      {
         throw new InvalidOperationException("This action only works with objects based on Control");
      }
   }
}

Adding to Xaml

Now that we have our custom Trigger and TriggerAction, how do we put them to use? It’s quite easy to start using custom Triggers and TriggerActions in Xaml.

First, add a namespace mapping to your assembly that contains the custom code at the top of your Xaml file .

xmlns:lct="clr-namespace:LeftyCoder.Triggers"

If you don’t already have a namespace mapping to the Windows Interactivity assembly, add that too.

 xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

Once those namespace mappings are in place, it’s as easy as adding the Triggers and TriggerActions to an object like as shown for the TextBox below. Once the code below is in place, the custom trigger will catch the enter key being pressed and invoke the action that will set focus to the specified element. The element “SearchPage” seen in the action, is the name of the PhoneApplicationPage that the TextBox is on. Setting focus to the PhoneApplicationPage from the TextBox causes the keyboard to close.

<TextBox x:Name="SearchText">
   <i:Interaction.Triggers>
      <lct:TextBoxEnterKeyTrigger>
         <lct:SetFocusToTargetAction TargetObject="{Binding ElementName=SearchPage}"></lct:SetFocusToTargetAction> 
      </lct:TextBoxEnterKeyTrigger>
   </i:Interaction.Triggers>    
</TextBox>

Conclusion

Writing custom Triggers and TriggerActions is a powerful concept that really isn’t difficult to grasp. Adding this capability to your toolbox comes in handy when you can’t find anything out of the box from Microsoft to meet your needs.

For further reading on the subject, check out the links below.
Custom Triggers and Actions
TriggerBase Class
TriggerAction Class
TargetedTriggerAction Class