Dial It Up a Notch

If my boss had come to me a year ago and said, Hey
Matthew, we need a three-way switch like the mode selector on an old stereo, I
would have replied, Sure thing, let me see what I can get. Then I would have
headed out onto the Internet to find and buy a third-party control that fit the
bill.

But last June when my boss really did come and say we
needed a three-way switch, I didn t say, Let me see what I can get. I said,
Let me see what I can make. What s changed? Very simply: Windows
Presentation Foundation (WPF).

In my view, WPF is a game-changing technology that makes
custom control development feasible for regular developers like you and me. In
the past, I had always considered building one s own controls to be a highly
risky endeavor. One could waste months building widgets that didn t work as well
as off-the-shelf models. But WPF changes all that. In this article, I ll take
you step-by-step through building a custom WPF control that simulates a
three-way switch. The result is fewer than one hundred lines of standalone XAML
that is fully self-contained, needs no procedural code-behind, and provides a
fully functional, animated control. WPF is a wide and deep topic. My hope is
that by looking at one custom control in detail I can give you a taste of what s
possible with this new technology.

To begin, let s take a look at the result. Figure 1 shows
three three-way switches on an XAML page (for fun, I ve set these up to
represent the selection for choosing ice cream). You can see in the figure many
of the key elements of the three-way switches. For example, the selected item is
indicated in two ways: by the font weight and color of the text, as well as by
the position of the indicator on the selector dial. What you can t see from the
example is additional control features, such as mouseover and click behaviors.
In this version of the control, when you mouse over a possible selection, the
text becomes bold. And when you click on a selection, the indicator dial
immediately changes to point at the new selection.

Figure 1: Three three-way switches on an XAML page.

A big part of the challenge in building custom controls
has always been supporting these kinds of behaviors. For a control to behave
properly, you need to spend time thinking about low-level plumbing like mouse
and keyboard input, enter and focus events, and painting. But WPF frees us from
these worries with the concept of ControlTemplates. A ControlTemplate represents
the visual presentation of a control separate from the behavior of a control. In
WPF, all controls have a template. For example, if you place a WPF Button on an
XAML page, at runtime, the Button will be rendered with some template. If you
happen to be using a Windows Classic theme, the template will be something named
a ClassicBorderDecorator. If you re using the default Windows XP theme, the
template will be something named ButtonChrome. You can check out the MSDN Button
ControlTemplate Example at
msdn2.microsoft.com/en-us/library/ms753328.aspx to see how you can build
a template to make a Button render in your own way.

In this article, however, we re going to go a bit beyond
simply changing the aesthetics of a control. We re going to take an existing
control that encapsulates the behavior we want and give it a control template
that seems to turn it into a completely new control. So, thinking about the
three-way switch, let s ask the question: what out-of-the-box control
encapsulates the behavior we desire? My first thought, and maybe yours, too, was
that a three-way switch is really just a fancy set of RadioButton controls.
However, it turns out that using a series of RadioButtons presents a problem
because we want to apply our template to the container, that is, the group of
RadioButtons, as well as to the individual items. So what we really need is some
sort of list container control. What is the basic list container control? The
ListBox, of course. If you think about it, while the three-way switch doesn t
look like a ListBox, it does behave exactly like a ListBox control containing
three items set to single-selection mode. So that s what we re going to base our
control on. What this project really boils down to is this: make a control
template that targets a ListBox and makes it look like a three-way switch.

Step 1: Designing the Dial

Before we delve into our control template, we can begin
by doing a little basic XAML design of the dial. I m going to make this control
a fixed size. Using a fixed size isn t as flexible as designing a sizeable
control, but it is much simpler and it makes positioning the controls much
easier (more on that later). Let s begin with a simple XAML Canvas object on
which to put our control:

Now, let s add a circle to the left side of our Canvas
for the actual switch dial. This is done with an Ellipse object:

Stroke="Silver"

Fill="Gray"

Width="50"

Height="50"

Canvas.Left="5"

Canvas.Top="10" />

Next, let s overlay a thick line on top of the circle to
form the indicator of the switch. I ve used rounded end caps to give it the
look I want. And because we re using a fixed-size layout, I can use explicit X-Y
coordinates to center the line right over the dial ellipse. Note that the line
is drawn straight up and down. This will come into play later when we write the
triggers to rotate the dial:

StrokeThickness="8"

Stroke="Black"

StrokeStartLineCap="Round"

StrokeEndLineCap="Round"

X1="30" Y1="14" X2="30" Y2="56" />

Lastly, we need to draw the little white dot on the end
of the indicator. This is an interesting drawing problem, and there are several
ways you might think to handle it. A key consideration is that we re going to
need to rotate this dot around the center point of the dial. To make the
rotation easy, I chose to render this dot as a line of exactly the same length
and position as the indicator. However, the indicator dot line is set to be
dashed with a StrokeDashArray and StrokeDashCap that makes it so we only get a
single oval dot exactly where we want it:

StrokeThickness="6"

Stroke="White"

StrokeStartLineCap="Round"

StrokeDashCap="Round"

StrokeEndLineCap="Round"

StrokeDashArray=".3, 10"

