Right before the weekend, another post on Behaviors…
In my last post, I discussed a few of the building blocks of the Behaviors mechanism in Blend 3: Triggers, Actions and Behaviors. I also talked a little bit about the Source for Triggers and the Target for actions. Let’s jump right into a sample.
Here is the code for an action that does, well, nothing:
Some of the interesting parts:
Your project needs to reference Microsoft.Expression.Interactivity.dll, which you can find in your Blend installation directory, in a folder Libraries. There are two sub-folders there, with versions of this library for WPF and Silverlight. You need to have a using statement for this namespace in your action class.
Your new action needs to inherit from one of the TriggerAction classes, which, not surprisingly, define actions that are fired by a trigger. This example uses a TargetedTriggerAction, which is an action that can be set up to target a different object than the object it is attached to. I’ll explain that in a moment. This action base class is also generic. Without going into too much details for now, the generic parameter allows you to limit to which types of objects an Action can be attached.
This is where the Action happens. All that your Action needs to do is to override the Invoke method with whatever activity you want your Action to perform. This sample of course very elegantly does nothing.
Creating a Project
To try out this sample, open Blend 3, and create a new WPF or Silverlight application project. I am using WPF, just to prove that Visual State Manager, which we will use in this sample, actually works in WPF. If you want to follow along with that, you must have the WPF Toolkit installed. If you want to use Silverlight instead, there might be a few minor code changes required.
Once you have created the project, add a reference to Microsoft.Expression.Interactivity.dll (in your Blend install directory, Libraries\WPF) and to the WPFToolkit.dll (by default in C:\Program Files\WPF Toolkit).
Finally, add a new item, using the new Class project item template:
Call the class SimpleSampleAction.cs and click OK.
This gives you an immediate opportunity to give the new code editor in Blend a spin. Just paste the code from the sample above into the editor:
Before we try this out, we should maybe make our sample action do something, otherwise this is not going to be an interesting experience. Change the Invoke() method as follows:
We just added Line 6, where we call up a message box saying “Hello!”
Now, go to the main window and draw a rectangle and make it red. While you are at it, also make a button.
Before you can apply the simple behavior, build your project by pressing Ctrl-Shift-B, or by selecting Project>Build Project.
Then, open the asset tool and switch to the Behaviors category:
Drag the simple sample action onto the red rectangle.
Repeat, but this time drag to the button.
Dragging an Action onto an object attaches the Action to this object. At this point, it is probably time to look what behaviors do in the object tree and in the XAML code. Here’s the object tree:
As you can see, Actions appear as children to the objects they are attached to.
And here’s the XAML:
What you see in Line 2 and Line 9 might surprise you: In the XAML, you can see that Blend has inserted a Trigger collection (Interaction.Triggers) with a single EventTrigger (Line 3 / Line 10). That EventTrigger contains the sample action you dragged out on the rectangle and button.
And this is indeed how it works: Actions are never directly attached to objects. Instead, the object has a list of triggers, and every trigger in the list has an attached action. The good news is that you can largely ignore this fact.
In a later post, we will have a look at how this works for actual Behaviors.
As you can see from the XAML Blend has generated, the SimpleSampleAction by default gets fired by an Event trigger, and the event that Blend set up by default is “Loaded”. Or in other words, if we run the app, as soon as the rectangle and button are loaded, we should see the message boxes, one for the rectangle and one for the button.
Let’s try that. Press F5 to run.
And indeed, this is exactly what happens. This is good because it was what we expected, but not so good because it is rather useless in this case. So how can we change the Trigger for an Action?
Luckily, there is nothing you need to do when you write an Action: Blend automatically provides UI to edit the Trigger for any Action. However, if you know that your Action works best with a particular default Trigger, you can add an attribute to the code of your Action that tells Blend which Trigger to insert by default. If you do not add such code, Blend will default to EventTrigger set for a Loaded event. We will look at the configuration for this in a future post.
For now, let’s look at editing the Trigger. To do this, click on one of the actions in the Object Tree, and then look at the Property Inspector:
In this screenshot, all areas are expanded. Our SimpleSampleAction shows two sections: One for setting the Trigger, and one for setting properties of your Action itself.
Our sample Action does not have any properties in the code we have written, but it inherits two properties by default:
- A switch to enable or disable the Action
- A Target property that determines which object is going to be targeted by this action. Our sample currently does not use the Target property (it just calls up a MessageBox) but if it did, here you have the UI to tell your Action where to target.
Every Action also inherits the Trigger UI by default. This allows you to pick a Trigger type (this is where you would pick custom triggers you have created yourself) and whatever properties the Trigger might offer. The current Trigger is an event trigger, and event triggers have one rather important property, which is the event to listen for. By default this is “Loaded”. Other Trigger types might have very different properties here.
Change the event to listen for to “MouseLeftButtonUp” for the Rectangle and to “Click” for the Button.
Now run your application again. Your message box will now fire when you click the button and the rectangle.
Before we continue, there is one more property for Triggers: The Source property for the trigger tells Blend where to listen for the trigger. It this property is not set, Blend listens for events from the object the Action is attached to.
Finally, A Real Sample
Ok, now that we spent all this effort to learn the basic, let’s do an Action that does something useful.
The Action I want to build is toggles between to visual states of some object on the screen. If you don’t know what Visual States are, please see this post for more information.
This Action will also give us a useful opportunity to try out the Target property. Let’s explain the Target quickly:
Imagine a push button that switches on a lamp. You want an action that switches the light on when the button is first pressed, and switches the light off when the button is pressed again.
Now, where do we attach the Action? We could attach it to the switch or to the light bulb.
If we attach it to the switch, the action needs to listen to the object it is attached to, but the object the Action actually changes when the switch is pressed is the light bulb. With Actions, we can accomplish this by making the light bulb the Target for the Action.
If we attach the Action to the switch, we don’t need to set the Target – remember that every Action by default operates on the object it is attached to. But in this case we need to listen for a Trigger from a different source. You can do that by setting the Source property of the Trigger to the switch.
Blend supports both scenarios, because they are both equally useful. Note: If your Action is attached to a UserControl, the Source property does not work in current builds of Blend 3.
ToggleStateAction is a little bigger than it strictly needed to be because I want to use this sample to illustrate a number of things:
- How an Action can deal with being re-attached
- One example of how an Action can store state so that it can be shared between multiple Actions. Also, the same technique allows Actions to coordinate and communicate.
But enough words, let’s look at code.
Create another source class and call it ToggleStateAction. Here’s the code:
What does it do, and how does it work? First, a look at the class and the Invoke() method:
The class declaration is exactly the same as before – we allow this Action to be applied to any FrameworkElement. However, the purpose of this Action is to toggle between two visual states, and VisualStateManager, the framework class used to actually do the work, can only switch states on Control or one of its subclasses.
In line 5, we therefore ensure that we actually are targeting a Control. If the Action targets something different than a Control, we simply do nothing.
You may ask why we did not simply change the generic parameter of the class from FrameworkElement to Control? The reason is that the generic parameter only controls what objects the Action can be attached to. The Target however, is not necessarily the object the Action is attached to – if you have set the Target property for the Action, it might be any other object, potentially one of a completely different type. Therefore, the test.
In order to toggle between two states of the Target, we need to know in which state the Target is in. Unfortunately, it turns out that there is no method to tell us in which state a control currently is in. That leaves us with a number of options: One is to listen to state change events on the Target and follow state changes that way. Or in a real simple implementation, I could just force a state when the Action is first attached, and then store state internally.
When I first wrote this sample, I just stored the state internally using an enum value. That worked fine as long as just one switch toggled the light bulb. However, as soon as there were multiple switches, each using an instance of the Action, the Actions would store their state independently and run out of sync.
This little problem gave me the opportunity to show one possible technique how multiple Actions can communicate and store shared state.
Overall, this adds quite a bit of code that is unnecessary in the simplest case, but it is really simple code.
The way this works is this:
I created a backend object, in this case a singleton, that basically contains a dictionary to store state for a number of Control objects. The backend class looks like this:
In Line 1, you see the enum used to define the possible states: None (Unknown), or the first or second state allowed by this Action.
Aside of that, there really is not much of interest in here: This is a simple singleton (a class that can only instantiated once, but the instance can be reached via a static Instance property), and that has methods to store state and read state.
The state is stored in a Dictionary (declared in line 29), with one state per targeted control. This way, multiple groups of ToggleStateActions can share state without stepping on each others toe’s.
The dictionary singleton is used in the rest of the sample to store and read state of the target.
Next, let’s look at the complete Invoke() method:
In line 7, we get the current state from the state manager singleton.
In the switch statement after line 9, we toggle to the right state we need to be in by calling VisualStateManager.GoToState() (line 14, 20 and 26), and then we store the new state back to the backend manager.
And that is almost all this Action does.
There’s just one more thing of interest:
This overridden method is called when the Target for the Action is changed for any reason. In this case, we make sure that an appropriate initial state is set, based on the settings of some properties on the Action class.
The rest of the code is just some DependencyProperty declarations for properties the Action exposes. These properties are:
- FirstState and SecondState: These are the two states to toggle between.
- ForceFirstState. When this property is true, the Action forces the first state the moment it is loaded.
- UseTransition. This property determines if we switch between the first and second state immediately, or if we let VSM run a transition, provided one has been defined.
Trying the Sample
First, create a user control for a little light bulb, and give it two states, On and Off:
Next build your project and add the lamp control to the scene:
Finally, wire up the ToggleStateBehavior to the rectangle and the button. These are the properties for the Button:
As you can see, we have set up the Target to be the Lamp control. For the rectangle, the properties are very similar, except that the EventName property for the Trigger needs to be MouseLeftButtonUp.
Run the application. Both the button and the rectangle will trigger the state change in the light bulb. One press changes to on, the next press changes to off. You can mix and match the buttons as desired, the state is still preserved correctly.
Here is the sample project: