Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


WPF Meets the iPhone : Page 6

Find out how to add iPhone-like UI features to your WPF applications.

Working with Animations
Now you are ready to add some "wow" effects to the user interface. The first effect you need to create is the button "scatter" effect. In the real world, when a user clicks any iPhone application button, all the buttons scatter vertically, horizontally, and diagonally off the screen. After the buttons have scattered, the iPhone displays the application associated with the clicked button as a tiny rectangle in the center of the button panel. The application then quickly grows to fill the whole button panel.

You can use WPF animations to recreate all these different iPhone visual effects. WPF contains a full set of animation classes that allow you to change the value of any dependency property on an object to create a visual animation. For example, you can change the value of an object's Height or Width to make it grow or shrink. Typically, you change the property from one value to another value and specify the duration—how long it takes to go from the starting to ending value.

When creating an animation, you must first determine which property you want to animate. In the case of the button scatter effect, changing the Margin property of grdMainButtonPanel to a negative value causes all sides of the grid to expand beyond the visual boundaries of the main Window. As the Grid expands, the contained cells grow proportionately, and if you set the value to a large enough negative number, the expansion causes all buttons to scatter off the main Window.

Expression Blend provides a powerful visual environment for creating animations. To get started, go to the very top of the Objects and Timeline palette and click the New button (+) as shown in Figure 19. A Storyboard allows you to define multiple animations, specifying the property to be animated and the timeline in which you want the animation to occur. In the Create Storyboard Resource dialog box, enter ScatterButtons as the Name (Key) and then click OK. This closes the dialog box and places the Objects and Timeline palette into Storyboard editing mode as shown in Figure 20.

Figure 19. Objects and Timeline Palette: Expression Blend's Objects and Timeline palette lets you create Storyboards, which allow you to specify a timeline in which your animations occur.
Figure 20. Storyboard Editor: You use the Storyboard editor to create animations, play them back, and fine-tune them.
As soon as you create a Storyboard, Expression Blend is placed in Timeline recording mode, as indicated by the red border around the main design surface. Also, the red text Timeline recording turns on in its upper left corner. In recording mode, you can select objects in the Objects and Timeline palette and change their property values in the Properties palette. Blend watches which values you change and records them in a timeline.

Since you want to animate the Margin property of the main button panel. First, select grdMainButtonPanel in the Objects and Timeline palette. To record this object with its initial design-time settings, click the "Record Keyframe" button as shown in Figure 21. This adds a small keyframe ellipse to the Timeline next to grdMainButtonPanel, indicating the initial property values. Next, click and drag the playhead to the one-second mark as shown in Figure 22. Afterwards, go to the Properties palette and change all four Margin property values to -700. As you change these values, you can see the sides of the button panel Grid expand accordingly. After changing the Margin values, go back to the Objects and Timeline palette again. Notice that a second Expression has automatically added another keyframe to the timeline at the one second position. Because you are finished with this animation, go to the top-left corner of the main design surface and click the "Timeline recording is on" button (see Figure 23) to turn off recording.

Figure 21. Keyframes: Keyframes record changes to properties allowing you to animate the transitions between different values.
Figure 22. Timeline Playhead: You can manually position the Timeline playhead to specify the interval of a keyframe.
Figure 23. Timeline Recorder: Expression Blend's Timeline recording watches the values you set in the Properties palette and records them in your animation Timeline.
Now you're ready for the fun part—playing back the animation. To do this, go to the top of the Objects and Timeline palette and click the "Go to first frame" VCR button. Next, click the Play button. The iPhone application buttons should scatter as the parent Grid expands. You can also step frame-by-frame through the animation by clicking the "Go to previous frame" and "Go to next frame" VCR-style buttons.

As an aside, if you manually step through the different frames using the VCR buttons, you will see that as the application buttons scatter, they are not visually confined to the original button panel area, but are also visible in other panels of the main grid. I'll show you how to address this undesirable behavior later in this article.

