Tải bản đầy đủ (.pdf) (102 trang)

programming windows phone 7 phần 7 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.65 MB, 102 trang )
























</Button.Style>
</Button>
Here’s what we’re up to so far:
The button still displays the text “temporary” and it should really be displaying the text “Click
me!” You might be tempted to put a TextBlock in there and set its Text property to a
TemplateBinding of the Content property of the Button:
<TextBlock Text="{TemplateBinding Content}" />
This actually works in this example, but it’s very, very wrong. The problem is that the Content


property of the Button is of type object. We can set it to anything—an Image, a Panel, a
Shape, a RadialGradientBrush, and then the TextBlock would have a little problem.
Fortunately, there is a class in Silverlight that exists specifically to display content in a
ContentControl derivative. That class is called ContentPresenter. It has a property named
Content of type object, and ContentPresenter displays that object regardless whether it’s a text
string or any other element:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Style>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" />
<Setter Property="BorderThickness" Value="6" />
<Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" />
597
















































<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="12">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Notice how the Content property of the ContentPresenter is bound to the Content property of
the Button. The ContentPresenter has the distinct advantage of working for any kind of object.
The ContentPresenter might create its own visual tree; for example, if the Content is of type
string, then the ContentPresenter creates a TextBlock to display that string. The
ContentPresenter is also entrusted with the job of building a visual tree to display content
based on a DataTemplate set to the Control. For this purpose, the ContentPresenter has its
own ContentTemplate property that you can bind to the ContentTemplate of the control:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Style>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" />
<Setter Property="BorderThickness" Value="6" />

<Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="12">
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
598


























These two TemplateBinding settings on ContentPresenter are so standard that they are not
required to be explicitly set! They will be set for you. I feel more comfortable seeing them
explicitly set, however.
You may recall that the Control class defines a property named Padding that is intended to
provide a little breathing room around the control’s content. Try setting the Padding property
of the Button:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Padding="24">

</Button>
Nothing happens. The visual tree in the template needs to accommodate this Padding
property. It needs to leave some space between the Border and the ContentPresenter. How
can this be done? One solution is to use a TemplateBinding on the Padding property of the
Border. But if there’s some other stuff in the Border besides the ContentPresenter that’s not

going to work right. The standard approach is to set a TemplateBinding on the Margin
property of the ContentPresenter:
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}" />
You don’t need to set a Padding value on the Button for this to have an effect. The theme
Style for the Button defines a Padding value that seems to work well with this Button, even
with the rounded Border corners.
Now try setting the HorizontalAlignment and VerticalAlignment properties of the Button to
Stretch. These work fine, so that’s something you don’t have to worry about in the template.
Similarly, you can set the Margin property of the Button and that’s still recognized by the
layout system.
But when you set the HorizontalAlignment and VerticalAlignment properties of the Button to
Stretch, you’ll discover that the content of the Button is at the upper-left corner:
599






















Control defines two properties named HorizontalContentAlignment and
VerticalContentAlignment that are supposed to govern how the content is aligned within the
ContentControl. If you set these properties on the button, you’ll discover that they don’t work.
This tells us that we need to add something to the template to handle these properties. We
have to align the ContentPresenter within the Border based on the
HorizontalContentAlignment and VerticalContentAlignment properties. This is accomplished by
providing TemplateBinding markup targeting the HorizontalAlignment and VerticalAlignment
properties of the ContentPresenter:
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
Again, this is very standard markup for a ContentPresenter. It’s copy-and-paste stuff.
If you set the font-related properties or the Foreground property on the Button, you’ll find
that the text changes accordingly. These properties are inherited through the visual tree of
the template and you don’t need to do anything in the template to accommodate them.
(However the theme Style for the Button explicitly sets the Foreground, FontFamily, and
FontSize properties, so the Button itself cannot inherit these properties through the visual tree,
and there is apparently nothing you can do in a custom Style to change this behavior.)
600

































