Monday, March 22, 2010

Merged Dictionaries of Style Resources and Blend

A while ago I needed to have multiple resource dictionaries available to my Silverlight application. One would be the base library with a complete set of style resources for the application, and the others would be resource dictionaries for alternative styles. The application would choose which alternative style to use when it loaded, and would merge the alternative style into the base style resources. For example we may have an application that we only want to install once on the server, but it needs to have different corporate branding depending on which customer is using it

After reading up on how best to use the MergedDictionary, and reading about resource usage problems others were having, I decided I needed a solution that would meet the following goals:

  1. Allow me to keep various styles in separate resource files
  2. Work with Prism modules
  3. Require the minimum of system resources
  4. Play nice with Blend (well, as much as possible)
There were trade-offs among the last two goals, but the following technique has been useful in my day to day development and I have continued to use it successfully.

I'll start with the basics though:

Reusable Styles
This first section describes an approach to styling that allows for separation of design and functionality, is efficient in terms of resource requirement, and is compatible with working with Blend.

Styling is carried out in a Silverlight application by defining a style and applying it to a control. For example, a TextBox control could be declared like this:
   1: <UserControl 
   2:     x:Class="SilverlightTestbed.UserControl1"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Width="400" Height="300">
   4:     <Grid x:Name="LayoutRoot" Background="White">
   5:         <TextBox Width="300" BorderBrush="Blue" FontSize="12" Background="Black" Foreground="White"/>
   6:     </Grid>
   7: </UserControl>

However, if the UserControl contains several TextBox controls that must all look the same it becomes tedious to update each one individually to make its appearance the same; and if the design requirements change then updating each control is even more tedious. So the attributes can be extracted out into a style like this:


   1: <UserControl x:Class="SilverlightTestbed.UserControl1"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   2:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Width="400" Height="300">
   3:     <UserControl.Resources>
   4:         <Style x:Key="TextBoxStyle" TargetType="TextBox">
   5:           <Setter Property="Width" Value="300"/>
   6:           <Setter Property="BorderBrush" Value="Blue"/>
   7:           <Setter Property="FontSize" Value="12"/>
   8:           <Setter Property="Background" Value="Black"/>
   9:           <Setter Property="Foreground" Value="White"/>
  10:         </Style>
  11:     </UserControl.Resources>
  12:     <Grid x:Name="LayoutRoot" Background="White">
  13:         <TextBox Style="{StaticResource TextBoxStyle}"/>
  14:     </Grid>
  15: </UserControl>


Note the following important points:



  • The style has a unique Key
  • The style specifies its TargetType
  • The TextBox.Style property uses the value of the style’s Key

This allows us to set the style on all TextBox controls in the UserControl to the “TextBoxStyle” style and update them all by changing the style in a single place. However, what if we want to apply this style to many TextBox controls throughout the application? If we re-declare the style in every UserControl then once again, if the design requirements change, we must tediously go through all declarations and update it once. We can do this by declaring the style in the App.xaml file.


   1: <Applicationxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"x:Class="SilverlightTestbed.App">
   2:     <Application.Resources>
   3:         <!-- Resources scoped at the Application level should be defined here. -->
   4:         <Style x:Key="TextBoxStyle" TargetType="TextBox">
   5:             <Setter Property="Width" Value="300"/>
   6:             <Setter Property="BorderBrush" Value="Blue"/>
   7:             <Setter Property="FontSize" Value="12"/>
   8:             <Setter Property="Background" Value="Black"/>
   9:             <Setter Property="Foreground" Value="White"/>
  10:         </Style>
  11:     </Application.Resources>
  12: </Application>


Merged Dictionaries

Now we can use it in all UserControls in our project. However, if we need to present the same application with different style themes – for example different corporate branding of the same application – then we have a problem. We need to be able to swap the styles for different customers, but we don’t want to have to edit App.xaml every time and compile a specific version for that customer. The solution is create a separate style assembly that we import in App.xaml. In the style assembly we just have the XAML file(s) for a customer’s branding; the style may be broken up into a file for color definitions, layout, and templates for example, or it may all be in one XAML file. The Build Action for the XAML file(s) will be set to “Resource”.



To use the style in our main app, and our UserControls, we add a reference to our styles project and use the MergedDictionary to make it available as a resource, which looks like this:


   1: <Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    x:Class="SampleNameSpace.App">
   2:     <Application.Resources>
   3:         <ResourceDictionary>
   4:             <ResourceDictionary.MergedDictionaries>
   5:                 <ResourceDictionary Source="/SharedNameSpace.StyleResources;component/BaseStyles.xaml"/>
   6:             </ResourceDictionary.MergedDictionaries>
   7:         </ResourceDictionary>
   8:     </Application.Resources>
   9: </Application>


In the example above, App.xaml loads in the BaseStyles.xaml and merges its content into the main shell’s resource library. This means that any control that the shell instantiates can apply one of the colors/templates/styles defined in BaseStyles.xaml. We could also have multiple style projects (with the same compiled assembly name) that we could swap in and out for different customer skins.



If we have other Prism modules that contain UserControls then we need to add a reference to the styles assembly in those modules also – but we should set the “Copy Local” property for that assembly reference to false since the Shell will already contain that styles assembly.




Resource Hog vs Design Time Support

Our UserControls don’t need to define any styles at all if they are all defined in the styles assembly and merged into App.xaml. We can explicitly import the styles assembly into each user control using exactly the same syntax as shown above for App.xaml, however each time we do that, we are creating another ResourceDictionary with the same content defined. Which means if we have 100 styles defined in BaseStyles.xaml, and we have 50 UserControls each importing BaseStyles.xaml into a ResourceDictionary, that means our application, at run time, has 50 ResourceDictionaries instantiated with a combined total of 5,000 style static resources.



