Saturday, April 11, 2009

DataTrigger, Bindings on non-FrameworkElements, TypeConverters, DataStateBehavior & DataStateSwitchBehavior

One of the behaviors that we've been interested in for quite a while is a DataTrigger which works nicely in Silverlight. If you aren't familiar with WPF, the DataTrigger is used to trigger state changes based on something in your view model rather than in the UI. Normal triggers are great for representing UI state such as 'when the mouse is over this element', but data triggers capture state of the view model such as 'when MyDataObject.UserState == UserState.Online'. It's great for triggering UI states based on changes in your data.

There are a couple of features which aren't in Silverlight yet which made creating a Silverlight DataTrigger interesting, but with a bit of trickery I was able to get a passable version of one working.

I was very interested in preserving the simple XAML syntax of WPF's DataTriggers-

<id:DataTrigger Binding="{Binding Value}" Value="True">
...

</
id:DataTrigger>


Bindings


The first issue issue is that Silverlight doesn't currently allow databinding on types which are not FrameworkElements. Since none of the Behaviors system derives from FrameworkElement it's not as simple as exposing a DependencyProperty and setting a binding on it.


One trick to adding bindings on behaviors is to expose a property of type Binding on the behavior- you can set a property of type binding, but you can't actually bind to something. To then actually bind this property I declared a dummy attached dependency property and set the binding on the attached DP on the FrameworkElement to which the behavior is attached to. Just listen to when the attached DP changes and presto! you have a functional binding :). It's not the most obvious solution, but I think it's by far the most elegant and functional that I've found. It actually works out quite well and works very well within Blend too.


In the source code which is attached, most of the heavy work here is encapsulated in the BindingListener helper class, you can see it being used in the DataTrigger class.



TypeConverters


Once you have the binding working you also need to get the type of the value to match the type of property that is being bound to. The reason for this is that in XAML the type is just represented as a string and you don't know what type to convert it to until the binding is actually evaluated.


To do this we use type converters when they're available, but for a number of types they are not and in this case we use a little hack which we also use in some places in Blend- serialize the property to XAML and pass that through Silverlight's parser :)


In the source code which is attached, this is encapsulated in the ConverterHelper class.



DataStateBehavior


A DataTrigger in Silverlight is nice, but a really common use of it is combined with a GoToState action to control states. This works fine, but the syntax gets a bit cumbersome since you'd need triggers for both states- the true state and the false state. To make this a bit easier, I made a simple DataStateBehavior which encapsulates the trigger and two actions into a single behavior.


In the scenario where I have a data source 'User' with a property 'IsOnline', I want to have two states in my UI- 'Online' and 'Offline'. When the user is online the 'Online' state should be active, when the user of offline the 'Offline' state should be active.


To do this, it's just:



<i:Interaction.Behaviors>
<
id:DataStateBehavior Binding='{Binding IsOnline}' Value='True' TrueState='Online' FalseState='Offline'/>
</
i:Interaction.Behaviors>


DataStateSwitchBehavior


Of course not all states are quite black and white, for the case of representing a cascade of states to represent something such as the values of an enum, I put together DataStateSwitchBehavior which allow an arbitrary number of values to check against and picks the first valid one.


An example of this is if you have the enum in your view model:



public enum UserSpeed {
Slow,
Medium,
Fast,
}



Then you can set up states for each of these enum values using the DataStateSwitchBehavior which allows specifying a state for each of the enum values:


<id:DataStateSwitchBehavior Binding='{Binding Speed}'>
<
id:DataStateSwitchCase Value='Slow' State='SlowState'/>
<
id:DataStateSwitchCase Value='Medium' State='MediumState'/>
<
id:DataStateSwitchCase Value='Fast' State='FastState'/>
</
id:DataStateSwitchBehavior>


Source


Source code and a real basic sample for all of this can be found here.

8 Comments:

Anonymous Anonymous said...

Hi Pete,
Thanks "again" for such insight! I always look forward to your blog posts and videos because I am certain to learn cutting edge stuff. BTW ... is there anyway you can change the colors you use for syntax? Maybe it's me :-), but, I find it hard to read the syntax with the color choices you made in this post. Again, this is "excellent" stuff, Pete.

Chris

12:01 PM  
Blogger Dewey said...

Anonymous, no, it's not just you, these colors suck.

On the bright side, the content is great, so please, find a friend that's not color blind, and fix this.

11:39 PM  
Anonymous Anonymous said...

how about just put a light color background behind the code. the text colors are standard VS / XML colors.

2:13 PM  
Blogger Peter Bromberg said...

I got these same comments on my blog which used to have a dark background. I simply changed it to white and everybody was happy. Good post!

9:57 AM  
Anonymous Mike Greenway said...

The samle code will not run. VS has 7 compile errors. Seems to be missing "Microsoft.Expression.Interactivity" I thought you would like it know and I would very much like a working sample.

3:40 PM  
Anonymous Anonymous said...

Hi Pete,

First of all, I am very glad that I found your post about SL behaviors based on DataTrigger, since I am using MVVM in my silverlight app.
I am trying to use DataStateSwitchBehavior for animating Datagrid cells based on the viewmodel the datagrid is attached to. My goal is to change the color of the value in the grid cell when the price property of my viewmodel changes. (price went down->red, price went up->green, price did not change->default color)
So far I did the followings:
1. I defined PriceChange enum for these 3 states
2. In my viewmodel, when the Price property changes, I also set the MarketChangeDirection property (of type PriceChange) accordingly. This property is bound to the DataStateSwitchBehavior
3. I use DataGridTemplateColumn for the Price column (bound to the Price property of VM) where I use a textblock (inside the datatemplate)to show the price.

My problems are:
1. I am not sure where I should place the DataStateSwitchBehavior (inside the datatemplate? or inside the datagrid?)
2. Should I change the default cellstyle for the Price column and define my 3 states+storyboards in the new controltemplate of the DataGridCell? If yes, which part of the DataGridCell can I animate (I can only see 2 Rectangles named RightGridLine and FocusVisual, a ContentPresenter (without name), and the Root grid in the default controltemplate)
3. Can the DataStateSwitchBehavior reach states in the controltemplate of the datagrid? Or should I define them inside the datagrid element?
4. Is it possible to debug the behavior (to see how the storyboard is actually started when the state changes)

Right now I am using ValueConverter to solve this issue. But this solution lacks animations.

Thanks for the help. -Adam

1:00 AM  
Blogger orangechicken said...

For those trying to use this solution, remember that Microsoft.Expression.Interactivity was renamed to System.Windows.Interactivity

2:25 PM  
Blogger orangechicken said...

Can you provide some insight on how to use this for a DataGridTemplateColumn template? Or provide info on even how to debug the behavior.

5:52 PM  

Post a Comment

<< Home