The Visual State Manager
All this time that the Button has been redesigned with a template, it has otherwise remained a
fully-functional button and it’s been generating Click events every time it’s been tapped. The
big problem is that the Button does not deliver visual feedback to the user. It has a
customized visual appearance, but that appearance does not change.
There are really just two features that need to be added to this template to make it
functionally and visually complete:
• The Button needs to provide visual feedback when the user presses it.
• The Button needs to indicate a disabled state if it’s disabled.
These two features are related because they both involve changing the visuals of the control
under certain circumstances. And the two features are also related because the solution
involves a Silverlight feature called the Visual State Manager.
The Visual State Manager helps the developer deal with visual states, which are changes in
control visuals that result from changes in properties (or other states) of the control. For the
Button on Windows Phone 7, the relevant visual states correspond to the properties IsPressed
and IsEnabled.
You can determine the visual states supported by a particular control by looking at the
documentation for that control. In the first page of the Button documentation, you’ll see the
class defined with six attributes of type TemplateVisualStateAttribute:
[TemplateVisualStateAttribute(Name = "Disabled", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "MouseOver", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "Pressed", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "Unfocused", GroupName = "FocusStates")]
[TemplateVisualStateAttribute(Name = "Focused", GroupName = "FocusStates")]
public class Button : ButtonBase
These are the six visual states of the Button. Each of these states has a name, but notice also
that each of them has a group name, either CommonStates or FocusStates.
Within any group, the visual states are mutually exclusive. One and only one visual state in
each group currently applies to the Button. In the CommonStates group, either a button is

Normal, or Disabled, or the mouse is hovering, or the button is pressed. You don’t need to
worry about combinations of these states. You don’t have to come up with a special state for
mouse hovering on a disabled button because those two states will never occur at the same
time.
601











































The code in the Button class is responsible for the button going into a particular state. It does
this through calls to the static VisualStateManager.GoToState method. The template is
responsible for responding to visual changes based on these states.
As Windows Phone 7 programmers, we have a somewhat easier job than template authors
targeting Silverlight on the web. We don’t have to worry about the two states in the
FocusStates group, or the MouseOver state. That leaves Normal, Disabled, and Pressed.
Very often, additional elements are inserted into the template specifically for these visual
states. When a control is disabled, the contents usually get grayer in some way regardless of
the nature of the content—be it text, a bitmap, or something else. This suggests that a
disabled state can be handled by putting a semi-opaque Rectangle on top of the entire
control.
So let’s put the entire visual tree of the template inside a single-cell Grid and add a Rectangle
on top:

<ControlTemplate TargetType="Button">
<Grid>
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="24">
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
<Rectangle Name="disableRect"
Fill="{StaticResource PhoneBackgroundBrush}"
Opacity="0" />
</Grid>
</ControlTemplate>
Fortunately the new Rectangle has its Opacity set to 0 or it would block out the entire control!
But if you set the Opacity to 0.6 (for example) it provides a proper dimming effect, regardless
of control content.
Notice the color of the Rectangle is set as the PhoneBackgroundBrush resource. The Button
has rounded corners so you don’t want the Rectangle changing the color of whatever’s
behind the Button and visible through those corners. It’s also possible to give the Rectangle
the same corner rounding as the Border and you’ll have a little more flexibility with the color.
Now that we have the Rectangle in there, all we need to do is find some way to change the
Opacity from 0 to 0.6 when the visual state becomes Disabled.
602
















































The markup for the Visual State Manager always appears after the start tag of the top level
element of the template, in this case the Grid. It begins with a
VisualStateManager.VisualStateGroups tag within which can be multiple VisualStateGroups
sections. I’ll be ignoring the FocusStates group:
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

</Grid>
</ControlTemplate>
The VisualStateGroup tags enclose a series of VisualState tags for each of the visual states in
that group:
<ControlTemplate TargetType="Button">

<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed">
</VisualState>
<VisualState x:Name="Disabled">
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