That’s unacceptable so we don’t want to import the resource dictionary in each UserControl, however we still need some way of including it for design time in Blend. At run time the App.xaml ResourceDictionary will be available to all the UserControls that are loaded into the application from various modules, but at design time the UserControls themselves don’t contain any reference to the styles so all controls appear in their default state. Worse than that – some UserControls won’t even display properly in Blend if they use child UserControls that need styles from the styles library.



There is no easy solution to this problem – if we add the explicit MergedDictionary entry (as above) for each of our UserControls then we have design time support, but then we face excessive resource usage. If we don’t have the MergedDictionary declaration in each UserControl then we are keeping our resource usage low, but lose design time support.



One way around this is to have the ResourceDictionary declaration in each user control, but commented out. The declaration can be uncommented while the UserControl is being worked on in Blend, and then uncommented when the design work is complete.



Finally, we get to the bit about merging selected styles when the application loads.




ThemeMerger

The ThemeMerger class described below provides functionality that allows the merged styles to be dynamically handled at start-up. The example in the introduction was that

we may have an application that we only want to install once on the server, but it needs to have different corporate branding depending on which customer is using it. If we have a different virtual directory on the server for each customer then they can have a different URL, or we could create a separate ASPX/HTML page for each customer. With either of these techniques we have the ability to determine at run time which customer we need to style the application for, so we need some way to respond to that knowledge and merge the correct styles. Let’s take the example where we have a separate page for each customer. The page can pass a parameter into the Silverlight application identifying the customer (relevant part emphasized):


   1: <body>
   2:     <form id="form1" runat="server" style="height:100%">
   3:         <div id="silverlightControlHost">
   4:             <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
   5:                 <param name="source" value="ClientBin/SampleNamespace.SampleApp.xap"/>
   6:                 <param name="onError" value="onSilverlightError" />
   7:                 <param name="background" value="white" />
   8:                 <param name="minRuntimeVersion" value="3.0.40624.0" />
   9:                 <param name="windowless" value="false" />
  10:                 <param name="autoUpgrade" value="true" />
  11:                 <param name="initParams" value="customer=Company_A" />
  12:                 <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration:none">
  13:                     <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style:none"/>
  14:                 </a>
  15:             </object>
  16:             <iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe>
  17:         </div>
  18:     </form>
  19: </body>

We want to use this knowledge at the earliest possible moment in the App.xaml since we want our resource dictionaries merged correctly before any XAML has been instantiated into controls. So in the App.xaml we can place the following code in the constructor:


   1: public App()
   2: {
   3:     // We need to work out what style to use.
   4:     if (Application.Current.Host.InitParams.ContainsKey("customer"))
   5:     {
   6:         // merge with correct style here.
   7:     }
   8:     this.Startup += this.Application_Startup;
   9:     this.Exit += this.Application_Exit;
  10:     this.UnhandledException += this.Application_UnhandledException; InitializeComponent();
  11: }

So now we need some mechanism of merging the correct style; which is what the ThemeMerger provides for us. ThemeMerger gives us this mechanism with two steps. The first step is to use an attached dependency property named “SourceLocation” that is designed to be attached to a ResourceDictionary like this:


   1: <ResourceDictionary
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:local="clr-namespace:SampleNamespace;assembly=SampleAssembly">
   5:     <ResourceDictionary.MergedDictionaries>
   6:         <ResourceDictionary Source="/SampleNamespace.StyleResources;component/BaseStyles.xaml"/>
   7:         <ResourceDictionary local:ThemeMerger.SourceLocation="SampleNamespace.StyleResources" />
   8:     </ResourceDictionary.MergedDictionaries>
   9: </ResourceDictionary>

The second step for using the ThemeMerger is to tell it which XAML file to load from the specified namespace. We do this back in the App.xaml.cs constructor, using the value of the parameter we passed in from the html page, setting it to the ThemeMerger.ThemeName static property:


   1: public App()
   2: {
   3:     // We need to work out what style to use.
   4:     if (Application.Current.Host.InitParams.ContainsKey("customer"))
   5:     {
   6:         // merge with correct style here.
   7:         ThemeMerger.ThemeName = Application.Current.Host.InitParams["customer"];
   8:     }
   9:     this.Startup += this.Application_Startup;
  10:     this.Exit += this.Application_Exit;
  11:     this.UnhandledException += this.Application_UnhandledException;
  12:     InitializeComponent();
  13: }

By the time the constructor gets down to the call to “InitializeComponent()”, the application has the correctly merged styles in place. The ThemeMerger has set the Source property on our second ResourceDictionary declaration by combining the namespace with the theme name.



There are two important points worth highlighting here:


  • The value of the “customer” parameter in the initParams is the same as the name of the xaml style filename.
  • Do not include the “.xaml” extension in the initParams, the ThemeMerger will add that.
