Monday, November 3, 2008

Using ImplicitStyleManager and Theme Containers

No doubt many of the developers and designers who are exploring Silverlight have had some exposure to WPF.  As a result they may be used to relying on implicit styles to ensure their application's have a uniform look and feel.  WPF's implicit styling behavior ensures that styles in the resources of an element are applied to their logical descendents.  Take the following WPF XAML snippet for example:

<StackPanel> <Border> <Border.Resources> <Style TargetType=“{x:Type Button}"> <Setter Property="Foreground" Value="Green" /> </Style> <Style TargetType=“{x:Type TextBlock}"> <Setter Property="Foreground" Value="Green" /> </Style> </Border.Resources> <StackPanel> <Button Content="Button inside border" /> <TextBlock>TextBlock inside border</TextBlock> </StackPanel> </Border> <Button Content="Button outside border" /> </StackPanel>

When the following markup is rendered the Button and the TextBlock inside the border will have inherited the applicable styles in the border's resource dictionary while the button outside of the border will remain unstyled.

wpfexample

This behavior is handy indeed.  It allows designers to use a mechanism similar to lexical scope to style their applications and eliminates the need to apply a style to several controls of a particular type.  That's WPF, but what about Silverlight? 

The Bad News

We can translate the XAML above to Silverlight quite easily:

<StackPanel> <Border> <Border.Resources> <Style TargetType=“Button"> <Setter Property="Foreground" Value="Green" /> </Style> <Style TargetType=“TextBlock"> <Setter Property="Foreground" Value="Green" /> </Style> </Border.Resources> <StackPanel> <Button Content="Button inside border" /> <TextBlock>TextBlock inside border</TextBlock> </StackPanel> </Border> <Button Content="Button outside border" /> </StackPanel>

The only thing we've done here is remove the {x:Type} markup extension which isn't supported in Silverlight 2.  There's one little problem though: the Button and TextBlock don't get styled.  Since Silverlight 2 does not support implicit styles you have to manually apply them like so:

<StackPanel> <Border> <Border.Resources> <Style x:Key="myButtonStyle" TargetType=“Button"> <Setter Property="Foreground" Value="Green" /> </Style> <Style x:Key="myTextBlockStyle" TargetType=“TextBlock"> <Setter Property="Foreground" Value="Green" /> </Style> </Border.Resources> <StackPanel> <Button Style="{StaticResource myButtonStyle}" Content="Button inside border" /> <TextBlock Style="{StaticResource myTextBlockStyle}">TextBlock inside border</TextBlock> </StackPanel> </Border> <Button Content="Button outside border" /> </StackPanel>

That's not so bad right?  Of course what if you have a form with 50 controls?  It's gets to be a drag explicitly applying style to each and every control.  No doubt you have better things to do with your time.  If you live in the US you could vote in Tuesday's election.  Or whatever.  I'm just saying.

The Good News

We on the Silverlight toolkit team have a created a control that brings WPF-like implicit styling to Silverlight 2.  The control's name is ImplicitStyleManager (ISM) and it allows implicit styling to be applied to a control by defining the attached "ApplyMode" property.

<StackPanel> <Border controls:ImplicitStyleManager.ApplyMode=“OneTime"> <Border.Resources> <Style TargetType="Button"> <Setter Property="Foreground" Value="Green" /> </Style> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="Green" /> </Style> </Border.Resources> <StackPanel> <Button Content="Button inside border" /> <TextBlock>TextBlock inside border</TextBlock> </StackPanel> </Border> <Button Content="Button outside border" /> </StackPanel>

If we run this we get the expected result:

wpfexample

There are three possible values for ApplyMode:

  1. None (default)
  2. OneTime
  3. Auto

Setting ApplyMode to None is equivalent to not setting the property at all.  Setting ApplyMode to OneTime will cause styles to be applied once after the templates are applied while setting ApplyMode to Auto will ensure that any controls dynamically added to an implicitly styled container will also be styled.

Using External Resource Dictionaries

WPF allows designers and developers to use the styles defined in an external resource dictionary instead of the styles defined in element's local resources.  This can be accomplished with the following XAML:

<StackPanel> <Border> <Border.Resources> <ResourceDictionary Source="myfile.xaml" /> </Border.Resources> <StackPanel> <Button Content="Button inside border" /> <TextBlock>TextBlock inside border</TextBlock> </StackPanel> </Border> <Button Content="Button outside border" /> </StackPanel>

Although Silverlight does not support styling descendents with styles loaded from an external dictionary ISM does provides an attached property that enables this scenario: ResourceDictionaryUri.  Here is the Silverlight+ISM equivalent of the XAML above:

<StackPanel> <Border controls:ImplicitStyleManager.ApplyMode=“OneTime“ controls:ImplicitStyleManager.ResourceDictionaryUri=“myfile.xaml"> <StackPanel> <Button Content="Button inside border" /> <TextBlock>TextBlock inside border</TextBlock> </StackPanel> </Border> <Button Content="Button outside border" /> </StackPanel>

That's it.  Remember to set the build action of your XAML resource dictionary file to "Content."  That allows you to reference it using a relative Uri.  You can also set it to "Resource" if you like but you will be required to specify the absolute path.

How Does ISM Work?

ISM hooks the LayoutUpdated event of the FrameworkElement it is applied to.  When the LayoutUpdated event is raised it traverses the logical descendents of the element and applies styles to them where applicable styles are found. 

