Thư viện tri thức trực tuyến
Kho tài liệu với 50,000+ tài liệu học thuật
© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

Apress pro Silverlight 3 in C# phần 8 pps
Nội dung xem thử
Mô tả chi tiết
CHAPTER 13 ■ TEMPLATES AND CUSTOM CONTROLS
464
(which is in the FocusStates group). If you do, the result will depend on the order that the
control applies its states. For example, if the button applies the state from the FocusStates
group first and then the state from the CommonStates group, your focused state animation will
be active for just a split second before being replaced by the competing MouseOver state.
Figure 13-5. Focus in a custom button template
Transitions
The button shown in the previous example uses zero-length state animations. As a result, the
color change happens instantly when the mouse moves over the button.
You can lengthen the duration to create a more gradual color blending effect. Here’s
an example that fades in the new color over a snappy 0.2 seconds:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0:0:0.2" ... />
</Storyboard>
</VisualState>
...
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Although this works, the concept isn’t quite right. Technically, each visual state is
meant to represent the appearance of the control while it’s in that state (not including the
transition used to get into that state). Ideally, a visual state animation should be either a zerolength animation like the ones shown earlier or a steady-state animation–an animation that
repeats itself one or more times. For example, a button that glimmers when you move the
mouse over it uses a steady-state animation.
If you want an animated effect to signal when the control switches from one state to
another, you should use a transition instead. A transition is an animation that starts from the
current state and ends at the new state. One of the advantages of the transition model is that
you don’t need to create the storyboard for this animation. Instead, Silverlight creates the
animation you need automatically.
CHAPTER 13 ■ TEMPLATES AND CUSTOM CONTROLS
465
■ Note Controls are smart enough to skip transition animations when the controls begin in a certain state. For
example, consider the CheckBox control, which has an Unchecked state and a Checked state. You may decide
to use an animation to fade in the checkmark gracefully when the check box is selected. If you add the fade-in
effect to the Checked state animation, it will apply when you show a checked check box for the first time. (For
example, if you have a page with three checked check boxes, all three checkmarks will fade in when the page
first appears.) However, if you add the fade-in effect through a transition, it will be used only when the user clicks
the check box to change its state. It won’t apply when the control is shown for the first time, which makes more
sense.
The Default Transition
Transitions apply to state groups. When you define a transition, you must add it to the
VisualStateGroup.Transitions collection. The simplest type of transition is a default transition,
which applies to all the state changes for that group. To create the default transition, you need
to add a VisualTransition element and set the GeneratedDuration property to set the length of
the transition effect. Here’s an example:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.2" />
</VisualStateGroup.Transitions>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0:0:0"
Storyboard.TargetName="ButtonBackgroundBrush"
Storyboard.TargetProperty="Color" To="Orange" />
</Storyboard>
</VisualState>
<VisualState x:Name="Normal">
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Now, whenever the button changes from one of the common states to another, the
default 0.2 second transition kicks in. That means that when the user moves the mouse over the
button, and the button enters the MouseOver state, the new color fades in over 0.2 seconds,
even though the MouseOver state animation has a zero length. Similarly, when the user moves
the mouse off the button, the button blends back to its original color over 0.2 seconds.
Essentially, a transition is an animation that takes you from one state to another.
VisualStateManager can create a transition animation as long as your state animations use one
of the following types:
CHAPTER 13 ■ TEMPLATES AND CUSTOM CONTROLS
466
• ColorAnimation or ColorAnimationUsingKeyFrames
• PointAnimation or PointAnimationUsingKeyFrames
• DoubleAnimation or DoubleAnimationUsingKeyFrames
The button example works because the Normal and MouseOver states use a
ColorAnimation, which is one of the supported types. If you use something else–say, an
ObjectAnimationUsingKeyFrames–the transition won’t have any effect. Instead, the old value
will stay in place, the transition will run out its duration, and then the new value will snap in.
■ Note In some cases, a state uses several animations. In this situation, all the animations that use supported
types are animated by the transition. Any unsupported types snap in at the end of the transition.
From and To Transitions
A default transition is convenient, but it’s a one-size-fits-all solution that’s not always suitable.
For example, you may want a button to transition to the MouseOver state over 0.2 seconds but
return instantly to the Normal state when the mouse moves away. To set this up, you need to
define multiple transitions, and you need to set the From and To properties to specify when the
transition will come into effect.
For example, if you have these transitions
<VisualStateGroup.Transitions>
<VisualTransition To="MouseOver" GeneratedDuration="0:0:0.5" />
<VisualTransition From="MouseOver" GeneratedDuration="0:0:0.1" />
</VisualStateGroup.Transitions>
the button will switch into the MouseOver state in 0.5 seconds, and it will leave the MouseOver
state in 0.1 seconds. There is no default transition, so any other state changes will happen
instantly.
This example shows transitions that apply when entering specific states and
transitions that apply when leaving specific states. You can also use the To and From properties
in conjunction to create even more specific transitions that apply only when moving between
two specific states. When applying transitions, Silverlight looks through the collection of
transitions to find the most specific one that applies, and it uses only that one. For example,
when the mouse moves over a button, the VisualStateManager searches for states in this order,
stopping when it finds a match:
1. A transition with From="Normal" and To="MouseOver"
2. A transition with To="MouseOver"
3. A transition with From="Normal"
4. The default transition
If there’s no default transition, it switches between the two states immediately.
CHAPTER 13 ■ TEMPLATES AND CUSTOM CONTROLS
467
Transitioning to a Steady State
So far, you’ve seen how transitions work with zero-length state animations. However, it’s
equally possible to create a control template that uses transitions to move between steady-state
animations. (Remember, a steady-state animation is a looping animation that repeats itself
more than one time.)
To understand what happens in this situation, you need to realize that a transition to a
steady-state animation moves from the current property value to the starting property value of
the steady-state animation. For example, imagine you want to create a button that pulses
steadily when the mouse is over it. As with all steady-state animations, you need to set the
RepeatBehavior property to a number of repetitions you want, or use Forever to loop
indefinitely (as in this example). Depending on the data type, you may also need to set the
AutoReverse property to true. For example, with a ColorAnimation, you need to use automatic
reversal to return to the original color before repeating the animation. With a key-frame
animation, this extra step isn’t necessary because you can animate from the last key frame at
the end of the animation to the first key frame of a new iteration.
Here’s the steady-state animation for the pulsing button:
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0:0:0.4" Storyboard.TargetName="ButtonBackgroundBrush"
Storyboard.TargetProperty="Color" From="DarkOrange" To="Orange"
RepeatBehavior="Forever" AutoReverse="True" />
</Storyboard>
</VisualState>
It’s not necessary to use a transition with this button–after all, you may want the
pulsing effect to kick in immediately. But if you do want to provide a transition, it will occur
before the pulsing begins. Consider a standard transition like this one:
<VisualStateGroup.Transitions>
<VisualTransition From="Normal" To="MouseOver" GeneratedDuration="0:0:1" />
</VisualStateGroup.Transitions>
This takes the button from its current color (Red) to the starting color of the steadystate animation (DarkOrange) using a 1-second animation. After that, the pulsing begins.
Custom Transition
All the previous examples have used automatically generated transition animations. They
change a property smoothly from its current value to the value set by the new state. However,
you may want to define customized transitions that work differently. You may even choose to
mix standard transitions with custom transitions that apply only to specific state changes.
■ Tip You may create a custom transition for several reasons. Here are some examples: to control the pace
of the animation with a more sophisticated animation, to use an animation easing, to run several animations in
succession (as in the FlipPanel example at the end of this chapter), or to play a sound at the same time as an
animation.
CHAPTER 13 ■ TEMPLATES AND CUSTOM CONTROLS
468
To define a custom transition, you place a storyboard with one or more animations
inside the VisualTransition element. Here’s an example that creates an elastic compression
effect when the user moves the mouse off a button:
<VisualStateGroup.Transitions>
<VisualTransition To="Normal" From="MouseOver" GeneratedDuration="0:0:0.7">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform"
Storyboard.TargetProperty="ScaleX">
<LinearDoubleKeyFrame KeyTime="0:0:0.5" Value="0" />
<LinearDoubleKeyFrame KeyTime="0:0:0.7" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
■ Note When you use a custom transition, you must still set the VisualTransition.GeneratedDuration property
to match the duration of your animation. Without this detail, the VisualStateManager can’t use your transition,
and it will apply the new state immediately. (The actual time value you use still has no effect on your custom
transition, because it applies only to automatically generated animations. See the end of this section to learn
how you can mix and match a custom transition with automatically generated animations.)
This transition uses a key-frame animation. The first key frame compresses the button
horizontally until it disappears from view, and the second key frame causes it to spring back
into sight over a shorter interval of time. The transition animation works by adjusting the scale
of this ScaleTransform object, which is defined in the control template:
<Grid RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<ScaleTransform x:Name="ScaleTransform" ScaleX="1" />
</Grid.RenderTransform>
...
</Grid>
When the transition is complete, the transition animation is stopped, and the
animated properties return to their original values (or the values that are set by the current state
animation). In this example, the animation returns the ScaleTransform to its initial ScaleX value
of 1, so you don’t notice any change when the transition animation ends.
It’s logical to assume that a custom transition animation like this one replaces the
automatically generated transition that the VisualStateManager would otherwise use. However,
this isn’t necessarily the case. Instead, it all depends whether your custom transition animates
the same properties as the VisualStateManager.
If your transition animates the same properties as the new state animation, your
transition replaces the automatically generated transition. In the current example, the
transition bridges the gap between the MouseOver state and the Normal state. The new state,
Normal, uses a zero-length animation to change the button’s background color. Thus, if you
don’t supply a custom animation for your transition, the VisualStateManager creates an
animation that smoothly shifts the background color from the old state to the new state.
CHAPTER 13 ■ TEMPLATES AND CUSTOM CONTROLS
469
So what happens if you throw a custom transition into the mix? If you create a custom
transition animation that targets the background color, the VisualStateManager will use your
animation instead of its default transition animation. But that’s not what happens in this
example. Here, the custom transition doesn’t modify the color–instead, it animates a
transform. For that reason, the VisualStateManager still generates an automatic animation to
change the background color. It uses its automatically generated animation in addition to your
custom transition animation, and it runs them both at the same time, giving the generated
transition the duration that’s set by the VisualTransition.GeneratedDuration property. In this
example, that means the new color fades in over 0.7 seconds, and at the same time the custom
transition animation applies the compression effect.
Understanding Parts with the Slider Control
In the parts and states model, the states dominate. Many controls, like Button, use templates
that define multiple state groups but no parts. But in other controls, like Slider, parts allow you
to wire up elements in the control template to key pieces of control functionality.
To understand how parts work, you need to consider a control that uses them. Often,
parts are found in controls that contain small working parts. For example, the DatePicker
control uses parts to identify the drop-down button that opens the calendar display and the text
box that shows the currently selected date. The ScrollBar control uses parts to delineate the
draggable thumb, the track, and the scroll buttons. The Slider control uses much the same set of
parts, although its scroll buttons are placed over the track, and they’re invisible. This allows the
user to move the slider thumb by clicking either side of the track.
A control indicates that it uses a specific part with the TemplatePart attribute. Here are
the TemplatePart attributes that decorate the Slider control:
[TemplatePart(Name="HorizontalTemplate", Type=typeof(FrameworkElement))]
[TemplatePart(Name="HorizontalTrackLargeChangeIncreaseRepeatButton",
Type=typeof(RepeatButton))]
[TemplatePart(Name="HorizontalTrackLargeChangeDecreaseRepeatButton",
Type=typeof(RepeatButton))]
[TemplatePart(Name="HorizontalThumb", Type=typeof(Thumb))]
[TemplatePart(Name="VerticalTemplate", Type=typeof(FrameworkElement))]
[TemplatePart(Name="VerticalTrackLargeChangeIncreaseRepeatButton",
Type=typeof(RepeatButton))]
[TemplatePart(Name="VerticalTrackLargeChangeDecreaseRepeatButton",
Type=typeof(RepeatButton))]
[TemplatePart(Name="VerticalThumb", Type=typeof(Thumb))]
[TemplateVisualState(Name="Disabled", GroupName="CommonStates")]
[TemplateVisualState(Name="Unfocused", GroupName="FocusStates")]
[TemplateVisualState(Name="MouseOver", GroupName="CommonStates")]
[TemplateVisualState(Name="Focused", GroupName="FocusStates")]
[TemplateVisualState(Name="Normal", GroupName="CommonStates")]
public class Slider: RangeBase
{ ... }
The Slider is complicated by the fact that it can be used in two different orientations,
which require two separate templates that are coded side by side. Here’s the basic structure:
<ControlTemplate TargetType="Slider">
CHAPTER 13 ■ TEMPLATES AND CUSTOM CONTROLS
470
<!-- This Grid groups the two orientations together in the same template.-->
<Grid>
<!-- This Grid is used for the horizontal orientation. -->
<Grid x:Name="HorizontalTemplate">
...
</Grid>
<!-- This Grid is used for the vertical orientation. -->
<Grid x:Name="VerticalTemplate">
...
</Grid>
</Grid>
</ControlTemplate>
If Slider.Orientation is Horizontal, the Slider shows the HorizontalTemplate element
and hides the VerticalTemplate element (if it exists). Usually, both of these elements are layout
containers. In this example, each one is a Grid that contains the rest of the markup for that
orientation.
When you understand that two distinct layouts are embedded in one control template,
you’ll realize that there are two sets of template parts to match. In this example, you’ll consider
a Slider that’s always used in horizontal orientation and so only provides the corresponding
horizontal parts: HorizontalTemplate, HorizontalTrackLargeChangeIncreaseRepeatButton,
HorizontalTrackLargeChangeDecreaseRepeatButton, and HorizontalThumb.
Figure 13-6 shows how these parts work together. Essentially, the thumb sits in the
middle, on the track. On the left and right are two invisible buttons that allow you to quickly
scroll the thumb to a new value by clicking one side of the track and holding down the mouse
button.
Figure 13-6. The named parts in the HorizontalTemplate part for the Slider
The TemplatePart attribute indicates the name the element must have, which is
critical because the control code searches for that element by name. It also indicates the
element type, which may be something very specific (such as Thumb, in the case of the
HorizontalThumb part) or something much more general (for example, FrameworkElement, in
the case of the HorizontalTemplate part, which allows you to use any element).
The fact that an element is used as a part in a control template tells you nothing about
how that element is used. However, there are a few common patterns:
CHAPTER 13 ■ TEMPLATES AND CUSTOM CONTROLS
471
• The control handles events from a part. For example, the Slider code searches for the
thumb when it’s initialized and attaches event handlers that react when the thumb is
clicked and dragged.
• The control changes the visibility of a part. For example, depending on the orientation,
the Slider shows or hides the HorizontalTemplate and VerticalTemplate parts.
• If a part isn’t present, the control doesn’t raise an exception. Depending on the
importance of the part, the control may continue to work (if at all possible), or an
important part of its functionality may be missing. For example, when dealing with the
Slider, you can safely omit HorizontalTrackLargeChangeIncreaseRepeatButton and
HorizontalTrackLargeChangeDecreaseRepeatButton. Even without these parts, you can
still set the Slider value by dragging the thumb. But if you omit the HorizontalThumb
element, you’ll end up with a much less useful Slider.
Figure 13-7 shows a customized Slider control. Here, a custom control template
changes the appearance of the track (using a gently rounded Rectangle element) and the thumb
(using a semitransparent circle).
Figure 13-7. A customized Slider control
To create this effect, your custom template must supply a HorizontalTemplate part. In
that HorizontalTemplate part, you must also include the HorizontalThumb part. The
TemplatePart attribute makes it clear that you can’t replace the Thumb control with another
element. However, you can customize the control template of the Thumb to modify its visual
appearance, as in this example.
Here’s the complete custom control template:
<ControlTemplate TargetType="Slider">
<Grid>
<Grid x:Name="HorizontalTemplate">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
CHAPTER 13 ■ TEMPLATES AND CUSTOM CONTROLS
472
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- The track -->
<Rectangle Stroke="SteelBlue" StrokeThickness="1" Fill="AliceBlue"
Grid.Column="0" Grid.ColumnSpan="3" Height="7" RadiusX="3" RadiusY="3" />
<!-- The left RepeatButton -->
<RepeatButton x:Name="HorizontalTrackLargeChangeDecreaseRepeatButton"
Grid.Column="0" Background="Transparent" Opacity="0" IsTabStop="False" />
<!-- The Thumb -->
<Thumb x:Name="HorizontalThumb" Height="28" Width="28" Grid.Column="1">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Ellipse x:Name="Thumb" Opacity="0.3" Fill="AliceBlue"
Stroke="SteelBlue" StrokeThickness="3" Stretch="Fill"></Ellipse>
</ControlTemplate>
</Thumb.Template>
</Thumb>
<!-- The right RepeatButton -->
<RepeatButton x:Name="HorizontalTrackLargeChangeIncreaseRepeatButton"
Grid.Column="2" Background="Transparent" Opacity="0" IsTabStop="False" />
</Grid>
<!-- Add VerticalTemplate here if desired. -->
</Grid>
</ControlTemplate>
CREATING SLICK CONTROL SKINS
The examples you’ve seen in this chapter demonstrate everything you need to know about the
parts and states model. But they lack one thing: eye candy. For example, although you now
understand the concepts you need to create customized Button and Slider controls, you haven’t
seen how to design the graphics that make a truly attractive control. And although the simple
animated effects you’ve seen here—color changing, pulsing, and scaling—are respectable, they
certainly aren’t eye-catching. To get more dramatic results, you need to get creative with the
graphics and animation skills you’ve picked up in earlier chapters.
To get an idea of what’s possible, you should check out the Silverlight control examples
that are available on the Web, including the many different glass and glow buttons that
developers have created. You can also apply new templates using the expansive set of themes
that are included with the Silverlight Toolkit (http://silverlight.codeplex.com). If you want
to restyle your controls, you’ll find that these themes give you a wide range of slick, professional
choices. Best of all, themes work automatically thanks to a crafty tool called the