The last step in the example above is to change the App.xaml file to refer to the StyleResources MergedStyle.xaml file:


   1: <Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   2:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   3:     x:Class="SampleNamespace.App">
   4:     <Application.Resources>
   5:         <ResourceDictionary>
   6:             <ResourceDictionary.MergedDictionaries>
   7:                 <!--
   8:                     App.xaml links to StyleResources.MergedStyles, which carries out the appropriate
   9:                     style overrides prescribed by the StyleResources.ThemeMerger.ThemeName which
  10:                     is set in the App.xaml.cs constructor.
  11:                     When using Expression Blend to edit styles, comment the MergedStyles resource dictionary
  12:                     and uncomment the appropriate style file that you want to work on (or both)
  13:                 -->
  14:                 <ResourceDictionary Source="/SampleNamespace.StyleResources;component/MergedStyles.xaml"/>
  15:                 <!--
  16:                     <ResourceDictionary Source="/SampleNamespace.StyleResources;component/BaseStyles.xaml"/>
  17:                     <ResourceDictionary Source="/SampleNamespace.StyleResources;component/Company_A_Styles.xaml"/>
  18:                 -->
  19:             </ResourceDictionary.MergedDictionaries>
  20:         </ResourceDictionary>
  21:     </Application.Resources>
  22: </Application>

We can leave in the commented out specific declarations for each style file and uncomment them when we need to use Blend to design the UserControls in the ShellView module, and place the following, similar declaration in each UserControl:


   1: <UserControl >
   2:     <UserControl.Resources>
   3:         <ResourceDictionary>
   4:             <!--
   5:                 This ResourceDictionary.MergedDictionaries tag is not required for the view to render correctly - the
   6:                 App.xaml file links to the external style libraries and allows overriding styles for different views.
   7:                 However, to edit this view in Expression Blend, you can uncomment the appropriate resource dictionary
   8:                 that you want to work on (or both if overriding parts). See StyleResources.MergedStyles.xaml for more details.
   9:                 
  10:                 <ResourceDictionary.MergedDictionaries>
  11:                     <ResourceDictionary Source="/SampleNamespace.StyleResources;component/BaseStyles.xaml"/>
  12:                     <ResourceDictionary Source="/SampleNamespace.StyleResources;component/Company_A_Styles.xaml"/>
  13:                 </ResourceDictionary.MergedDictionaries>
  14:             -->
  15:         </ ResourceDictionary>
  16:     </ UserControl.Resources>
  17:     ...
  18: </ UserControl>

The only difference in the declaration for the UserControl is that we don’t link to the MergedStyles.xaml file since that would duplicate the style resources.



So finally, here is the code for the ThemeMerger class:


   1: namespace SampleNamespace
   2: {  
   3:     using System;  
   4:     using System.Windows; 
   5:     
   6:     /// <summary>  
   7:     /// Allows a theme to be set at run time. To use this class do the following:  
   8:     /// 1) Set the static ThemeName property as early in the application as possible  
   9:     ///   (before the app.xaml.cs calls InitializeComponent()). The Theme name is the  
  10:     ///    name of the xaml file that overrides the base styles (the first file mentioned in 3)  
  11:     /// 2) In App.xaml, define a merged dictionary pointing to a single xaml file  
  12:     /// 3) In the xaml file link to two merged dictionaries: the base xaml file that  
  13:     ///    defines the styles for the default appearance of the app; and a merged dictionary  
  14:     ///    that does not have it's Source property set, but has the attached ThemeMerger.SourceLocation  
  15:     ///    property set to the namespace of the assembly with the resource file to merge.  
  16:     /// </summary>  
  17:     
  18:     public static class ThemeMerger  
  19:     {      
  20:         /// <summary>      
  21:         /// Dependency Property for the Active attached property      
  22:         /// </summary>      
  23:         public static readonly DependencyProperty SourceLocationProperty =
  24:             DependencyProperty.RegisterAttached(
  25:             "SourceLocation",
  26:             typeof(string),
  27:             typeof(ThemeMerger),
  28:             new PropertyMetadata(string.Empty, OnSourceLocationChanged));
  29:         
  30:         private static string themeName = string.Empty;
  31:         
  32:         /// <summary>      
  33:         /// Gets or sets the theme name to use. The ThemeName should be just the name of the      
  34:         /// xaml file, without the .xaml extension.      
  35:         /// </summary>
  36:         public static string ThemeName      
  37:         {         
  38:             get { return themeName; }
  39:             set { themeName = value; }
  40:         }     
  41:         
  42:         /// <summary>      
  43:         /// Accessor method for SourceLocation Attached Property      
  44:         /// </summary>      
  45:         /// <param name="resourceDictionary">The DependencyObject the property is attached to</param>
  46:         /// <returns>The namespace of the assembly the xaml file is in</returns>
  47:         public static string GetSourceLocation(DependencyObject resourceDictionary)
  48:         {
  49:             return (string)resourceDictionary.GetValue(SourceLocationProperty);
  50:         }     
  51:         
  52:         /// <summary>      
  53:         /// Accessor method for Active Attached Property      
  54:         /// </summary>      
  55:         /// <param name="resourceDictionary">The DependencyObject the property is attached to</param>
  56:         /// <param name="value">The namespace of the assembly the xaml file is in</param>
  57:         public static void SetSourceLocation(DependencyObject resourceDictionary, object value)
  58:         {
  59:             resourceDictionary.SetValue(SourceLocationProperty, value);
  60:         }
  61:         
  62:         private static void OnSourceLocationChanged(DependencyObject resourceDictionary, DependencyPropertyChangedEventArgs e)
  63:         {
  64:             if (string.IsNullOrEmpty(ThemeName))
  65:             {
  66:                 // no override theme selected
  67:                 return;
  68:             }
  69:  
  70:             if (string.IsNullOrEmpty((string)e.NewValue))
  71:             {
  72:                 // theme overriding not active
  73:                 return;
  74:             }
  75:  
  76:             // set the source of the resource dictionary to the specified theme
  77:             ResourceDictionary dictionary = resourceDictionary as ResourceDictionary;
  78:             if (null == dictionary)
  79:             {
  80:                 return;
  81:             }
  82:             Uri uri = new Uri("/" + e.NewValue + ";component/" + ThemeName + ".xaml", UriKind.RelativeOrAbsolute);
  83:             dictionary.Source = uri;
  84:             return;
  85:         }
  86:     }
  87: }