howdoesitwork

Those of you familiar with WPF may know that is logical descendent is an element in the same namescope of its parent element.  It is important to differentiate between logical descendents and visual descendents.  The latter is styled by ISM and the former is not. 

Understanding the Logical Tree

Consider the following XAML:

<Grid> <Button Content="This is a button"></Button> </Grid>

The Grid's logical tree consists of the two elements you see in the XAML: the Grid and the Button.  On the other hand the visual tree looks like this:

visualtree

The visual tree includes all of the element's in the Button's default template.  Neither WPF or ISM styles these elements because it would cause unpredictable results.  Notice that the Button's visual tree contains a TextBlock.  If styles were applied to all element's in the visual tree a style intended to set all of the TextBlock's font sizes to 30 would also cause all the Button's font sizes to jump to 30.  That wouldn't be very logical now would it? ;-)

For more information on how to programmatically determine if an element is in the logical tree or not check this post out.  If you want a more in-depth explanation of how ISM was implemented check this post out.

Performance Considerations

ISM is a powerful control but with great power comes great responsibility.  Traversing the logical tree is both CPU and memory intensive.  Under the circumstances you'll want to ensure that you are choosing an ApplyMode that delivers an acceptable level of performance.

The "Auto" apply mode is the most expensive because it traverses the tree whenever the LayoutUpdated event is raised.  This event is raised very often.  Simply scrolling up and down in your browser may cause the event to be raised fifty times.  As a result this apply mode is generally only appropriate for small applications or for prototyping.  In contrast the "OneTime" apply mode is comparatively inexpensive because the tree is only traversed once at. 

Fine I'll use OneTime.  What if I need to style a dynamically created control though?

If you want to ensure a dynamically created control is styled you can use ISM's "Apply" method.  You can manually call this method after adding your dynamically generated control to the styled container:

Button b = new Button { Content = "I'm a dynmaically generated button." }; myStyledStackPanel.Children.Add(b); ImplicitStyleManager.Apply(myStyledStackPanel);

Themes, Themes, and More Themes

You can use ISM directly to apply your own themes or you can use any one of the theme containers available with the Silverlight Toolkit.  The themes use ISM and an external resource dictionary file under the hood.  As a result themes are only applied to elements in the logical tree and have the same performance trade-offs as ISM.  To take a look at the available themes go the Silverlight Toolkit's Theming page.

For information on how to customize the themes check out Mehdi Slaoui Andaloussi's blog.

26 comments:

Eamon Nerbonne said...

Part of the attractiveness of silverlight and WPF are that they are almost compatible. ISM feels like a different means of accomplishing almost the same thing. Yet, I don't see why: why isn't the more flexible WPF approach possible in silverlight?

Given the fact that it's not, ISM means you can minimize distinctions, and that's a very nice piece of software. But really, seeing actual compatibility here would be much more attractive...

Jafar Husain said...

ISM is a control with an expiration date. The Silverlight team is working towards implicit styles out of the box. When they get there you will be able to remove ISM.

Anonymous said...

Nice post when will Silverlight support Merged Dictionaries ??

Anonymous said...

Thank you for the clear and concise description of ISM! You anticipated and addressed the reader's responses very well. One thing that's kind of curious... It's a little hard to understand why traversing a tree of say, a few thousand elements should be so CPU and memory intensive, but I definitely believe you. I do understand that the frequency of LayoutUpdated events is a problem.

Jafar Husain said...

anonymous: I honestly don't know. Hopefully by the time they get implicit styles.

Tom: Good point. One of the reasons the traversal is more costly that it seems is that in order to seperate the logical tree from the visual tree I must call FindName on every node. FindName presumably does some traversal of its own. This raises the computational cost.

As for memory we've tried to be as efficient as possible but due to the fact that the LayoutUpdated event is fired so frequently a large number of objects may be created before the garbage collector has a chance to free them.

BonGeek said...

I have a query, why I am keep getting the following Design-time error:

Unable to load resources

It occurs almost in every example by Microsoft or other famous bloggers sample for Theme that just introduced by MS, if they implement the ImplicitStyleManager.

Take Care,

Mudassir Azeemi

Brian said...

I found a pretty big bug with the ImplicitStyleManager: when ApplyMode is set to "Auto" or "OneTime", any controls inside of a scrollviewer get loaded twice! If you put a breakpoint in a Loaded event of a textbox control, for example, you can confirm this. Does anybody know what's going on or a workaround? I tried logging a bug on Codeplex but am unable to register...

Jafar Husain said...

Hi Brian,

Thahks for bringing this to my attention. I ask that you please make another effort to register on CodePlex. That's where we track our bugs. I'd like to avoid using my blog as a bug tracker.

If you can't successfully register please send me a zip with a small repro and I'll open a bug for you. My address is jahusain at the domain microsoft.com.

Affordable Luxurious Wedding Dress Blog said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
rou said...
This comment has been removed by a blog administrator.
deoo said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
ewart at ihug dot co dot nz said...

with silverlight 4 is there still a performance hit using the ImplicitStyleManager?

About Me

My photo
I'm a software developer who started programming at age 16 and never saw any reason to stop. I'm working on the Presentation Platform Controls team at Microsoft. My primary interests are functional programming, and Rich Internet Applications.