Creating a Custom Skin for Silverlight 2's Button Control

For the past couple of weeks I have been reading about Silverlight 2.  I have gone through ScottGu's tutorial, checked out the deep zoom demos (here and here) and even browsed the XAML for some sample control themes.  But I haven't actually tried to build anything myself yet.  So I thought I would put together a small sample around Silverlight's styling and templating features by creating a skin for the Button control that looks somewhat like the a button renders in IE7 on my Vista box.  I know that in a way it's taking a step back (Silverlight's default Button is really pretty sweet looking), but I didn't want to be too overwhelmed for my first sample. 

The grid below shows what my Button control looks in each of the 4 states the Button supports.  If you look close at the Buttons background, you can see it has that nice glassy look that we used to use images for when working with HTML.  Besides that bit of flare, the rest of the Button is primarily made up of one of the most basic Silverlight elements - the Rectangle (one for the background, one for the glassy overlay and another for the focus dashed border).  In addition to the Rectangles, my custom Button skin is also configured to animate the Button as the user mouses over, clicks or disables the Button.  Below are some screen shots of what my custom Button looks like in a few different states.

image

Live Demo | Download (Requires VS 2008 Plus Silverlight 2 Beta 1 Tools)

 

Customizing the Button's Control's Appearance

Before we can start skinning the Button control, we need to understand what the Button's visual customization points are - this way we can tap into them to control how it renders. 

Using in-lined styles

Much like ASP.NET controls, the Buttons appearance can be customized by setting the buttons attributes via the markup.  So if you want a blue button, just set the Background attribute to the value Blue. 

image

image

Using style elements

That should feel pretty natural for most UI developers.  But of course in-lining styles is hard to maintain and gets messy pretty fast.  So instead we could move these inline settings to the Resources collection of the Application to encapsulate these settings as a reusable resource - ala CSS.  To do this, I have added a Style element to the Application's nodes Resources collection.  Within this Style element, I have set the Background property to Blue as follows ...

App.xaml

image

Page.xaml

image

image

Again, this pattern should feel pretty familiar if you are comfortable with CSS.  Instead of in-lining styles, you just point to the resource that knows what style elements you want applied.

Completely replacing a control's visual appearance while still keeping the original behavior

And now to the really cool part.  With Silverlight and WPF we can customize just about every aspect of how the Button control renders.  ScottGu's tutorial on using control templates has some pretty interesting examples that show the full flexibility of this technique by embedding a fully functional calendar control within the Buttons content (the screen shot below is from his blog).   

image

And along with allowing us to add other shapes and controls to the Button's template, the Button control also supports hooks that allows us to declaratively specify how our button should look as it as it passes through the following four states: {Normal State, MouseOver State, Pressed State, Disabled State} as well as when the button currently has focus.  I found the following description from MSDN especially helpful in explaining this. 

When you create a new template for a control, you redefine its visual structure and visual behavior. Is some cases, the code of a control might refer to parts of the control's ControlTemplate. For example, the code might call a method on an element that is in the template to perform some functionality. This means you must understand how the template and the code relate to each other. That relationship is described by the control contract, which is an agreement between the logical and visual parts of the control. - MSDN

And I think I get this - the code for the control needs to know a little something about the items in the template for it to function properly.  If you want the background color to change from red to blue when the button is hovered over, you need to let the Button know.  Or when my control currently has focus, the Button can make sure the dashed rectangle shape is displayed.  So after learning about these additional customization points I went to the Button control's  Style and Templates page on MSDN where I found a description of the elements and StoryBoard (used for playing animations) the Button control looks for within the ControlTemplate.  So if you are creating a template for the Button and want a certain animation played when the Button is hovered over - make sure you include a StoryBoard item within the template with the name MouseOver State.  Below is a table that summarizes these additional elements that make up the Button controls public interface.

image

These additional bits of information are call Template Parts in the documentation.  And if you go to the MSDN page for the Button control, you will notice these items are specified as part of the documentation for the Button class.  I don't think you would get to far creating a control skin if you don't understand what Template Parts a control defines.

image

 

Creating the Shapes that Define the IE Button Skin

And now with some of the background information out of the way, we can start creating our IE button skin.  

Step 1: Add the RootElement, background Rectangle and the ContentPresenter

If we look back to the table above, the Button's ControlTemplate looks for two elements within the control's template: one with the name RootElement and the other with the name FocusVisualElement.  The RootElement is the element that contains all of the visual elements for our control - so this is where we will start.  I have used the Grid control for the layout of my Button and the first child contained within the Grid is a Rectangle (with rounded corners natively supported!) that is filled with a light gray color.  After the Rectangle I have added a ContentPresenter that handles rendering the content portion of the Button.  I have used markup extension syntax to bind the properties of the control to the ContentPresenter (I am still wrapping my head around exactly what a ContentPresenter is and isn't.  When I figure that out well enough so that I can explain it I will no doubt include a post about it). 

image  

With just this single rectangle, the button displays like this:

image 

Step 2: Add the glassy-look by overlaying a semi-transparent Rectangle

Next, I added another Rectangle to the template that sits on top of the Background Rectangle.  This second Rectangle is responsible for providing us with the nice glassy effect.  To achieve this I added a second Rectangle to the markup (unless I am mistaken, Z-Order is the order in which the items are added.  Because the Background Rectangle is added first it will have a lower Z-Order).  And instead of adding a semi-transparent image, instead I filled this rectangle with a gradient brush - lighter on the top, darker on the bottom.

image

With the second Rectangle added to the template, the button now displays like this:

image 

Step 3: Add the FocusVisualElement Rectangle

Next, I added another Rectangle to the template that handles applying the dashed shape just inside the Buttons border when the button currently has focus.  Again, I added this Rectangle after the first two so it has the highest z-order.  When the button has focus, a white dashed border is applied to the control.  I didn't find this specifically mentioned in the documentation, but all of the examples for skinning the button had the FocusVisualElement initially set to the Visibility=Collapsed state.  I haven't verified this, but I believe when the Button determines it has focus it updates the Visibility value to Visible causing the dashed Rectangle to be displayed.   

image 

Step 4: Add a Rectangle that overlays all of the other shapes

Finally, to handle the disabled button state, I have added yet another Rectangle to the very end of the template (giving it the highest z-order).  I initially set the Opacity to 0 making it invisible.  When the button enters the Disabled State I will use a StoryBoard to update the Opacity value to 1, placing it on top of all of the other Rectangles.

image

Step 5: Use StoryBoards for animating state transitions 

The last step in building our IE skin is attaching the animations that will be run as the button passes through the 4 well-known button states mentioned earlier.  Here is a quick summary of what actions need to be taken as the Button passes through these states:

MouseOver State
  • Change the color of the Background Rectangle to Blue
image
Pressed State
  • Change the Background Rectangle to Blue
  • Make the gradient Rectangle slightly see through by setting the Opacity to 0.7 (this will make more of the Blue color show through)
  • Thicken up the border
image
Normal State
  • Make the Background Gray, 
image
Disabled State
  • Render a grayed out Rectangle over the control
image

 

If you notice, when the button changes between the various states, I want to update properties on a few of the embedded Rectangles.  To achieve this, I have added 4 StoryBoard nodes to my template - a different StoryBoard for each of the Button states.  The MouseOver StoryBoard is the simplest one - it runs when the user mouses over the RootElement.  This StoryBoard uses the ColorAnimation object to change the Color property of the Brush that fills the Background Rectangle from gray to blue.  Here is the markup for this StoryBoard ...

image

A few things to notice:

  • The value of the StoryBoard's Key attribute is MouseOver State.  The Button control looks for this item by name, so you will need to enter it just as I have above
  • The Duration of my animation is set to 0:0:0 - meaning this animation happens instantly.  If you wanted it to slowly turn blue, you could set this to a small value.  Something like 0:0:0.5 would cause it to turn to from gray to blue in 1/2 a second.
  • The ColorAnimation is pointing to the Target named Brush.  If you look back to step 1, this is the name of the Brush that fills the Background Rectangle

The rest of the animations follow a similar pattern.  The TargetName attribute points to the item I want to animate.  And I animate the objects by changing one of their property values (i.e. Opacity, StrokeThickness, Color). 

And finally, after all of this work is done, I have moved my new skin to the Resources section of the Application XAML file and used the Style setting to point my buttons towards their lovely new IE skin ...

image

And when all of this runs - it looks something like this.

image

Finally, I collapsed all of the elements and resources that make up my custom style so you could get a feel for what it looks like when it is all put together.  The complete skin comes to a about 150 lines of markup.  Really not too bad considering the flexibility it provides.  I haven't worked with Silverlight too long, but I am already getting the feeling that control skinning is going to be one of my favorite topics.

image 

That's it.  Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/110

Listed below are links to weblogs that reference Creating a Custom Skin for Silverlight 2's Button Control:

» ScottGu Silverlight Reference Page from ScottGu's Blog
I'll be using this page to link to Silverlight 2 articles and posts (both ones I write as well ones by [Read More]

» ScottGu Silverlight Reference Page from ScottGu's Blog
I'll be using this page to link to Silverlight 2 articles and posts (both ones I write as well ones by [Read More]

Comments


Posted by: Ted Glaza on March 19, 2008 12:00 AM

Hi Matt! Great post. ContentPresenter does the heavy lifting in the content model by adding any object of any type to the visual tree. If the object is a UIElement, it adds it directly. If its text, it creates a new TextBlock to hold the text and adds that. If its something else, it tries to convert that to a string and then use the TextBlock. It will also expand DataTemplates and handle the generated result according to the aforementioned rules. Ideally in future versions it will also respect custom TypeConverters so you can programmatically define how other types of objects are converted into UIElements or text (there wasnt enough time to get this in for MIX).

Posted by: Andrew Rea on March 19, 2008 12:00 AM

Hi Matt, Great article.

I wonder if you can shed some light on something for me. In VS 2008 when I create a new Silverlight application. The design will not allow me to drag or create controls using the GUI. I can design them using the XAML, but the drag and drop behaviour from a fresh project seems to be locked.

Do you have any idea why this may be?

Thanks

Andrew

Posted by: Pasha on March 19, 2008 12:00 AM

Very Nice.

Posted by: Pasha on March 19, 2008 12:00 AM

Very Nice.
Thanx Matt.

Posted by: Jaweed on March 19, 2008 12:00 AM

Thanks Matt.
once again a great article. Do you use an external hosting provider? Just wanted to know since my provider is very slow in upgrading to new technology.

@Ted -
Great information. I wish you had a blog!

@Jaweed -
Discount ASP - I have been running it for just about a year and I have had absolutly zero problems.

Well presented, like always. I like to read your posts!

Matt, thanks for providing a very clear explanation of how to use ControlTemplates. I learned how to use them by reading the SDK docs and hacking my way through it. Now, I wish I knew about and started with your post instead! Im working on a few posts about using ControlTemplates - Ill be sure to link back here for the basics.

By the way, why doesnt this post appear in the "Silverlight 2" category?

For anybody that has updated to Silver 2 Beta 2 and notices that this EXTREMELY cool tutorial doesnt work anymore. See: http://silverlight.net/forums/p/17571/60302.aspx

It seems MS has moved Storyboards from Grid.Resources to vsm:VisualStateManager. Visual Studio 2008 complains, but it compiles and renders just fine.

Thanks for the great post!

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • William Powell wrote: For anybody that has updated to Silver 2 Beta 2 and notices that this EXTREMELY cool tutorial doesnt...
  • snyhol wrote: Matt, thanks for providing a very clear explanation of how to use ControlTemplates. I learned how t...
  • Janko wrote: Well presented, like always. I like to read your posts! ...
  • Matt Berseth wrote: @Ted - Great information. I wish you had a blog! @Jaweed - Discount ASP - I have been running it ...
  • Ted Glaza wrote: Hi Matt! Great post. ContentPresenter does the heavy lifting in the content model by adding any ob...
  • Andrew Rea wrote: Hi Matt, Great article. I wonder if you can shed some light on something for me. In VS 2008 when I...
  • Pasha wrote: Very Nice. ...
  • Pasha wrote: Very Nice. Thanx Matt. ...