Specifying Animation Event Triggers
Your next step is to specify the user action that begins the animation. In this case you want the click of any application button to initiate the scatter effect. However, rather than associating each button in the panel with the animation you can specify the association in one place, taking advantage of a WPF feature known as routed events. Unlike regular .NET events, routed .NET events can automatically bubble up or tunnel down the container hierarchy. For example, when you click a WPF button, the Click event fires on the button itself, and then travels up the container hierarchy—all the way up to the top level Window. This provides a powerful event-handling model that allows you to place common handler code in a higher-level container. This is exactly what you need in this situation. You can place code at the grdMainButtonPanel level that executes regardless of which button is clicked.

Figure 24. Triggers Panel: The Triggers panel allows you to specify a trigger that begins an animation.
To do this, go to the Expression Blend Triggers panel. As shown in Figure 24, when you create a new Timeline, it automatically creates an associated event trigger for Window.Loaded. If you click on this event and look in the middle of the panel, you'll see a combination of text and combo boxes that says "When Window Loaded is raised—ScatterButtons Begin." This means that, by default, the animation you just created will begin when the Window is first loaded—which is not what you want. To change the object that fires the event, under Objects and Timeline, select the grdMainButtonPanel object. Next, go to the middle portion of the Triggers panel and in the first combo box, select grdMainButtonPanel as the object that raises the event. The second combo box allows you to select the event that raises the event; however, since the button Click event that fires the animation doesn't belong to the Grid but to each of the buttons instead, you need to manually specify the event trigger in XAML.

To do this, go to the XAML editor and under the <Window.Triggers> element change the EventTrigger subelement's RoutedEvent attribute from FrameworkElement.Loaded to ButtonBase.Click (if you can't see the XAML window, select Split from the Design → XAML → Split chooser to the right of the main design surface). When you're done, the XAML should look like this:

<EventTrigger RoutedEvent="ButtonBase.Click" SourceName="grdMainButtonPanel">

Now, if you go back to the Triggers panel and select the grdMainButtonPanel.Click event, the text in the middle bar should read "When grdMainButtonPanel Click is raised—ScatterButtons Begin."

You can now test how this works by pressing F5 to build and run the project. Clicking any application button should now cause the buttons to scatter.

Now that you have successfully created the scatter effect, it's time to create the effect that gathers the buttons back to their original positions. The real-world iPhone has a button at the bottom of the front panel that closes any open application and gathers the buttons back to their original position. Rather than creating an exact replica of this button (you now know enough to do it on your own) you can create a reasonable likeness, and spend more time on the important WPF features.

Go to the Objects and Timeline palette and double-click the grdMainPanel object. Next, go to the Asset Library, select a Button, then drag it on the bottom row of the Grid. Afterwards, go to the Properties palette and set the following properties:

  • Name: btnMain
  • Background: Color Resource | MainPanelBackColor
  • Width: 20
  • Height: 20
  • Content: (clear all content)
  • HorizontalAlignment: Center
  • VerticalAlignment: Center
  • Margin: Reset all values to zero (0)
Now you need to create the animation this new button will start. Go to the top of the Objects and Timeline palette and click the New Storyboard button (+) as shown in Figure 19. In the Create Storyboard Resource dialog box, set the Name to GatherButtons, and then click OK. This creates a new storyboard and turns on Timeline recording. In the Objects and Timeline palette, double-click grdMainButtonPanel to select it, and then go to the Properties palette and set the all Margin values to minus 700 (-700). This automatically adds a keyframe at the zero (0) second mark. Next, select the recording play head and drag it to the one second mark as shown in Figure 22. Go back to the Properties palette and set all Margin properties to zero (0). You're building the reverse of the ScatterButtons animation. Because you are finished creating the animation, click the Timeline recording button to turn off recording (see Figure 23). You can use the VCR buttons in the Objects and Timeline palette to test your animation.

To hook the new animation to the btnMain button you just created, go to the Triggers panel and select the second event in the list, which again defaults to Window.Loaded. Go to the Objects and Timeline palette and select btnMain. Now go back to the controls in the center of the Triggers panel, and in the first combo box, change the selection from Window to btnMain. Go to the second combo box and change the selection from Loaded to Click. The text and controls when read together should say "When btnMain Click is raised—GatherButtons Begin."