</Grid>
</ControlTemplate>
The VisualState tag for the Normal state is empty because the template that’s already been
designed is for the normal button. But don’t leave out the tag; otherwise the control won’t
return to its Normal state after being in another state. The MouseOver state won’t be used; it
too will remain empty.
Within the VisualState tags you indicate what you want to happen for that state. How do you
do that? You might imagine doing it with a Setting tag as in a Style, and that would work well.
But to allow you to be very much more flexible, the Visual State Manager lets you use
animations instead, and because the animation syntax isn’t very much more complex than the
Setting syntax, the Visual State Manager actually requires you to use animations. Within the
VisualState tags you’ll put a Storyboard containing one or more animations targeting
properties of named elements within the template. In many cases, these animations will have
603













































a Duration setting of 0 so that the visual state changes immediately. But you can have
smoother state animations if you want. Here’s an animation for the Opacity property of the
Rectangle named disableRect:
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed">
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="disableRect"
Storyboard.TargetProperty="Opacity"
To="0.6" Duration="0:0:0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>


</Grid>
</ControlTemplate>
The animations in the Visual State Manager don’t usually have From values so they just take
off from the existing value. The empty VisualState tag for the Normal state effectively restores
the Opacity value back to its pre-animation value when the state goes back to Normal.
The Pressed state presents some challenges. Usually a Pressed state is often rendered as a
form of reverse video. In the web version of Silverlight, the Button template hard-codes a
LinearGradientBrush for the background and changes properties of that brush for the Pressed
state. Because the template is controlling the brush for the Normal state, it can easily change
that brush for the Pressed state.
In the Button template being created here, the default Foreground color is set in the theme
style for the Button, and a default Background color is set in the Style that the template is part
of. If these properties aren’t changed, the colors will be either white on black (with the “dark”
theme) or black on white. But the properties can be changed with local property settings on
the Button.
It would be great to have some kind of graphic effect to reverse the colors, but that’s not
available. We need to define animations to set new foreground and background colors for the
Pressed state that will seem to reverse colors in the normal case, that is, when the foreground
is set to the PhoneForegroundBrush resource and the background is PhoneBackgroundBrush,
604



































which means that the Pressed state can set Foreground to PhoneBackgroundBrush and
Background to PhoneForegroundBrush.
Can we use a ColorAnimation for this job? We could if we knew that the Foreground and
Background brushes were actually SolidColorBrush objects. But we don’t know that. That
means we need to use ObjectAnimationUsingKeyFrames objects to target the Foreground and
Background properties themselves. The ObjectAnimationUsingKeyFrames can have children of