X1="30" Y1="14" X2="30" Y2="56" />

Now we have a Canvas with an Ellipse and two lines on it.
If you were to place this XAML snippet on an XAML page with a white background,
you d see something like the dial shown in Figure 2.

Figure 2: The dial by itself.

Step 2: Building the ListBox ControlTemplate

This is perhaps the key step to this entire project.
Here s where we take the little dial we designed, place it into a
ControlTemplate, and make it display list items. We begin with an XAML snippet
that names our ControlTemplate and targets it to a ListBox. I ve placed our
Canvas here, as well:

TargetType="{x:Type ListBox}">

Now we re going to place one more visual element on our
Canvas: a StackPanel. As you may know, the StackPanel is a container control
that allows us to arrange a series of items so they flow. In this template, it
plays a critical role because the StackPanel is set with the property
IsItemsHost = True . The IsItemsHost property is specific to ControlTemplates
that target ItemsControls, such as a ListBox because it tells the control where
the contained items are supposed to go. In other words, this StackPanel is where
our list of three selectable items is going to be rendered. I ve nudged the
StackPanel over to the right to make room for the switch dial, and I ve also set
it so the list items will be stacked vertically on top of each other and aligned
to the left:

Name="itemsPanel"

Orientation="Vertical"

VerticalAlignment="Center"

HorizontalAlignment="Left"

IsItemsHost="True"

Canvas.Left="50"

Canvas.Top="7"/>

At this point, you could actually apply this
ControlTemplate to a ListBox and it would work. In other words, you could do
something like:

If you did, you d see something like the example shown in
Figure 3. The items in the ListBox would display and be selectable, but the
indicator dial would not rotate and the items would have the default styling
depending on your Windows theme.

Figure 3: Functional, but incomplete, template.

Step 3: Animating the ListBox ControlTemplate

Now comes the fun: animating the dial. At first you might
wonder how this can be done without some procedural code-behind. The answer is:
Triggers. In XAML, Triggers behave very much like event sinks. There are several
different kinds of Triggers in WPF, but for these control template Triggers, we
want to identify a property we care about and a value we care about. We then can
write a Trigger that will fire when that property changes to that value. Within
the Trigger, we can set other properties as we see fit. In this case, the
property I want to target is the SelectedIndex property of the ListBox. The
values I will target are 0, 1, and 2, because this is a three-way switch. And
the properties I want to set are the RenderTransform properties of the indicator
lines, which will allow us to rotate them.

Before we actually write the Triggers, it will be helpful
to define some RotateTransform objects as resources of the ControlTemplate. This
creates slightly more efficient code and simplifies our Trigger syntax quite a
bit. So, within the ControlTemplate tags, we ll add a ControlTemplate.Resources
section that contains three RotateTransform objects: one for a 45 rotation
(position 0), one for a 90 rotation (position 1), and one for a 135 rotation
(position 2):

CenterX="30"

CenterY="35"

Angle="45" />

CenterX="30"

CenterY="35"

Angle="90" />

CenterX="30"

CenterY="35"

Angle="135" />

Now we re ready for the Triggers themselves. Rather than
belabor the discussion, let me simply show you the code, which, after a little
inspection, is fairly self-explanatory. Here is one of the three Triggers we
need:

Property="RenderTransform"

Value="{StaticResource rotate45}" />

Property="RenderTransform"

Value="{StaticResource rotate45}" />

This Trigger will rotate 45 both the indicator line and
the indicatorDot line when the SelectedIndex of the ListBox is zero. In the
final code, we actually have two more Triggers much like this: one for
SelectedIndex=1 and one for SelectedIndex=2, which rotate the lines by 90 and
135 , respectively.

At this point our main ControlTemplate is complete. It s
a good idea at this point to gather all our code snippets together in one place
so you can see what the template looks like in its totality (see Listing One).

Step 4: The ListItem Template

With the ControlTemplate for the ListBox in hand, our
work is almost done but not quite. Whenever you are designing a control
template for an ItemsControl like a ListBox, you can also design a template for
the items contained in the control. This actually turns out to be quite
important, because if you don t explicitly template the contained items, you are
likely to get unintended visual effects. For example, when an item in a ListBox
is selected, the background color of the item (under Windows Classic) is dark
blue, as you can see in Figure 3. That is definitely not what we want. In our
case, we want the background of our selected item to stay the same, and for the
font to become bold and yellow, among other things. So, to take control of the
presentation of the list items as well as the container, we need a
ControlTtemplate for them.

This ControlTemplate is quite a bit simpler than the one
for the ListBox itself, so I ll simply present it whole and then discuss it (see
Figure 4).

TargetType="{x:Type ListBoxItem}">

BorderThickness="0"

Padding="3"

Background="Black" >

Text="{TemplateBinding ContentControl.Content}"

Foreground="White" />

Property="TextBlock.FontWeight"

Value="Bold" />

Property="TextBlock.Foreground"

Value="Yellow" />

Property="TextBlock.FontWeight"

Value="Bold" />

Figure 4: The ListBoxItem ControlTemplate.

