Tuesday, June 16, 2009

The Quickest Custom Control You’ll Ever Make

WPF and Silverlight controls each support the concept of ‘templates’. Essentially, this features allows you to rip out the default set of rendering instructions with a custom look and feel. Therefore, rather than assuming the only way to make a ‘round button’ is by extending the Button class, you can define a ‘round template’ in markup, and connect it to a widget via the Style property (or the Template property; I examined how to do this manually in a previous blog post).

While it is always good to know how to build templates by hand, the amount of requires markup can be quite verbose, especially when you add back the expected visual cues (e.g., how your control looks when focused, when capturing a mouse down / mouse up command, etc).

As luck would have it, Expression Blend provides the tools you need to transform any rendered geometry into a brand new Button style, or if you prefer, a new UserControl derived class. Let’s walk through the basic process of generating a new WPF UserControl from a rendered 2D geometry (if you are a Silverlight developer, the process seen here will be a tad different, as WPF currently makes use of ‘triggers’ rather than the Silverlight VSM).

Assume you have drawn the following Path using the Pencil tool and the integrated Brush editor:

image

The markup for this geometry will be recorded using the ‘path modeling language’, seen here:

<Path Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="42.959,61,0,0"
VerticalAlignment="Top" Width="158.041" Height="144.676"

Data="M76,61 C105.06376,74.414044 112.19527,69.60086 143,64 151.66608,62.424349 159.11968,62 168,62 176.08355,62
176.34758,60.117623 182,69 187.97746,78.393147 178.51878,86.085302 176,97 173.30227,108.69018 176.30709,98.914264 190,105
200.11756,109.49669 200,113.32086 200,128 200,152.61687 185.27361,149.74528 154,156 154,164.08587
153.04266,182.08907&#xd;&#xa;150,190 145.81585,200.8788 133.0419,204 118,204 112.85646,204 105.94378,205.52116 101,204
92.754503,201.46292 83.182382,200.45596 79,190 73.61827,176.54567 81.638001,170.9525 92,158 100.47878,147.40152 106,151.69459
106,134 106,117.44967 84.144921,118.56076 61,115 48.081385,113.01252 43,117.93998 43,96
43,86.490398&#xd;&#xa;41.68613,72.925548 54,68 56.422354,63.155292 66.021256,64.395749 73,63" StrokeThickness="5">
  <Path.Fill>
    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
      <GradientStop Color="#FF4948CC" Offset="0"/>
      <GradientStop Color="#FF23E6D2" Offset="1"/>
    </LinearGradientBrush>
  </Path.Fill>
</Path>

As you can see, this Path object currently has no relationship to a custom UserControl….until you activate the “Make Control” menu option:

image

Once you do so, Expression Blend will prompt you for a name for your new UserControl derived class. I’ll name my control OddButton:

image

At this point, two key changes have occurred. First, the IDE has added a brand new UserControl to your project, which you will see in your Project window. If you were to view the generated XAML, you’ll see that original markup has been wrapped in a <UserControl> root element. The related code file is as you would expect (seen in C#, the VB code would be quite similar):

public partial class OddButton
{
    public OddButton()
    {
        this.InitializeComponent();
    }
}

Second, the Window which originally contained the 2D geometry has been updated as follows:

  1. A new XMLNS tag prefix has been defined to allow you to declare the control in markup.
  2. An instance of the new control has been substituted for the previous Path object.
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="BlendUserControl.Window1"
    x:Name="Window"
    Title="Window1"
    Width="640" Height="480"
    xmlns:BlendUserControl="clr-namespace:BlendUserControl">

    <Grid x:Name="LayoutRoot">
        <BlendUserControl:OddButton Margin="42.959,61,423,238.324"/>
    </Grid>
</Window>

At this point, you can use Blend (or Visual Studio) to handle the any event you choose. If you were to handle the MouseDown event and run the application, you will be happy to find that the event fires *only* if the mouse is within the boundary of the geometry itself (not the bounding rectangle).

While this is a massive step in the right direction, you will quickly notice that when you do ‘click’ the control, there is no visible signs that you have done so. End users certainly will expect to see some change of visual state when key UI commands occur, and once again Expression Blend makes it very simple to add these gestures back.

Let’s say you wish to add a visual cue which will change the UI of OddControl when the mouse goes down within it’s boundary. To do so, make sure you have the designer of the new UserControl opened within Blend, and locate the Triggers window. Once you have done so, click on the “+Event” button, and specify that you wish to capture the MouseDown event on the UserControl (use the drop down list boxes to do so):

image

Next question: What shall we do when this event condition arises? If you have experience building custom WPF templates, you know that event triggers commonly execute a custom animation of some sort. For example, you could shrink the size of OddButton when the MouseDown event fires, paint the background with a new brush, spin the control 180 degrees, etc. Here, we will simply change the size of the control using a ScaleTransform object.

First. Click the small “+” button to the left of your newly captured trigger command:

image

Once the story board has been created, it is time to make use of the integrated animation editor (you might want to press the F6 key to gain more screen real-estate). Locate the Object and Timeline editor, and click the “Record Keyframe” button straightaway:

image

Click the very top of the yellow line, and drag it to the one-second mark. Once you have done so, click the Record Keyframe button once again. Your timeline editor should now look something like this:

image

Your next task is to specify what to do when time moves between these two keyframes. Locate the Transform editor of the Properties window, and scale down your control by some amount, for example:

image

At this point, you can click the ‘Play button’ of the animation editor. You should see your OddControl shrinks in size over the course of one second. If you wish to have the control grow back to it’s original size, first make sure the Storyboard is highlighted in the Objects and Timeline editor:

image

And click the AutoReverse option via the Properties window:

image

Now, run your application! You should be able to see your control change its size when you click the OddControl user control. This is possible thanks to the following markup, rendered free of charge by the Blend IDE:

image

You can use Blend in a similar way to incorporate additional visual cues, but I’ll allow the interested reader to do so if they so choose.

Hope this blog entry was helpful to you! Take care.

2 comments:

bradutz01 said...

Hi,

At http://www.xamltemplates.net you can find themes/styles for all the WPF and Silverlight controls.

Andrew Troelsen said...

Thanks for that bradutz01! I was not aware of that web site myself, but it is in my favorities now ;-)

Post a Comment

Note: Only a member of this blog may post a comment.