First, there was plain-old Win32: C interfaces and a lot of work to get anything done. MFC wrapped up some of the complexity into an object-oriented framework, but it was a fairly thin layer on top of Win32. .NET’s Windows Forms was a more elegant attempt at abstracting away the old C interfaces, but they were still there somewhere.
Windows Presentation Foundation (WPF) throws all of that away and gives you a completely new framework from which to build user interfaces. It is designed to be hardware-accelerated to take advantage of today’s modern graphics cards.
As with any new framework, there is a lot to learn. However, especially in the case of WPF, the payoff is huge. You can do things in WPF with little effort that would previously require you to learn a specialized graphics API, such as DirectX.
WPF is nearly boundless in the amount you could learn, but the examples and tips in this chapter will get you well on your way to creating astounding interfaces in far less time than with the older framework.
Solution: Although most things in WPF can be expressed as either XAML or code, most examples will use XAML as much as possible.
Here’s the code in Window1.xaml:
The corresponding code in Window1.xaml.cs is even simpler:
The base Windows class’s InitializeComponent
method will do the XAML parsing and present the window.
This and the next few sections build up a simple WPF-based text editor, complete with menu, toolbar, status bar, and commands.
Solution: WPF is all about automatic layout. You should rarely need to manually position UI elements. This makes it much easier to deal with dynamic content and internationalization, for example. WPF provides a number of layout controls for you, which are detailed in Table 18.1. You should rarely need to build your own.
Nearly any interface can be composed of combinations of these layouts. The examples in this chapter use Grid, StackPanel, and DockPanel.
Solution: Many WPF examples show off the 3D capabilities, or its advanced data-binding features, but WPF excels at standard interfaces (menu bar, toolbar, status bar) just as easily, so we’ll start there before moving on to the more “wow” capabilities.
Let’s add a simple menu definition to the bare window:
Note the underscores (_) in front of some of the letters in the menu. These indicate that the following character should be a shortcut character. In Win32-style programming, you would use an ampersand (&), but that has a special meaning in XML (of which XAML is derivative), so the underscore is used instead.
This menu doesn’t really do anything except be visible, so let’s add a few more UI elements and then hook everything up with commands.
Solution: The StatusBar can easily be docked to the bottom of the DockPanel, as shown in this example:
The StatusBar contains a TextBlock that binds to the Text’s Length
property of the TextBox. Data binding is discussed later in this chapter.
One of the great powers of WPF is that because it’s not tied to Win32 in any way, you have a lot more freedom over layout. You can make your StatusBar contain a button, a menu, or a custom control if you want. The layout system is completely flexible. You can embed text boxes in buttons, and movies into check boxes (if you wanted to do such an odd thing), among many, many other things.
Solution: Toolbars are the same concept as in Windows Forms but come with the flexibility inherent in all WPF controls:
The toolbar is docked to the top, and because it comes after the menu, it will appear right below it.
Solution: WPF ships with a number of standard command handlers that work with the standard controls. For example, associating a Copy menu item with the built-in Copy
command automatically enables the command when the focus is in a TextBox.
Solution: Commands are commonly grouped into static classes for easy reference. Here, we define two commands:
These commands can now be used in command bindings and attached to event handlers:
And the XAML to hook them up is as follows:
Our nearly complete application, shown in Figure 18.1, requires little effort to implement but demonstrates some of WPF’s powerful techniques, such as data binding and commands.
Solution: To complete this basic editor, let’s give it the dubious feature of disabling the Exit command if text has been entered (see Figure 18.2).
Solution: An Expander is similar to a GroupBox in that it lets you group related elements under a common header. An Expander, however, also allows you to collapse the group to save UI space.
Here is one of the Expander elements from the ImageViewer sample app (see Figure 18.3):
Solution: The ImageViewer example defines two RadioButton elements. The RadioButton
class has a Checked
event. To assign an event handler in XAML, you can merely assign the name of the function to the event’s name:
The event handler looks quite similar to ones in standard .NET event handling:
WPF elements use RoutedEvents
, which are like regular .NET events with a bunch of features added onto them. They are required because WPF elements can have many layers of embedding. For example, if you embed a StackPanel, a TextBlock, and an Image inside a button and you click on the image, you still want it to seem to your application like a button was clicked, not the image.
RoutedEvents
come in a few flavors: direct, bubbling, and tunneling.
Direct events are similar to .NET events: Only the source element itself can call event handlers.
Bubbling events are the most common in the WPF UI. The event starts with the source element and “bubbles” up to its parent, and its parent in turn, and so on.
Tunneling events start at the element root and travel downward to the source element.
Solution: The Web introduced the concept of separating visual style from functionality to many developers. WPF uses the same idea with its powerful style system.
Styles are resources that can be defined in dedicated resource files or in an element’s own resource section.
This simple style merely sets the FontSize
property of a Label to 12.
Because the Style
in this example specifies a TargetType
but does not specify a name, it applies to all Label elements in its scope. You could alternatively name the style with <Style x:Key="MyStyle" ... />
and apply it to a specific Label with <Label Style="{StaticResource MyStyle}" ... />
.
Solution: Triggers are a powerful functionality that can cause certain things to happen when events occur or data changes. They can be used in particular with styles to cause visual changes in response to the user’s input.
This example modifies the same Label style as before to change the look when the user hovers the mouse over it (see Figure 18.4).
Solution: We already have some simple data binding in the preceding examples. It is, in fact, hard to do WPF without using data binding, because that is what it naturally wants to do.
For binding to work, the binding must be associated with a DataContext
. Elements search up their parent tree for the closest DataContext
. Data binding is relative to that, unless explicitly stated otherwise (using ElementName
or RelativeSource
).
For the ImageViewer sample application, a data object was created specifically for data binding and set to the window’s DataContext
.
Listings 18.1 and 18.2 demonstrate data binding with a drag-and-drop image viewer. Every time an image is dropped on the application, a new ImageInfoViewModel
is created and assigned to the window’s DataContext
property. The view model abstracts away the actual model (the image) and presents a simple class that can be used in data binding for the UI. For more on the view model and the overall design pattern here, see Chapter 25, “Application Patterns and Tips.”
Here’s a part of the XAML that binds elements to the ImageInfoViewModel
object:
Every time you drag an image onto the ImageViewer application, the UI will update with information about that image automatically when the ImageInfo
property is set.
Solution: Starting in .NET 3.5 SP1, you can use the StringFormat
property.
Solution: Define a converter class derived from IValueConverter
and implement one or both methods:
In your window XAML, define a resource and specify the converter where needed. In the following example, the background color is bound to the filename with this converter so that it becomes red if the filename is null:
Solution: Binding to a collection of items is fairly straightforward. You can declare XAML like this:
However, because WPF doesn’t know how to display each item in the collection, it’s just going to call ToString()
on each item. To customize the display, you need to define a data template, which is covered in the next section.
Solution: You can use a data template. Data templates are defined as resources:
Then modify the ListBox’s ItemTemplate
property to refer to it:
The result is an attractive display of an arbitrary number of collection items, as previously seen in Figure 18.4.
Solution: If you look closely at Figures 18.3 and 18.4, you’ll see that the latter shows a caption under the image, whereas the former does not. This is accomplished with control templates.
The two ControlTemplate
s are defined, as usual, in the resources:
And the default template is set in the element’s definition:
An event handler (discussed previously) on the radio buttons causes the template to be switched:
Solution: As far as WPF is concerned, animation is the change of an element’s properties over time. For example, you could change the x position of a button from 1 to 100 over, say, 5 seconds. This would have the effect of moving the button on the screen during that time. You can animate any dependency property in WPF.
The ImageViewer application uses a single animation to fade in the left-side panel over 5 seconds when the application starts. Figure 18.5 shows the application with it faded in about halfway.
First, define the storyboard with animation in the Window’s resources:
Once the animation is defined, you need to start with a trigger:
Solution: Starting in this section, you’ll see that WPF provides the unprecedented ability to merge 3D graphics with standard user interface elements and multimedia.
Effectively using 3D graphics requires learning a little about cameras, lighting, materials, and coordinate systems. Thankfully, WPF makes a lot of this very easy.
This simple demonstration creates a cube, complete with color and lighting, as seen in Figure 18.6
First, the geometry needs to be defined. For our simple examples, the shapes will be defined in the Window.Resources
sections. Here is the definition for the six faces of the cube:
The actual placement of these mesh geometries is done inside a Viewport3D
element:
Solution: A plain-old cube is great, but what about one of those cube faces playing a movie (or showing an image, or any other WPF element)? Now, that is pretty cool. WPF makes this almost too easy.
The movie can be loaded into the MediaElement
with code like this:
mediaPlayer.Source = new Uri(filename);
mediaPlayer.Play();
In the next section, we’ll hook it up to some controls, also in 3D.
Yes, the ability to do this kind of stuff is pretty neat, but don’t go overboard. A movie pasted on to a 3D surface might make sense as part of a presentation, in a store kiosk, or perhaps as supporting material or as an animation to bring the movie to the foreground, but it’s probably not the best scenario for watching a whole movie. Just because you can do something doesn’t always mean you should.
Solution: For that, Microsoft has released 3D Tools for Windows Presentation Foundation, available at http://3dtools.codeplex.com/. This package includes objects that wrap around WPF elements and translate the 3D environment into something the elements can understand.
In this example, let’s add some file selection and playback controls to our crazy video player (see Figure 18.7). You will also need to add a reference to the 3DTools.dll assembly in your project.
Here is the complete code:
The code-behind provides the button functionality:
Solution: You can easily interoperate between Windows Forms and WPF with the ElementHost control.
Figure 18.8 shows a Windows Forms application with a standard WinForms control and a WPF control. (It’s a button with a Label and TextBox in it just to prove it’s really WPF.)
Solution: It’s just as easy to go the other direction.
Note that there is no NumericUpDown control in WPF (see Figure 18.9).