Creating a Custom Skin for Silverlight 's Button Control(收藏)
2010-10-05 10:24 Kevin-wang 阅读(349) 评论(0) 编辑 收藏 举报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.
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.
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
Page.xaml
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).
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.
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.
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).
With just this single rectangle, the button displays like this:
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.
With the second Rectangle added to the template, the button now displays like this:
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.
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.
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 |
|
|
Pressed State |
|
|
Normal State |
|
|
Disabled State |
|
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 ...
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 ...
And when all of this runs - it looks something like this.
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.
That's it. Enjoy!