The visual layout of the item template is fairly simple:
a Border control with a TextBlock within it. The Border allows us to set some
padding around the text. The TextBlock is where the text of the ListItem is
actually displayed. How does the TextBlock know to display the text of the
ListItem? This is achieved through the magical incantation:
Text= {TemplateBinding ContentControl.Content} . Lastly, the ItemTemplate
includes a few Triggers that handle the MouseOver and Selected properties to
change the font color and weight, appropriately. Now when you mouse over a list
item, the font turns bold; when a list item is selected, the font turns bold and
yellow.

Step 5: Using Styles to Apply the Templates

At this point, you could apply these two templates to our
ListBoxes and ListBoxItems, directly. A sample ListBox might look something like
that shown in Figure 5.

Template="{StaticResource
threeWaySwitchItemTemplate}"/>

Template="{StaticResource
threeWaySwitchItemTemplate}"/>

Template="{StaticResource
threeWaySwitchItemTemplate}"/>

Figure 5: A sample ListBox might look something like
this.

Fortunately, there is a better way. In addition to a
Template property, most WPF controls have a Style property. Sometimes people get
confused about the difference between Template and Style and when to use
which. A Style is really a much simpler thing than a Template. A Style lets you
bundle a series of property settings under a name and apply them as a group. For
example, if you wanted a series of TextBoxes to all have bold, yellow, Verdana
text, you could create a Style representing those property settings and apply it
to each TextBox rather than all the properties. Because the Style can be a
resource, it makes your code a bit more efficient (but mainly it makes your XAML
a lot more concise).

The interesting thing is that you can use a Style to
apply a Template because a Template is nothing more than another property
setting on a control. This is very convenient because it allows you to bundle
your Template with other property settings you may wish to control; on the
whole, this makes for some very elegant XAML. I strongly recommend that people
consider using a Style to apply their Templates. In our case, we actually can
make use of two Styles: one for the ListBoxItems and one for the ListBox (see
Figure 6).

Figure 6: The Styles for the ListBoxItems and the
ListBox.

Note that the Style for the ListBox not only sets the
ListBox template, but sets the ItemContainerStyle, as well which is the Style
to apply to items in the list. This is pretty nifty. The Style for the ListBox
applies a Style to the ListBoxItems it contains. And the Style for those
ListBoxItems applies a Template to those items. Now all we have to do is apply
the top-level Style to the ListBox and everything cascades down quite elegantly:

Now our three-way switch is ready for use. In Figure 1 I
showed three of these against a black background. To achieve that effect, I
simply placed three of the ListBoxes above on a WrapPanel on a Page with a black
background. Voil ! The full, standalone XAML file is available for download; see
end of article for details.

Discussion and Conclusion

At several places in this process you may have noticed
that I took some shortcuts. For example, this three-way switch isn t sizeable
and it has a fixed-layout design. Likewise, it relies on the Tag property of the
items in the list being 0, 1, or 2 which means your ListBox instances have to
conform to this standard. Also, it s only a three-way switch. What if you needed
a four- or five-way switch?

In addition, none of the control s visual aspects are
exposed through properties. What if you wanted the items listed on the right? Or
the bottom? What if you wanted selected items red, not yellow, and italic, not
bold? What if you wanted the dial to be beveled or raised? Certainly, if you
were designing a switch control for resale, you d need to take all these things
into consideration. The result would need to be a sizeable n-way switch
with a raft of settable properties to customize its look and feel.

So have I cheated? Have I taken too many shortcuts? I
don t think so and here s why. If you were planning to sell your control, yes,
you d need to do all that extra work to make the control highly flexible. But as
I suggested at the outset, one thing that is powerful and compelling about WPF
is that it allows us to design UI controls that do exactly what we need. And if
what we need is a three-way switch that looks and behaves a certain way, why
program it to do any more than that? The three-way switch I demonstrated is
deployed in a live enterprise application and it s working just fine. If
tomorrow my boss said to me, Hey Matthew, now we need a four-way switch, would
I rebuild the three-way switch as an extensible n-way switch? No way! I d
simply copy and paste my hundred lines of XAML and tweak it. It would take 10
minutes instead of 10 days. That s the beauty of WPF: Once you get the hang of
it, you can quickly build exactly what you need no more, no less.

Of course, there are many interesting ways you might
think to improve this three-way switch. You could use a background image or some
of WPF s 3D drawing capabilities to make a more realistic control. You could
play a custom sound in the selected index changed event. You could even trap
some mouse events on the dial and make it directly responsive to mouse input. I
hope you ll consider this simple control as a starting place.

I d like to offer a little challenge to asp.netPRO
readers. The application described in this article is cute, but it seems somehow
incomplete. Once the user has made their flavor, scoops, and container
selection, wouldn t it be appropriate for the application to somehow serve them
some tasty visual ice cream? Now there s a WPF challenge for you! I would be
very interested to see what folks can come up with. If you send me some code,
I ll do my best to feature the wackiest, prettiest, and most impressive WPF ice
cream implementations in a future article.

Matthew Hess is a software developer in Albuquerque,
NM. He grew up on a diet of Delphi but has since switched to almost pure .NET.
You can reach Matthew with your questions, corrections, suggestions and humor at
[email protected]