by JustinAngel via Justin myJustin = new Microsoft.Silverlight.Justin(); on 12/26/2008 2:27:49 AM
Hi Folks
I’ve been getting some questions through the usual backchannels (Twitter, my J@JustinAngel.Net email and mail pigeons) about creating custom VSM managers.
If you’re unfamiliar with VSM I strongly suggest you take 30 minutes and watch Steve White talking in his ‘ohhh-so-British' voice about it:
Watch these 4 videos on the Blend 2.5 website which should provide you with deep technical insight into the inner workings of Templating Parts & VSM States from Blend: http://expression.microsoft.com/en-ca/cc643423.aspx
Let’s try and conceptually sum up what VSM does:
Well, we could have just said this instead:
Point is – the VisualStateManager class is the driving force behind the actual change between states.
Let’s see how that comes into play.
Here are all the states for a Button: (snapshot from Blend)
Let’s put a normal button on our form, run the app and see it’s MouseOver state:
<Button x:Name="myButton" Content="myButton" Width="100" Height="100" />
We can see that once the mouse enters the button area, the MouseOver state is fired, a storyboard executes and everyone’s happy.
But how executes the state change? VSM of course.
Let’s see how we can “interfere” with the normal operations of VSM.
public SilverlightControl2536()
{
InitializeComponent();
myButton.MouseEnter += new MouseEventHandler(myButton_MouseEnter);
}
private void myButton_MouseEnter(object sender, MouseEventArgs e)
VisualStateManager.GoToState(myButton, "Disabled", true);
Over here I’ve registered to the MouseEnter event and told VSM that “On MouseEnter go to the Disabled State”.
We can definitely see that mouseovering the button clearly puts it in Disabled state.
And we caused this little change through talking directly to the VSM static class. I’ll just point out that the button isn’t really disabled and is still clickable. All we did here is cause a VSM state change, which caused a storyboard to begin, which changed the Visual UI of the button.
As for demos – the disabled button one was short, to the point and utterly useless. Let’s get our hands dirty with some custom VSM goodness.
Writing a Custom VisualStateManager – Getting the current states
We just demonstrated and explained that the VisualStateManager is an actual class which drives state management at the control level. We can also create our own VisualStateManager that has additional functionality and logic in that stage.
public class CustomVisualStateManager : VisualStateManager
protected override bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)
this.
return base.GoToStateCore(control, templateRoot, stateName, group, state, useTransitions);
This basic class doesn’t do a lot. We inherit from VisualStateManager and override the only method we can – GoToStateCore.
We can see that we get some important data:
Now, if we were the original VisualStateManager class this is the place to actually do state changes, fire storyboards, raise events and what not. Luckily, we’re not. So we’ll call the base method and that would take care of actually running states. Which leaves us as free to do whatever we want.
And here’s what we want – We want to be able to query the VisualStateManager and get a string the represents the control’s current state in each state group.
public class QueryableVisualStateManager : VisualStateManager
public static string QueryState(Control controlToFindItsState)
Now, at this point we’ll add a lot of plumbing to store the last fired states in each group for each control.
/// <summary>
/// Dictionary of controls --> Dictionary of group names and state names
/// </summary>
private static Dictionary<WeakReference, KeyValuePair<string, string>> controlStates =
new Dictionary<WeakReference, KeyValuePair<string, string>>();
bool ReturnValue = base.GoToStateCore(control, templateRoot, stateName, group, state, useTransitions);
if (ReturnValue)
var existingPair = controlStates.SingleOrDefault(pair => pair.Key.IsAlive
&& pair.Key.Target == control
&& pair.Value.Key == group.Name);
if (existingPair.Key != null)
controlStates.Remove(existingPair.Key);
controlStates.Add(new WeakReference(control), new KeyValuePair<string, string>(group.Name, stateName));
return ReturnValue;
var existingValues = controlStates.Where(pair => pair.Key.IsAlive
&& pair.Key.Target == controlToFindItsState);
string ReturnValue = string.Empty;
foreach (var existingValue in existingValues)
ReturnValue += existingValue.Value.Key + "." + existingValue.Value.Value + " ";
This code really isn’t interesting. All it does is store controls, state groups and last fired states in that state group. Than, it queries against that data and returns the current state in a string form.
There’s really nothing interesting about the implementation, just the fact that we’re getting some data, we’re storing it and later on query it.
Next, we’ll want to use this custom Visual State Manager with the Button, CheckBox and Expander control.
So, we’ll start by putting these on our form:
<StackPanel x:Name="LayoutRoot" Background="White">
<Button x:Name="myButton" />
<CheckBox x:Name="myCheckBox" />
<controls:Expander x:Name="myExpander" />
</StackPanel>
In order to change the VSM manager, we need to edit the template for each of these controls.
We’ll open up Blend on this form.
We’ll right click on each of the controls –> “Edit Control Parts (Template)” –> Edit Copy –> Ok.
After getting local copies of the templates for all the controls, we’ll need to set the VisualStateManager.CustomVisualStateManager Attached property in their templates:
<vsm:VisualStateManager.CustomVisualStateManager>
<SL_RTM_VS:QueryableVisualStateManager />
</vsm:VisualStateManager.CustomVisualStateManager>
Here’s a method that uses the static method we defined on our custom visual state manager and updates their content:
private void updateContentFromVSM(object sender, EventArgs e)
myButton.Content = QueryableVisualStateManager.QueryState(myButton);
myCheckBox.Content = QueryableVisualStateManager.QueryState(myCheckBox);
myExpander.Header = QueryableVisualStateManager.QueryState(myExpander);
Let’s sign this method up for some events that may cause visual state changes:
myButton.MouseEnter += new MouseEventHandler(updateContentFromVSM);
myButton.MouseLeave += new MouseEventHandler(updateContentFromVSM);
myButton.MouseLeftButtonDown += new MouseButtonEventHandler(updateContentFromVSM);
myButton.MouseLeftButtonUp += new MouseButtonEventHandler(updateContentFromVSM);
myCheckBox.MouseEnter += new MouseEventHandler(updateContentFromVSM);
myCheckBox.MouseLeave += new MouseEventHandler(updateContentFromVSM);
myCheckBox.MouseLeftButtonDown += new MouseButtonEventHandler(updateContentFromVSM);
myCheckBox.MouseLeftButtonUp += new MouseButtonEventHandler(updateContentFromVSM);
myCheckBox.Checked += new RoutedEventHandler(updateContentFromVSM);
myCheckBox.Unchecked += new RoutedEventHandler(updateContentFromVSM);
myExpander.MouseEnter += new MouseEventHandler(updateContentFromVSM);
myExpander.MouseLeave += new MouseEventHandler(updateContentFromVSM);
myExpander.MouseLeftButtonDown += new MouseButtonEventHandler(updateContentFromVSM);
myExpander.MouseLeftButtonUp += new MouseButtonEventHandler(updateContentFromVSM);
myExpander.Expanded += new RoutedEventHandler(updateContentFromVSM);
myExpander.Collapsed += new RoutedEventHandler(updateContentFromVSM);
Now, let’s run our app.
This is how the app looks like initially once we mouseover elements and cause them to fire their initial states.
Now, What happens if we mouseover the Button?
And Click it?
What happens if we mouse over the CheckBox?
And click the CheckBox?
And than Click the Expander?
All and all, we can see that through a Custom VisualStateManager we can store the state changes and query them at a later phase.
One last change – Getting the initial state
Through some sleight of hand I was able to hide the fact that our form look like so when it first load up:
If we want to have the controls initialized with their states, we need to make sure the Visual Tree (which setups the custom VSM) is up and running. In order to do that, we’ll register for a one-time call to LayoutUpdated on each control and initialize state there.
myButton.LayoutUpdated += new EventHandler(myButton_LayoutUpdated);
void myButton_LayoutUpdated(object sender, EventArgs e)
myButton.LayoutUpdated -= myButton_LayoutUpdated;
And now in our UI we can see the following initial state:
Now, let’s add this initialization step for our two other controls:
myCheckBox.LayoutUpdated += new EventHandler(myCheckBox_LayoutUpdated);
myExpander.LayoutUpdated += new EventHandler(myExpander_LayoutUpdated);
void myCheckBox_LayoutUpdated(object sender, EventArgs e)
myCheckBox.LayoutUpdated -= myCheckBox_LayoutUpdated;
void myExpander_LayoutUpdated(object sender, EventArgs e)
myExpander.LayoutUpdated -= myExpander_LayoutUpdated;
myExpander.Content = QueryableVisualStateManager.QueryState(myExpander);
And you can get the source at: http://silverlight.net/blogs/justinangel/BlogStorage/SilverlightCustomVSM.zip
-- Justin Angel Microsoft Silverlight Toolkit Program Manager
Original Post: Custom VSM VisualStateManagers in Silverlight 2.0
The content of the postings is owned by the respective author. Silverlight Feeds is not responsible for the contents of the postings. This site is automatically generated and cannot be reviewed for abusive content. If you find abusive content on Silverlight Feeds, please contact us. Designated trademarks and brands are the property of their respective owners. All rights reserved.