Please leave a comment if you find it useful.


Sunday, March 14, 2010

A Scalable Orb Panel-Button-Thingy

I think I may have mentioned before that I'm not much of an artist - thankfully the internet is chock-full of inspirational ideas. When browsing around some Photoshop tutorial sites, I came across an avatar of enzudesign on a forum; the avatar looks like this.

That's a cool looking button. So credit where credit's due - enzudesign is a talented artist. I wanted to try create something similar in Silverlight that would work as a button, following the same kind of process that I used for the Radio Button I did recently. This one has a LOT more content to it. My first attempt at it required 14 ellipses; 11 of those requiring a blur effect. It also meant containing those ellipses in about 18 proportional columns and rows. It looked nice, but performed poorly. I suspect all those blurs were chewing through a lot of CPU. I don't know how much CPU effort it takes to update those proportional values into real values when the whole thing is animating, but I'm guessing it contributed to the poor performance. This is what it looked like with and without the blur effects:


The blur gives the chrome surrounding ring a rounder appearance, and makes the black look more "rubbery". In comparison, the image without the blur looks stark. I guess this is quite subjective, but to my eyes, the blurred button looks nicer. But it's performance is terrible. I turned it into a button template and the transitions from Normal to MouseOver were not smooth at all. I've included a (collapsed) copy of the above design in the download. So it looked great but it had to lose some detail.

The result, after trimming it down, is embedded below, but I wanted to do more with it than just use it as a button. It occurred to me that this design would make a nice border for a panel; cut it into quarters and you have the corners. All it would need is filling in some horizontal and vertical banding to join the corners. "All it would need" actually turned out to be somewhat fiddly, but before I bore you with the details, the effect I was after needed to have two features:
  1. The button itself would scale nicely, keeping proportion where it needed to
  2. The transition from round orb to rounded panel should be as seamless as possible.
Here is what the "finished" product looks like (download link at the bottom of the post):


The button now consists of only 10 ellipses; 4 of those with blur effects. I won't go through the whole process of compositing the button since that was covered here. But I will quickly cover the process I used of turning an ellipse into a rounded panel.

Setting up the Panel Grid and Corners

I wanted a panel where the corners stay the same and, as the panel grows or shrinks, the "filler" between the corners would expand or contract to fill the gap. The first thing to do was to work out how to get just the appropriate part of each corner to show. I played around with a few options such as using a clipping path, an opacity mask, or using the Path | Subtract tool to convert the ellipses into just the quarter that was needed. These approaches all had their own problems: I don't like clipping paths because they are high-maintenance to animate in storyboards; the Opacity Mask has similar problems since it essentially just a brush that covers the whole object; using the Path | Subtract tool gave the right shape, but the blur effects and the gradients were now applied to just that quarter and proved too fiddly to work with. This was compounded by the fact that the button is not symmetrical across all four quarters.

In the end I discovered that if I confined the whole thing into a grid cell, but sized it so that it was twice as wide and twice as high as the cell, then the Grid automatically clipped out everything except the bit that fitted in the cell. So the here is what the containing grid looks like with the four corners in place:


The first and last columns, and the first and last rows, are each set to a fixed width/height (in this case 50 pixels). The middle columns and rows have their width/height set to "1*" to take up all the gap between the fixed rows and columns. Each corner has an entire copy of the "button" shapes sized to 100x100 pixels, and has it's horizontal/vertical alignment set to the appropriate values for each corner.

Although this provides the corners that I need, I'm not entirely happy with this approach because I can't easily animate the column width and height of the corner cells if I want the radius of the corners to change. In the embedded Silverlight app above, it is the button itself that is changing radius - not the panel grid. I would like to come back and address this at a later date to work out how to achieve this more easily. I have in mind that creating a custom control with template parts for the corners and horizontal fills would work - I'll have to experiment when I get the time.

The Fillers

The next stage was to create the fills between the corners and in the middle. For the top and bottom fills, this was achieved by copying the whole button structure, changing the ellipses into rectangles, and removing the columns from the containing grid to make the shapes span the whole grid. I had to change into the XAML view to change the ellipse tags to rectangle tags.

The resulting grid is sized the same as the corner shapes (100 pixels high) and restricted to the middle top grid cell to clip out the bottom half.

The same result is copied and pasted for the bottom fill and placed in the middle bottom cell.

The vertical fills are done essentially the same except there were a couple of areas that were a bit trickier because the button is asymmetrical vertically. I was able to remove a few of the shapes for the vertical fill because it's mostly just black between the chrome ring and the blue orb center. The trickiest part was the center blue part of the orb. The bit that goes from the the left side of the blue center to the right side of the blue center. For the other rectangles I just tweaked the existing gradients, but for the center rectangle I created the gradient from scratch using the color picker tool to get a selection of colors across the width of it.

The last part of the grid was straight forward, it is just a rectangle with a solid color fill. It stretches horizontally and vertically to fill the middle space.

The only other thing to point out is that I had to set the margins of the horizontal and vertical fill edges that met the corners to -3 so that there was no obvious seam. If you look hard you can still make out where the parts meet, but I don't think you would notice them unless you were specifically looking for them.

The Animation