only one type: DiscreteObjectKeyFrame.
Let’s do the Background property first by giving the Border a name:
<Border Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="12">
That name allows the animation to target the Background property of Border:
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="border"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{StaticResource PhoneForegroundBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
For the Pressed state, the animation changes the Background property of the Border element
to the brush referenced as the PhoneForegroundBrush resource. Excellent!
Now let’s add a similar animation to target the Foreground property of … of what? There is no
element in this template visual tree that has a Foreground property!
It would be ideal if ContentPresenter had a Foreground property but it does not.
But wait a minute. What about ContentControl? ContentControl is basically just a
ContentPresenter but ContentControl has a Foreground property. So let’s replace
ContentPresenter with a ControlControl and give it a name:
<ContentControl Name="contentControl"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"

VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
Now it’s possible to define the second animation for the Pressed state:
<VisualState x:Name="Pressed">
<Storyboard>
605






















<ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentControl"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0:0:0"

Value="{StaticResource PhoneBackgroundBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
And here’s the pressed Button:
I will now declare this template to be completed! (And now you’ll understand why the default
template for the Button contains a ContentControl.)
Let’s look at this entire Style and ControlTemplate in the context of a page. In the
CustomButtonTemplate program, the Style is defined in the page’s Resources collection.
Mostly to reduce keep the lines lengths shorter than the width of the page, the
ControlTemplate is defined as a separate resource and then referenced by the Style. Here’s the
ControlTemplate first followed by the Style referencing that template:
Silverlight Project: CustomButtonTemplate File: MainPage.xaml (excerpt)
<phone:PhoneApplicationPage.Resources>
<ControlTemplate x:Key="buttonTemplate" TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
606


























































<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="border"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{StaticResource PhoneForegroundBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="contentControl"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{StaticResource PhoneBackgroundBrush}" />

</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="disableRect"
Storyboard.TargetProperty="Opacity"
To="0.6" Duration="0:0:0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="12">
<ContentControl Name="contentControl"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding
HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding
VerticalContentAlignment}" />
</Border>
<Rectangle Name="disableRect"
Fill="{StaticResource PhoneBackgroundBrush}"
Opacity="0" />
</Grid>

</ControlTemplate>
<Style x:Key="buttonStyle" TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" />
607














































<Setter Property="BorderThickness" Value="6" />
<Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" />
<Setter Property="Template" Value="{StaticResource buttonTemplate}" />
</Style>
</phone:PhoneApplicationPage.Resources>
The content area contains a Button that references this Style, of course, but I wanted to test
the enabling and disabling of the Button in a very interactive manner, so I added a
ToggleButton to the page and set a binding targeting the IsEnabled property on the styled
and templated Button from the IsChecked property of the ToggleButton.
But it didn’t look quite right for the ToggleButton to be toggled on (that is, highlighted) when
the regular Button was in its normal (that is, enabled) state. It occurred to me that what I really

wanted was for the ToggleButton to actually say “Button Enabled” when the ToggleButton was
toggled on and the Button was enabled, and for it to say “Button Disabled” when the
ToggleButton was toggled off and the Button was disabled.
This is the beauty of templates. You can do something like this right in XAML without a whole
lot of fuss and without any extra tools like Expression Blend.
Silverlight Project: CustomButtonTemplate File: MainPage.xaml (excerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0"
Content="Click me!"
Style="{StaticResource buttonStyle}"
IsEnabled="{Binding ElementName=toggleButton, Path=IsChecked}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<ToggleButton Name="toggleButton"
Grid.Row="1"
IsChecked="true"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Border BorderBrush="{StaticResource PhoneForegroundBrush}"
BorderThickness="{StaticResource PhoneBorderThickness}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>

<ObjectAnimationUsingKeyFrames
608


























Storyboard.TargetName="txtblk"
Storyboard.TargetProperty="Text">

<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="Button Enabled" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock Name="txtblk"
Text="Button Disabled"/>
</Border>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</Grid>
This ToggleButton here has what I think of as a single-purpose special-use ad hoc
ControlTemplate, so it doesn’t have a lot of extra frills. The visual tree consists entirely of a
Border and a TextBlock. It ignores the Content property, and the Text property of the TextBlock
is initialized with “Button Disabled”. Everything else is done with visual states. In addition to
the regular Button visual states, the ToggleButton also defines a CheckStates group with states
Checked and Unchecked. These are the only states this template handles, and the animation
for the Checked state sets the Text property of the TextBlock to “Button Enabled.” Here it is in
action with the Button disabled:
609




















I didn’t define a Disabled state for the ToggleButton because this is a template I intend to use
only for this program, and I know that this ToggleButton will never be disabled.
Sharing and Reusing Styles and Templates
As you know, it’s possible to derive one Style from another, in the process inheriting all the
Setter objects. The new Style can add to those Setter objects or override them.
However, it is not possible to derive from a ControlTemplate. There’s no way to reference an
existing ControlTemplate and specify an additional piece of the visual tree, or a replacement
for part of the visual tree. (It’s hard enough imaging the mechanics or syntax of such a
process.)
Generally if you want to apply some changes to an existing ControlTemplate, you obtain a
copy of that entire template and begin editing it. These default templates are generally
included with Silverlight documentation. (However, as I write this chapter, the Silverlight
documentation contains only the templates for the web-based version of Silverlight and not
those for Silverlight for Windows Phone.) Expression Blend also has access to the standard
default templates.
If you need to share a Style or a ControlTemplate (or a Style containing a ControlTemplate)
among multiple controls on a page, you simply put it in the Resources collection of the page.

If you need to share the Style or ControlTemplate among multiple pages, put it in the
Resources collection of App.xaml.
610





































It is also possible to share resources among multiple applications. To share these resources
you define them in a XAML file with a root element of ResourceDictionary. Here’s such a file
with the name SharedResources.xaml:
<ResourceDictionary
xmlns=" /> xmlns:x="
<SolidColorBrush x:Key="brush" Color="Blue" />

</ResourceDictionary>
The file probably has a lot more resources than just the SolidColorBrush. Each resource has an
x:Key attribute, of course. You could create this file as part of a project, or you can add this
existing file to a project. In either case, the Build Action in the properties window should
indicate Page.
Now you can reference that file in the Resources collection in App.xaml:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="SharedResources.xaml" />

</ResourceDictionary.MergedDictionaries>

</ResourceDictionary>

</Application.Resources>
Notice the ResourceDictionary.MergedDictionary property element used for referencing
external ResourceDictionary objects.
Here’s a different approach: You might want to define a custom style and template for an
existing control, and then refer to that control with a new name rather than to explicitly
reference the style. That too is possible.
Here’s a program called FlipToggleDemo that includes a custom class named FlipToggleButton
that derives from ToggleButton. But FlipToggleButton doesn’t add any code to ToggleButton—
just a Style and ControlTemplate.
In the FlipToggleDemo project, I added a new item of type Windows Phone User Control and
I gave it a name of FlipToggleButton.xaml. This process creates a FlipToggleButton.xaml file
and a FlipToggleButton.xaml.cs file for a class that derives from UserControl. But then I went
611


















































into both files and changed UserControl to ToggleButton so FlipToggleButton derives from
ToggleButton.
To keep things simple, I decided not to implement any state transitions for a disabled button,
but to flip the button upside down for the Unchecked state. Here’s the complete XAML file for
the custom button, with indentation reduced to 2 spaces to avoid lines wider than the pages
of this book:
Silverlight Project: FlipToggleDemo File: FlipToggleButton.xaml
<ToggleButton x:Class="FlipToggleDemo.FlipToggleButton"
xmlns="
xmlns:x="
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border BorderBrush="{StaticResource PhoneForegroundBrush}"
BorderThickness="{StaticResource PhoneBorderThickness}"
Background="{TemplateBinding Background}"
RenderTransformOrigin="0.5 0.5">
<Border.RenderTransform>
<RotateTransform x:Name="rotate" />
</Border.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="rotate"
Storyboard.TargetProperty="Angle"
To="180" Duration="0:0:0.5" />

</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="rotate"
Storyboard.TargetProperty="Angle"
Duration="0:0:0.5" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding
HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding
612





































VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

</ToggleButton.Style>
</ToggleButton>
Usually when you’re looking at XAML file, the bulk of the file is set to the Content property of
the root element. Here the bulk of the file is set to the Style property of the root element.
Notice that the Style object and the ControlTemplate object both have TargetType set to
ToggleButton rather than FlipToggleButton. This is fine because neither references any
properties specifically defined by FlipToggleButton because FlipToggleButton does not define
any new properties.
The template itself is very plain vanilla, consisting of just a Border and a ContentPresenter with
all the standard template bindings. But the Border also has its RenderTransformOrigin
property defined and its RenderTransform property set to a RotateTransform object.
The two animations that flip the button upside down (for the Checked state) and back (for the
Unchecked state) have non-zero times. The DoubleAnimation for the Checked state has no
From value; it uses the base value of the property, which is zero. The DoubleAnimation for the
Unchecked state has neither a To or From value! The animation starts at whatever value the
Angle property of the RotateTransform happens to be—probably 180 but perhaps something
lower if the animation to flip the button hasn’t quite completed when the button is
unchecked—and it ends at the base value, which is zero.
Here’s the complete code-behind file for the custom control:
Silverlight Project: FlipToggleDemo File: FlipToggleButton.xaml.cs
using System.Windows.Controls.Primitives;
namespace FlipToggleDemo
{
public partial class FlipToggleButton : ToggleButton
{
public FlipToggleButton()
{
InitializeComponent();
}
}

}
The MainPage.xaml file of the project instantiates the custom button to test it out:
613



































Silverlight Project: FlipToggleDemo File: MainPage.xaml (excerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<local:FlipToggleButton Content="Flip Toggle"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
Custom Controls in a Library
Generally when you create a custom control, you define some new properties for the control
as well as a default Style and ControlTemplate, and you put that new control in a DLL for
sharing among multiple applications. You can couple the code and Style as shown in the
FlipToggleButton example, but a more standard approach for Silverlight libraries involves
defining the Style in a special file named generic.xaml located in a directory named Themes.
This generic.xaml file has a root element of ResourceDictionary.
Let’s look at an example.
Suppose you conceive of a ToggleButton template something like the one in the
CustomButtonTemplate project but much more generalized. Rather than just switch between
two hard-coded text strings, you want to switch between two objects of any type. And not
just switch—you want an object associated with the Checked state and an object associated
with the Unchecked state to fade from one to the other. Your name for this new button is
called FadableToggleButton.
As you think about it, you realize that the control needs to define a new property named
CheckedContent, similar to the normal Content property. The Content property is the object
displayed for the button’s Unchecked state, and CheckedContent is displayed for the Checked

state.
I defined this class in the Petzold.Phone.Silverlight library. The complete code for
FadableToggleButton is here:
Silverlight Project: Petzold.Phone.Silverlight File: FadableToggleButton.cs
using System.Windows;
using System.Windows.Controls.Primitives;
namespace Petzold.Phone.Silverlight
{
public class FadableToggleButton : ToggleButton
{
public static readonly DependencyProperty CheckedContentProperty =
DependencyProperty.Register("CheckedContent",
typeof(object),
typeof(FadableToggleButton),
614







































new PropertyMetadata(null));
public FadableToggleButton()
{
this.DefaultStyleKey = typeof(FadableToggleButton);
}
public object CheckedContent
{

set { SetValue(CheckedContentProperty, value); }
get { return (object)GetValue(CheckedContentProperty); }
}
}
}
This is the only C# code required to implement this control! There’s not even a property-
changed handler for this new CheckedContent property. It’s just a DependencyProperty
definition and a CLR property definition. Everything else is XAML.
But notice the constructor. If this code file were a partial class definition partnered with a
XAML file, you’d see a call to InitializeComponent in the constructor. Instead, there’s the
following:
this.DefaultStyleKey = typeof(FadableToggleButton);
This statement indicates that this class has a default Style definition, and the TargetType of this
Style definition is FadableToggleButton. To apply a default Style to instances of this class,
Silverlight needs to find that Style definition. Where does it search?
Silverlight looks in a very special XAML file in the library. This XAML file is always named
generic.xaml and it is always located in a directory named Themes of the DLL project. This is
how a control gets a default theme style and template.
This generic.xaml file has a root element of ResourceDictionary. However the file is special in
another way: The contents are regarded as resources but the Style elements don’t require
x:Key or x:Name attributes because they are referenced via the TargetType.
Here’s the portion of the generic.xaml file in the Themes directory of Petzold.Phone.Silverlight
that contains the default Style definition of the FadableToggleButton class:
Silverlight Project: Petzold.Phone.Silverlight File: Themes/generic.xaml (excerpt)
<ResourceDictionary
xmlns="
xmlns:x="
xmlns:local="clr-namespace:Petzold.Phone.Silverlight">
<Style TargetType="local:FadableToggleButton">
<Setter Property="Template">

615

























































<Setter.Value>
<ControlTemplate TargetType="local:FadableToggleButton">
<Grid>

<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed" />
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="disableRect"
Storyboard.TargetProperty="Opacity"
To="0.6" Duration="0:0:0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="uncheckedContent"
Storyboard.TargetProperty="Opacity"
To="0" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetName="checkedContent"
Storyboard.TargetProperty="Opacity"
To="1" Duration="0:0:0.5" />
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="uncheckedContent"
Storyboard.TargetProperty="Opacity"
Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetName="checkedContent"

Storyboard.TargetProperty="Opacity"
Duration="0:0:0.5" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border BorderBrush="{StaticResource PhoneForegroundBrush}"
BorderThickness="{StaticResource PhoneBorderThickness}"
Background="{TemplateBinding Background}">
<Grid Margin="{TemplateBinding Padding}">
<ContentPresenter
Name="uncheckedContent"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding
HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding
616













































VerticalContentAlignment}" />
<ContentPresenter
Name="checkedContent"
Opacity="0"
Content="{TemplateBinding CheckedContent}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding
HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding
VerticalContentAlignment}" />
</Grid>
</Border>
<Rectangle Name="disableRect"
Fill="{StaticResource PhoneBackgroundBrush}"
Opacity="0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

</ResourceDictionary>
The TargetType for the Style is FadableToggleButton, and that’s enough to allow Silverlight to
find this Style definition that becomes the default theme for the FadableToggleButton. Within
the Border is a single-cell Grid with two ContentPresenter elements, one with a
TemplateBinding referencing the normal Content property, the other referencing the
CheckedContent property. The ContentPresenter referencing the CheckedContent property has
an initial Opacity of zero. The animations for the Checked and Unchecked states target the
Opacity property of the ContentPresenter so that one fades out as the other fades in.

Although the Content properties of the two ContentPresenter elements are bound to two
different properties of the FadableToggleButton, the ContentTemplate properties of both are
bound to the same ContentTemplate property originally defined by ContentControl. If you set
a DataTemplate to the ContentTemplate property of FadableToggleButton, then that same
DataTemplate must apply to both the Content property and the CheckedContent property. In
other words, this template implicitly assumes that the Content property and CheckedContent
property are of the same types.
To test out this new control, I created a FadableToggleDemo program. The project contains a
reference to the Petzold.Phone.Silverlight library and an XML namespace declaration for the
library in MainPage.xaml. I added two bitmaps of the same size to an Images directory in the
project. These bitmaps are referenced by Image elements set to the Content and
CheckedContent properties of the button:
617























Silverlight Project: FadableToggleDemo File: MainPage.xaml (excerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<petzold:FadableToggleButton HorizontalAlignment="Center"
VerticalAlignment="Center">
<petzold:FadableToggleButton.Content>
<Image Source="Images/MunchScream.jpg"
Stretch="None" />
</petzold:FadableToggleButton.Content>
<petzold:FadableToggleButton.CheckedContent>
<Image Source="Images/BotticelliVenus.jpg"
Stretch="None" />
</petzold:FadableToggleButton.CheckedContent>
</petzold:FadableToggleButton>
</Grid>
The Content property is set to an image from Edvard Munch’s painting The Scream:
618


















The CheckedContent property uses Botticelli’s Birth of Venus:
Variations on the Slider
As you might expect, the Slider has one of the more complex templates in all of standard
Silverlight, and for that reason, it’s important to get familiar with it—particularly if you’re not
a big fan of the default Slider template implemented in Windows Phone 7.
At first, a Slider does not seem to fit into the scheme of templates, primarily because it
contains moving parts. How does this work exactly?
If you look at the documentation of Slider, you’ll see the customary
TemplateVisualStateAttribute tags, but also a collection of TemplatePartAttribute tags
(rearranged somewhat here form their order in the documentation):
[TemplateVisualStateAttribute(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "MouseOver", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "Disabled", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "Focused", GroupName = "FocusStates")]
[TemplateVisualStateAttribute(Name = "Unfocused", GroupName = "FocusStates")]
[TemplatePartAttribute(Name = "HorizontalTemplate", Type = typeof(FrameworkElement))]
[TemplatePartAttribute(Name = "HorizontalTrackLargeChangeDecreaseRepeatButton",
Type = typeof(RepeatButton))]
[TemplatePartAttribute(Name = "HorizontalTrackLargeChangeIncreaseRepeatButton",
Type = typeof(RepeatButton))]
[TemplatePartAttribute(Name = "HorizontalThumb", Type = typeof(Thumb))]
619







































[TemplatePartAttribute(Name = "VerticalTemplate", Type = typeof(FrameworkElement))]
[TemplatePartAttribute(Name = "VerticalTrackLargeChangeDecreaseRepeatButton",
Type = typeof(RepeatButton))]
[TemplatePartAttribute(Name = "VerticalTrackLargeChangeIncreaseRepeatButton",
Type = typeof(RepeatButton))]
[TemplatePartAttribute(Name = "VerticalThumb", Type = typeof(Thumb))]
public class Slider : RangeBase
What this means is that the Slider expects its template to contain eight elements with the
names of “HorizontalTemplate” and so forth. These are referred to as “parts” of the template.
The “HorizontalTemplate” and “VerticalTemplate” parts need only be of type
FrameworkElement (or derived from FrameworkElement) but other parts are required to be of
type RepeatButton or Thumb.
The RepeatButton and Thumb are a couple of controls that I haven’t yet had much occasion to
use in this book. (They are both found in the System.Windows.Controls.Primitives namespace,
a subtle suggestion that the controls are intended to be used in building other controls.) The
RepeatButton is similar to a regular Button except that when you hold your finger on it, it fires
repeated Click events. It’s perfect for a ScrollBar or Slider and was probably invented
specifically for that purpose.
The Thumb is a rather special control that reports how the user is trying to drag it. But if you
can’t quite figure out where the Thumb is located in the standard Slider on Windows Phone 7,
that’s because it’s been pretty well hidden in the theme template. One of my goals here is to
restore the Thumb to the Slider.
A control with parts (such as the Slider) overrides the ApplyTemplate method to be notified

when a template has been set to its Template property. It then uses GetTemplateChild to find
the elements with these particular names. It can attach event handlers to these elements, and
otherwise manipulate these elements when the control is in use. (You’ll see this process from
the code perspective towards the end of this chapter.)
The standard Slider supports horizontal and vertical orientations, and the template actually
contains two separate (and fairly independent) templates for these orientations. These two
separate templates are enclosed in elements with the “HorizontalTemplate” and
“VerticalTemplate” names. If the Orientation property of Slider is Horizontal, then the Slider
sets the Visibility property of the “HorizontalTemplate” element to Visible and the Visibility
property of “VerticalTemplate” element to Collapsed, and oppositely for the Vertical
orientation.
When designing a new template for the Slider, the most straightfoward approach is to use a
single-cell Grid to enclose the two templates. A nested Grid named “HorizontalTemplate”
contains three columns with the two RepeatButton controls and a Thumb. Another nested
Grid named “VerticalTemplate” has three rows.
Here’s is what I think of as a “bare bones” template for Slider defined as a resource:
620



















































Silverlight Project: BareBonesSlider File: MainPage.xaml (excerpt)
<phone:PhoneApplicationPage.Resources>
<ControlTemplate x:Key="bareBonesSliderTemplate"
TargetType="Slider">
<Grid>
<Grid Name="HorizontalTemplate">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<RepeatButton Name="HorizontalTrackLargeChangeDecreaseRepeatButton"
Grid.Column="0"
Content="-" />
<Thumb Name="HorizontalThumb"
Grid.Column="1" />
<RepeatButton Name="HorizontalTrackLargeChangeIncreaseRepeatButton"
Grid.Column="2"
Content="+" />
</Grid>
<Grid Name="VerticalTemplate">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<RepeatButton Name="VerticalTrackLargeChangeDecreaseRepeatButton"
Grid.Row="0"
Content="-" />
<Thumb Name="VerticalThumb"
Grid.Row="1" />
<RepeatButton Name="VerticalTrackLargeChangeIncreaseRepeatButton"
Grid.Row="2"
Content="+" />
</Grid>
</Grid>
</ControlTemplate>
</phone:PhoneApplicationPage.Resources>
The Slider template does not exactly require the two RepeatButton controls and the Thumb to
be in a three-row or three-column Grid, but it’s certainly the easiest solution. Notice that I
621

×