Figure 25. Exiting Timeline Editing Mode: You exit Timeline editing mode by clicking the up arrow near the top of the Objects and Timeline palette.
To test both animations at run time, press F5 to build and run the project. Clicking any application button should cause them all to scatter. Clicking btnMain should cause the buttons to gather.

To stop editing the animations, go to the top of the Objects and Timeline palette and click the up arrow as shown in Figure 25.

Working with Clip Property and Geometries
As mentioned earlier, when the application buttons scatter, they don't stay within the boundaries of the iPhone application button panel—they scatter over the top panel, status bar and even the Window border. Although you could create solid shapes for the top and bottom areas of the Window, the images would still cover the Window border when they scatter and gather, causing a slight flicker. Instead, it's best to use the Grid's Clip property to constrain the button display to the main button panel.

Although you can use the Expression Blend IDE to specify the Clip property (see Blend's Apply or remove a clipping path Help topic for details) because you need to animate the clipping region, you have to edit the Window's XAML file directly. First, you need to locate the XAML for the main button panel. To do this, go to the Objects and Timeline, and select grdMainButtonPanel. This highlights the starting tag of the XAML that comprises the Grid. Directly beneath the first line of selected XAML, and directly above the <Grid.RowDefinitions> element, add the following element:

<Grid.Clip> <RectangleGeometry Rect="0,0,265,300" x:Name="ButtonPanelGeometry"/> </Grid.Clip>

The preceding code tells WPF to clip the Grid within the bounds specified by the RectangleGeometry element. The first two numbers specify the location of a RectangleGeometry object. As the Grid grows and shrinks, the location of the RectangleGeometry needs to change accordingly. The best way to do this is to add animations that change the location in tandem with the animation of the Grid. However, because this requires the animation of the complex Grid.Clip property, you again need to edit the XAML directly to add the animations. First of all, go to the top of the XAML file and search for the phrase "Storyboard." You should see a storyboard element with the key ScatterButtons. Add the following XAML animation directly below its Thickness animation. The animation you're adding animates the location of the ButtonPanelGeometry object to 700, 700which offsets the animation of the parent Grid's Margin property:

<RectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ButtonPanelGeometry" Storyboard.TargetProperty="Rect" > <LinearRectKeyFrame KeyTime="00:00:01" Value="700,700,265,300" /> </RectAnimationUsingKeyFrames>

Next, locate the storyboard element with the key GatherButtons and add the following XAML animation directly below its Thickness animation—resetting the ButtonPanelGeometry back to its original position of 0,0:

<RectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ButtonPanelGeometry" Storyboard.TargetProperty="Rect" > <LinearRectKeyFrame KeyTime="00:00:01" Value="0,0,265,300" /> </RectAnimationUsingKeyFrames>

You can test the result of the new Clip property setting and its associated animations by pressing F5 to build and run the project. When the buttons scatter they should no longer expand over the other panels or the Window border.

Figure 26. Removing Start Values in Property Animations: Not setting a start value for a property animation usually helps avoid unexpected animation behavior.
Before moving to the next step, there is one fine point to note. If you press btnMain when the application buttons are not scattered, it first scatters them, and then gathers them. It does that because the animation you defined has both "From" and "To" values. When the animation starts, the "From" value of the animation gets, set which gathers the buttons. To fix this problem, go to the Objects and Timeline palette and select the GatherButtons storyboard from the controls at the top of the palette. Next, locate the Margin animation listed under grdMainButtonPanel. Right-click the keyframe ellipse located at the zero second mark as shown in Figure 26, and select Delete from the shortcut menu.

Typically, you don't need a "From" value in an animation because you are often not concerned with the current value of an animated property—you normally just want to animate it "To" a particular value. Removing the "From" value often prevents odd behavior from occurring. With this in mind, go back to the top of the Objects and Timeline palette, select the GatherButtons storyboard, and delete that Margin animation's zero second keyframe too. When you're done, click the Close Storyboard (x) button at the top of the Objects and Timeline palette to get out of Storyboard editing mode.

Creating the Lower Button Panel
Rather than provide highly detailed instructions for creating the lower button panel (the panel that contains the four main application buttons as shown in Figure 1), you should be able to create this lower button panel with minimal instructions. If necessary, refer back to the more detailed instructions at the beginning of this article.

First of all, add a new Grid to Row 3 of grdMainButtonPanel. Set the following properties on the new Grid:

  • Name: grdLowerButtonPanel
  • Background: Solid Color Brush (170, 170, 170)
  • Add four column definitions (accept all defaults)
  • Add two row definitions (set the Height of Row 0 to 1* and Row 1 to .30*)
Next, add a Rectangle element to Row 1 (the second row) of the Grid, sizing it to fill the row. Set its Fill color to Solid Color Brush (130, 130, 130).

Add four new iPhoneButtons to grdLowerButtonPanel. Place each button in its own column, but have them span two rows, so they fill the entire Grid. Set the ImageSource and LabelContent properties to the following values:

  • phone.png: Phone
  • mail.png: Mail
  • browser.png: Safari
  • ipod.png: iPod
When you're finished, your lower button panel should look like the panel shown in Figure 1.

Figure 27. Running Multiple Animations for an Event: You can specify multiple animations to execute for a single WPF event.
When you press real-world application buttons, the bottom button panel fades until it is no longer visible. To add this effect, go to the top of the Objects and Timeline palette and click the New button (+) and in the Create Storyboard Resource dialog box enter LowerButtonsHide and click OK. In the Objects and Timeline palette, select grdLowerButtonPanel. Next, move the recording playhead to the .5 second mark. Afterwards, go to the Properties palette and set the Opacity property to 0 percent. This should cause the lower button panel to disappear. Select the "Timeline recording is on" button to turn off recording. By default, a new Window.Loaded event was added to the Triggers panel. Since you want to associate the new timeline with the grdMainButtonPanel.Click event, go to the Triggers panel, click the Window.Loaded event and then click the "-Trigger" button to delete the trigger. In the top of the Triggers panel, select grdMainButtonPanel.Click. This displays the associated ScatterButtons animation in the lower half of the panel. Because you want to add a second action for this event, click the "Add new action" button (+) in the middle of the Triggers panel. Next, in the first combo box in the newly added action, select LowerButtonsHide. When you're done, the Triggers panel should look like Figure 27.

Now you need to create a corresponding animation to display the lower button panel when a user clicks the main button. To do this, create another new storyboard in the Objects and Timeline panel and name it LowerButtonsShow. Next, select the grdLowerPanelButtons item in the Objects and Timeline palette then go to the Properties panel and change the Opacity setting to 0 percent. This adds a keyframe at the 0 second mark. Next, move the recording playhead to the 1 second mark and change the Opacity property to 100%. This adds a new keyframe to the timeline. Even though you had to create a keyframe at the zero second mark, you no longer need it for the completed animation, so right-click the zero second keyframe and select Delete from the shortcut menu. When you're finished, turn off timeline recording.

You need to associate this new animation with the btnMain.Click event. To do this, go to the Triggers panel again, select the Window.Loaded trigger and click the "-Trigger" button. Next, select the btnMain.Click trigger and click the "Add new action" button (+) located in the middle of the Trigger panel. In the newly added action, go to the first combo box and select LowerButtonsShow from the list.

To test the new animations, press F5 to run the project and try scattering and gathering the buttons and you should see the lower panel buttons dissolve then fade back in as the main panel buttons scatter and gather.

This article presented many great features of WPF, Expression Blend, and Visual Studio. However, you can do much more to this project to demonstrate additional features of WPF. In a future article I'll demonstrate how to launch a corresponding application when a user clicks an application button. The article will demonstrate the use of business objects in WPF as well as additional data binding features in WPF.

Kevin McNeish is president of Oak Leaf Enterprises, Inc, and chief architect of the MM .NET Application Framework. He is a Microsoft .NET MVP and a well-known INETA speaker and trainer who's worked throughout North America and Europe, including at VSLive!, DevTeach (where he serves as one of the .NET chairs), SDC Netherlands, and Advisor DevCon. He is coauthor of the book "Professional UML with Visual Studio .NET," author of the book ".NET for Visual FoxPro Developers," writes articles, and has been interviewed for .NET Rocks! He spends about half his time on the road training and mentoring companies to build well-designed, high-performance .NET applications.
Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.