The animation is somewhat of a cheat. It looks like the button shrinks, and then expands into a panel containing the buttons. There is actually two groups of controls here: the button is shrinking down in size, but then it's opacity animates to 0 to reveal the panel behind it (in exactly the same location). It is then the panel that grows to reveal the fillers, and then displays the buttons inside it. The whole process is reversed when the panel is closed.

I really like the end result, but I would like to come back and revisit this later to make the whole thing easier to replicate with a different design.

Download, including source, from here.

Wednesday, March 10, 2010

Animating the Silverlight Opacity Mask

I used to use Adobe Flash a few years ago. Not much - just enough to get a couple of little projects done. Silverlight competes with Flash and Flex. That may or may not be the official Microsoft position - but it is true nevertheless.

Most of my work involves creating corporate level Silverlight apps. There is still much room for creativity, but only to a point. Animations are usually restricted to a logo, or transitioning between screens etc. So when I get the time to play with the more creative side of Silverlight development I often think about the artistic tools that were available in Flash.

In the context of animation, one of the biggest features in Flash that is missing from Silverlight 3 is an Opacity Mask layer. I haven't seen any mention of this in to Silverlight 4, but I could be mistaken. The only feature that we have in Silverlight 3 is the OpacityMask on each element, which is either a Brush or an image - (Image Brushes don't tile either, but that's a post for another day).

An Opacity Mask "layer" allows for animation, so you can do things like a moving spot light revealing parts of an image, or have a foggy border animating around something.

An Opacity Mask "Layer" Behavior for Silverlight

I've written a Behavior for Silverlight that lets you turn a FrameworkElement into an opacity mask for it's parent container. Before I discuss the code, let's have a look at it in action:


You can grab the source here.

The behavior is attached to a UIElement - for example, one of the containers in the demo above looks like this:

The grid named "Island" holds a border with an ImageBrush for it's fill. It has a child grid called "SpotLightMask" with the ElementMaskBehavior attached.

The ElementMaskBehavior will turn the whole "SpotLightMask" grid into an opacity mask (at run time) that will be applied to the parent "Island" grid.

As the ellipse is animated, the opacity mask is updated. As the SpotLightMask layer is turned into the opacity mask, it's flattened image's opacity is inverted, so if an area on the mask grid is transparent, it will mask out the parent layer. If an area on the mask grid is opaque it will allow the parent layer to show through. This means that you only have to add and animate the part of the mask that you want the image to show through. This is generally easier than creating a shape and cookie-cutting a mask out of it.

The ElementMaskBehavior has the following property:

The UpdateBehavior exists because the behavior can use up a lot of CPU power if it is left to continuously update the opacity mask. The property can be set to one of the following values:
  1. Disabled - no mask is generated.
  2. SingleUpdate - the mask is generated once and not updated after that unless the UpdateBehavior property is changed to one of the following two values.
  3. ContinuousUpdate - the mask is continually updated. This may max out your CPU depending on your computer's ability, so if you set the UpdateBehavior to this value, you may want to restrict the frame rate of your Silverlight app.
  4. LinkedToHitTestVisible - the mask will be updated while the mask layer has it's IsHitTestVisible property set to true. This option is discussed in more detail below.
Performance issues

The behavior works by attaching an event to the CompositionTarget.Rendering event and checking to see if it should update the opacity mask as each frame is about to be rendered. For a static opacity mask, the SingleUpdate is all I need. But if I'm animating the opacity mask, I don't want to have the behavior to continue demandanding so much CPU usage after the animation is finished. Ideally, I would like the Storyboard to change a property on the behavior itself to turn it on and off during the Storyboard playing out. Unfortunately in Silverlight 3, you can only animate properties on a FrameworkElement object.

Since the layer being used as a mask is not going to be visible anyway, I've provided the LinkedToHitTestVisible enumeration value that synchronises the updating of the opacity mask to the checked state of the IsHitTestVisible property on the behaviors associated object. The IsHitTestVisible property can be changed during a Storyboard so it provides a behavior controlling mechanism. It's a compromise, but it's the best solution I could find.

So how does it work?

How it works

The behavior itself is pretty small. There are only three methods that do anything worth discussing. The first method is the LoadedEvent handler for the behavior's associated object:

The code attaches an event handler to the SizeChanged event of both the parent and the associated object - if either of these objects changes shape we should regenerate the mask. The other event it hooks up is the CompositionTarget.Rendering event, which is called as each frame is about to be rendered. That handler looks like this:

This code calls the method to attach the opacity mask (if it's not already attached) and then checks the UpdateBehavior property. If the value of the UpdateBehavior property allows it, the method clears the mask and re-renders it using the associated object.

The AttachOpacityMask method looks like this:

The attached object is moved out of view of the parent's masked content and then used as the source for an ImageBrush applied to the parent object's OpacityMask. A WriteableBitmap is used as the source for the ImageBrush, and it is that WriteableBitmap that is updated during the Rendering event.

Improvements

There is plenty of scope for enhancing this behavior. If I get the time I would like to add a property to allow the mask to be inverted. I understand that in Silverlight 4 binding both ways on DependencyProperties will be allowed down to the DependencyObject level instead of just down to the FrameworkElement level. If this flows through into being able to animate such properties then this will allow me to get rid of the link to the associated object's IsHitTestVisible property and just animate the UpdateBehavior property directly.

Source here.

If you like this please leave a comment. Any suggestions for improvement are welcome also.

Saturday, March 6, 2010

The pros and cons of UserControls

An anonymous commenter pointed out some problems with using a UserControl to build the Watermarked Textbox user control. I don't think the commenter read the whole post, but he points out some things that, perhaps, I should have discussed at the start of the first post. As I wrote in the very first post, there are two approaches to building the Watermarked TextBox control - a UserControl or a Templated Custom Control. Each approach has it's advantages and disadvantages.

It is worth while going over these advantages and disadvantages in more detail.

User Control

The advantages of using a User Control are mostly to do with being easier:
  1. The whole thing can be done easily in Blend, the small amount of code required can be written in the Blend IDE even though it is a poor cousin of Visual Studio for writing code.
  2. It is an easy to understand approach for beginners; no special attributes required and simple code.
  3. Quicker approach than creating a Templated User Control. For a one-off project that doesn't require building a flexible control that can be reused widely, a UserControl is certainly faster.
The disadvantages of using a User Control are generally to do with reuse and customizability. One of the wonderful things about Silverlight/WPF is the separation of presentation and functionality - for example a Button's functionality has very little to do with being a gray rectangle with a faux-3D border effect.

So the disadvantages, if you are after a reusable control, are:
  1. Harder to customize. Everything that you think you may want to style or localize needs to be re-exposed on the User Control. We only exposed the Text and Watermark properties, and a Style for the TextBox. This is not acceptable for a reusable control and the amount of work required to do so is such that we would be better to go down the Templated Custom Control path.
  2. Not very reusable. Following on from the first point, to use the same control in a second project will probably require changes to the UserControl itself; that's a pain.
For the purposed of my tutorial though, the UserControl was a useful approach to cover some basic techniques such as using states, dependency properties, and binding.

Templated Custom Control

A Templated Custom Control, when done well, is easy to use and easy to customize. From the both the point of view of a tutorial, and for use within a project though, there are some disadvantages. But first the advantages:
  1. The end result can be easily styled for each project that uses it since it has proper separation of functionality and presentation. This is a very important point and the only reason I used a UserControl in our project was because it was a one off that I could style how I needed it for that particular project.
  2. It is less effort if the end result needs to be fully localized - exposing every single property on a UserControl that may need localization is tedious.
  3. Validation is possible - we didn't need validation for our UserControl TextBox in the project I was working on because it was just a filtering text box to reduce the result set that was displayed in a list. But for use in a form requiring input validation, a Templated Custom Control is the correct approach.
And the disadvantages:
  1. Steeper Learning Curve. There are special requirements your code should meet if you are going to write a custom control; especially if you want it to play nicely with Blend. These are not enormously difficult, just one more thing that needs to be learned. If your needs are simple, and you are just learning Silverlight, then the extra learning curve can be daunting; especially considering the next point.
  2. The information available on creating custom controls is somewhat sparse. It is out there, but you may need to visit several sites to find everything you need. And you will want to look at quite a bit of source code from both the .Net framework (Reflector is your friend) and the excellent Silverlight Toolkit to get a good understanding of what is required.
So if you need a solution that is only going to be styled once, for a single project then there is nothing wrong in using a UserControl. Purists will still shake their head and mutter because things should be done the right way even for one-offs - but if you are working to a deadline then sometimes you need a simple and quick solution.

It's also a good learning exercise and a useful introduction into why a Templated Custom Control is a better approach. As stated from the beginning of the first post, I plan to come back to this topic in a later post and redo this control as a Templated Custom Control.

Thursday, March 4, 2010

Watermarked Textbox Part III

This is the third and final post on creating a Watermarked TextBox UserControl. The first two parts can be found here and here. In those posts we created all the visual elements, including the animations we need.

The final version can be downloaded here. I finally worked out the easiest way to embed silverlight apps in the blog so here is the finished working demo app:


In this post we will look at the code we need to make it all work. Although writing code is easier in Visual Studio, we will do it all in Blend since we have been working in there anyway. Be prepared; this post doesn't have much in the way of graphical content - it's pretty much all code.

Dependency Properties
Since we have buried the TextBox control inside a UserControl we need to expose a couple of properties to make this reusable. Although we could do a thorough job and expose quite a few properties of the TextBox control, I just want to expose two properties for now: the Text property and the Style property.

There are plenty of other tutorials on the web about Dependency Properties so if you are not familiar with them then it would pay to do a bit of reading later on; but for now all you need to know is that the we are going to use them to expose properties on our user control.

The Text property

Open up the WatermarkedTextbox.xaml.cs file and you should see the current code behind. The first property to declare looks like this:

This declares a bindable dependency property on our UserControl that another consuming UserControl can use to set the text content of our embeded TextControl. So if you placed the control on another UserControl and set it's Text property, the static method TextChangedHandler will be called for us to respond to the new text value. The TextChangedHandler method looks like this:

The above static method ensures that the change to our UserControl's text property gets passed on to our embedded TextBox control. If the new text is not empty then we go to the WatermarkHidden state, and the ButtonVisible state. We still have to handle the case where the user types into our embeded TextBox; we need to update our UserControl's Text DependencyProperty. We cant bind the TextEntry.Text property in the XAML against the UserControl's Text property because we would have to give our UserControl an "x:Name=..." attribute, and you can't name UserControls if you want to use more than one instance on the same parent UserControl; otherwise you get a nasty error that stops you Silverlight app dead in it's tracks.

So we are going to handle the TextChanged event on our embeded TextEntry TextBox. To do this, select the TextEntry TextBox control and on the Properties page select the "Events" button in the top left corner. You will see all the events that the TextBox control exposes. Double click the "TextChanged" event and Blend will create a method stub in our code-behind called "TextEntry_TextChanged". The code we need to write here looks like this:
This method does two important things: it updates our UserControl's Text DependencyProperty and it updates the states that the UserControl needs to be in. So now our Text property updates and is updates in sync with the TextEntry TextBox's Text property. Consuming UserControls can use our UserControl's Text property the same way they would have used the TextBox's Text property (including binding).

The Watermark Property

The watermark is a little easier. It's not an editable control so we only have to update it in one direction. The declaration for the DependencyProperty and UserControl property looks like this:
And the WatermarkChangedHandler is as follows:
The TextBoxStyle Property

The last Dependency Property to add is the Style property. This will allow users of our FilteredTextBox UserControl to supply a style to change the appearance of the text box. If we were going to be really thorough we would also provide properties for the button and the watermark too - and there is nothing stopping you from adding that functionality yourself!

Here is the DependencyProperty declaration for the Style property:
Pretty straight forward by now. And you can probably guess what the TextBoxStyleChanged method will look like too:
The Last Events

There are three more events that we need to write code for. Back on the design surface select the TextEntry control and go to the Events page of the Properties panel again. Create an event handler for both the GotFocus and LostFocus events. The code for these events is going to make sure the Watermark disappears and appears at the right time. It requires us to use a flag to keep track of when our TextBox has the focus. The flag declaration and method handlers look like this:
And the last event to handle is the user clicking on our ClearFilterButton. Use the same technique as above to create an event handler for the Click event on our button. The code looks like this:
And we're finished!

The download for this control also has support for Commanding, which will only mean something to you if you are using the MVVM pattern, in which case the extra code will probably be straight forward for you anyway - so I won't go over it here.

Summary

So you now have a working, reusable UserControl that gives you the functionality of a Watermarked TextBox control. Back in Part I of the series I said we could go about building this control two ways: as a UserControl or as a templated custom control. The most flexible way would have been as a templated custom control - and I hope to do a series on this at a later stage - but the easiest way is using a UserControl.

Note : for those of you who have already downloaded the control, I have updated the file on Microsoft Expression Gallery, and the alternate download location. I noticed an incorrect binding I had left on the TextEntry text box, which is now removed.

Feel free to use this control for your own projects - if you like it, please leave a comment below!

Download full source code here or here.

Tuesday, March 2, 2010

Watermarked TextBox Part II

In the first post we created the visual elements for our WatermarkedTextbox user control. In this post we are going to add some code, create a few more states, and wire everything up.

Remember that you can grab the finished control from here.

You can view a working sample on the Expression Gallery here.

Before we add any code-behind, we'll create two state groups - one for showing or hiding the watermark, the other for showing or hiding the clear-text button.

Select the States tab and click the Add State Group button. Name the new state group "ClearFilterButtonStates", since this group is going to hold the states for the ClearFilterButton.

Before we add a state for the button, lets set the default appearance of the ClearFilterButton. Since the default text in the TextBox will be an empty string, we don't need to show the ClearFilterButton until there is some text to clear, so set the Opacity of the ClearFilterButton to 0. I want the button to animate as it appears; expanding from a dot until it reaches it's full size. To make the ClearFilterButton start out as a dot, go to the RenderTransform group on the Properties panel, click on the Scale tab and set the X and Y values to 0 as shown here:


Now click the Add State button on the ClearFilterButtonStates line and name the new state "ButtonVisible". Add another state named "ButtonHidden".

Select the "ButtonVisible" state (Blend will show the red border to indicate it is recording property changes for the selected state) and then select the ClearFilterButton. Set it's opacity to 100, and it's X and Y scale render transform values back to 1. We want the button to animate, rather than appear all at once, so let's change the transition time value for the whole ClearFilterButtonStates to 0.3:


Click on the "Turn on transition preview" button in the top right of the States panel, select the [UserControl] element in the Objects tree view and click back and forth between the ButtonVisible state and the ButtonHidden state.

Select the "Base" state group so any changes we make next are not just recorded for a state. The button is a little to close to the edges, so lets give it some room by setting it's Right, Top, and Bottom margin to 3.

Now we will create the states for the watermark in much the same way. Create a new state group called "WatermarkStates" and add two states called "WatermarkVisible" and "WatermarkHidden". We don't need to do anything for the WatermarkVisible state, so just select the WatermarkHidden state, select the WatermarkText control and set it's Opacity to 0. Also, expand the Common Properties group on the Properties panel and turn off "IsHitTestVisible" - we don't want it interfering with the text box interaction.

We want the watermark to disappear straight away when the user clicks in the text box, so we won't set a group level transition time for the Watermark States, but it would be nice to have it fade in when the text box becomes empty. Click the Add transition button on the WatermarkHidden state and select the "WatermarkHidden -> WatermarkVisible" option:


And set the transition time to 0.4:


Now we are ready to add some code to put make the whole control work.

This post has already gone longer than I thought it would, so I'm going to describe the code in Part III.

Monday, March 1, 2010

Watermarked TextBox Part I

The project I'm currently working on has several views that contain lists of things in a DataGrid. We have a tool bar above each table in which we placed a label (that said "Filter : ") followed by a text box for entering text that would cause the list of items to be filtered to only those items containing the filter text, in one of a couple of the displayed columns.

That extra label was taking up some precious screen real estate so it had to go. What we really wanted was a TextBox with a watermark that disappears when it get's the focus or when it contains text. It would look like this when it is empty:

Because it is a filter text box I want a quick and easy way to clear it too. I don't want to have to click in it, select the text, and press the delete key just to clear it. So I want to add a button inside the text box that clears the text when the button is clicked. But I only want the button to appear when there is text inside the text box. It would look like this with text in it:


There are two ways to go about this: Create a UserControl, or create a templated custom control. In this post, and the next, we will create a UserControl since it is the easiest. I want to come back another day to do the templated custom control

Part I
In this first post we will create all the visual elements. The second post will be about creating some properties for binding, and the code to support those properties. The final result can be downloaded from here.

Start a new project in Blend. Select the "Silverlight 3 Application + Website" template. With your new solution, right click the first project in the solution and select "Add New Item...". Select "UserControl" if it's not already selected, give it the name "WatermarkedTextbox" and click OK.

In the Objects and Timeline panel, select the "UserControl" item at the top of the tree view. By default Blend starts you off with a design time size of 640 by 480. To set this to something more practical for this control (200 by 24 should be fine) just grab the design-time resize handle and drag it towards the top left corner of the box - you should see the Width and Height properties in the Properties panel change as you drag it. Stop dragging when the Width and Height end up at 200 and 24 respectively.

First though, we need to add some columns to the LayoutRoot grid. Select the "LayoutRoot" grid in the Objects and Timeline panel, and in the Properties panel, click on the expand button at the bottom of the Layout group.

This will show some more layout properties including "ColumnDefinitions (Collection)". Click the button with the ellipses next to this property to bring up the ColumnDefinitionsCollection Editor.

Use the editor to add two columns. Leave the width of the first column (it should be set to 1 Star), but set the width of the second column to 17 pixels. That second column is going to hold our button for clearing the text from the TextBox.

So now with our two columns in place we are ready to add our controls.

Select the TextBox icon from the text controls flyout on the Blend Tools toolbar (usually on the left of the screen). You can get this flyout by clicking and holding the displayed text control icon as shown in the picture.

Once it's selected, double click it. This will add a new TextBox control to the Layout grid since we had the grid selected when we double clicked. When the TextBox appears Blend swaps into edit mode and has the text "TextBox" already selected and ready for editing in the new TextBox control. Just hit escape for now to leave edit mode. We'll come back later and change that.

Now, with the TextBox control selected in the Objects tree, use the Properties panel to change the default value of HorizontalAlignment to "Stretch", and ColumnSpan to 2. You should end up with a design surface that looks like this:


Now we want to add our watermark. Select the "Layout" grid again, select the "TextBlock" control from the text flyout, then double click the TextBlock icon to create a TextBlock in the grid. Again, Blend will enter edit mode for the TextBlock control so just click escape to exit - we'll come back to this control later.

Blend will have automatically set some values in one or more of the Margin property values for the new TextBlock. Set all of the margins back to 0 except for the left margin - set that to 3, and change the VerticalAlignment to Center. We want the appearance of the watermark to be a little different than the TextBox text, so in the Text group on the Properties panel, click the italics button on, and up in the Brushes group set the Foreground color to something like #FF868686.

Now we can create the button to clear the text from the TextBox. We are going to make it out of two controls; an ellipse and an "x" that we convert to a path. First, select the LayoutRoot grid again, then select the ellipse from the shape flyout on the Tools toolbar and double click it to create a new ellipse. Again, reset all the margins back to 0. Change the Column property to 1, the Width to 14, the Height to 14, the Fill Brush to #FF787878, and the Stroke Brush to #FF494949.

Select the LayoutRoot grid again and add a TextBlock. Change the text of the new TextBlock to lowercase "x" (remember that to exit the text editing mode press escape, not enter) and set the font size to 8. Right click the TextBlock and select "Path" -> "Convert to Path" to convert the text into a path shape. Set all the margins to 0, the Width and Height both to 5, the Horizontal and Vertical Alignments both to Center, the Column to 1, and the Fill color to white.

If you select the LayoutRoot grid again, you should have something like this:


Now select both the Ellipse and the Path in the Object tree (hold Ctrl down to select multiple items). Right click the selected controls and select "Group Into" -> "Grid", to wrap the two controls into a grid. Doing this will have set some default Margin values on the grid so reset them all back to 0. Now right click the new grid and select "Make Into Control...". The Make Into Control dialog will appear with a list of controls to choose from. Select the Button control and give it the name "ClearFilterButtonStyle" and click OK to turn it into a button style.

Blend has now put us into the template of the button style. The top left of the design surface shows three connected boxes showing us where we are editing. The button style is essentially a collection of property values that can be applied to a button. One of those properties is called "Template" which accepts a ControlTemplate. Blend has turned our grid containing the ellipse and path into a ControlTemplate for the button style. Part of that style includes a collection of states, which allow us to change the appearance of parts of our template as things happen to the button, such as when the mouse rolls over it. Don't worry about the "Button" text that has appeared, we will clean that up soon.

If it's not already showing, select the "States" tab. We want to change the appearance of our button to make it interact nicely with the user. Select the "MouseOver" state. A red border will appear around the design surface letting you know that Blend is recording property changes that are only applied when the control is in the MouseOver state. Now select the ellipse and change it's Fill color to #FFBD7777 and it's Stroke color to #FFD63030.

Right click the MouseOver state label and select "Copy State To" -> "Pressed". Select the "Pressed" state and change the path's Fill color to #FFD63030.

To exit editing the template, click the [Button] connected panel at the top left of the design surface, or click the "Return scope to [UserControl]" button at the very top of the Object tree panel. Now we are back editing the user control, clear the text from the Content property of the new button.

To finish up this post, let's name a couple of the controls. If you select a control in the Objects tree, and then single click it's name you can edit it. Change the TextBox name to "TextEntry", the TextBlock name to "WatermarkText", and the button to "ClearFilterButton".

In the next post, we will add some code in the code-behind file to make this user control actually do something.

You can jump ahead and grab the complete solution here if you want to.