Introduction to Windows Presentation Foundation
WPF as the New GUI
Before we dive into WPF proper, it is interesting to consider where we're coming from.
User32, à la Charles Petzold
Anyone programming to User32 has, at some point, read one of Petzold's "Programming Windows" books. They all start with an example something like this:
#include <windows.h> LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdline, int cmdshow) { MSG msg; HWND hwnd; WNDCLASSEX wndclass = { 0 }; wndclass.cbSize = sizeof(WNDCLASSEX); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszClassName = TEXT("Window1"); wndclass.hInstance = hInstance; wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); RegisterClassEx(&wndclass); hwnd = CreateWindow(TEXT("Window1"), TEXT("Hello World"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if( !hwnd ) return 0; ShowWindow(hwnd, SW_SHOWNORMAL); UpdateWindow(hwnd); while( GetMessage(&msg, NULL, 0, 0) ) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch(msg) { case WM_DESTROY: PostQuitMessage(WM_QUIT); break; default: return DefWindowProc(hwnd, msg, wparam, lparam); } return 0; }
This is "Hello World" when talking to User32. There are some very interesting things going on here. A specialized type (Window1) is first defined by the calling of RegisterClassEx, then instantiated (CreateWindow) and displayed (ShowWindow). Finally, a message loop is run to let the window receive user input and events from the system (GetMessage, TranslateMessage, and DispatchMessage). This program is largely unchanged from the original introduction of User back in the Windows 1.0 days.
Windows Forms took this complex programming model and produced a clean managed object model on top of the system, making it far simpler to program. Hello World can be written in Windows Forms with ten lines of code:
using System.Windows.Forms; using System; class Program { [STAThread] static void Main() { Form f = new Form(); f.Text = "Hello World"; Application.Run(f); } }
A primary goal of WPF is to preserve as much developer knowledge as possible. Even though WPF is a new presentation system completely different from Windows Forms, we can write the equivalent program in WPF with very similar code1 (changes are in boldface):
using System.Windows; using System; class Program { [STAThread] static void Main() { Window f = new Window(); f.Title = "Hello World"; new Application().Run(f); } }
In both cases the call to Run on the Application object is the replacement for the message loop, and the standard CLR (Common Language Runtime) type system is used for defining instances and types. Windows Forms is really a managed layer on top of User32, and it is therefore limited to only the fundamental features that User32 provides.
User32 is a great 2D widget platform. It is based on an on-demand, clip-based painting system; that is, when a widget needs to be displayed, the system calls back to the user code (on demand) to paint within a bounding box that it protects (with clipping). The great thing about clip-based painting systems is that they're fast; no memory is wasted on buffering the content of a widget, nor are any cycles wasted on painting anything but the widget that has been changed.
The downsides of on-demand, clip-based painting systems relate mainly to responsiveness and composition. In the first case, because the system has to call back to user code to paint anything, often one component may prevent other components from painting. This problem is evident in Windows when an application hangs and goes white, or stops painting correctly. In the second case, it is extremely difficult to have a single pixel affected by two components, yet that capability is desirable in many scenarios—for example, partial opacity, anti-aliasing, and shadows.
With overlapping Windows Forms controls, the downsides of this system become clear (Figure 1.1). When the controls overlap, the system needs to clip each one. Notice the gray area around the word linkLabel1 in Figure 1.1.
Figure 1.1 Windows Forms controls overlapping. Notice that each control obscures the others.
WPF is based on a retained-mode composition system. For each component a list of drawing instructions is maintained, allowing for the system to automatically render the contents of any widget without interacting with user code. In addition, the system is implemented with a painter's algorithm, which ensures that overlapping widgets are painted from back to front, allowing them to paint on top of each other. This model lets the system manage the graphics resource, in much the same way that the CLR manages memory, to achieve some great effects. The system can perform high-speed animations, send drawing instructions to another machine, or even project the display onto 3D surfaces—all without the widget being aware of the complexity.
To see these effects, compare Figures 1.1 and 1.2. In Figure 1.2 the opacity on all the WPF controls is set so that they're partially transparent, even to the background image.
Figure 1.2 WPF controls overlapping, with opacity set to semitransparency. Notice that all the controls compositing together are visible, including the background image.
WPF's composition system is, at its heart, a vector-based system, meaning that all painting is done through a series of lines. Figure 1.3 shows how vector graphics compare to traditional raster graphics.
Figure 1.3 Comparing vector and raster graphics. Notice that zooming in on a vector graphic does not reduce its crispness.
The system also supports complete transform models, with scale, rotation, and skew. As Figure 1.4 shows, any transformation can be applied to any control, producing bizarre effects even while keeping the controls live and usable.
Figure 1.4 WPF controls with a variety of transformations applied. Despite the transformations, these controls remain fully functional.
Note that when User32 and GDI32 were developed, there was really no notion of container nesting. The design principle was that a flat list of children existed under a single parent window. The concept worked well for the simple dialogs of the 1990s, but today's complex user interfaces require nesting. The simplest example of this problem is the GroupBox control. In the User32 design, GroupBox is behind controls but doesn't contain them. Windows Forms does support nesting, but that feature has revealed many problems with the underlying User32 model of control.
In WPF's composition engine, all controls are contained, grouped, and composited. A button in WPF is actually made up of several smaller controls. This move to embrace composition, coupled with a vector-based approach, enables any level of containment (Figure 1.5).
Figure 1.5 WPF controls are built out of composition and containment. The button shown here contains both text and an image.
To really see the power of this composition, examine Figure 1.6. At the maximum zoom shown, the entire circle represents less than a pixel on the original button. The button actually contains a vector image that contains a complete text document that contains a button that contains another image.
Figure 1.6 The power of composition, as revealed by zooming in on the composite button shown in Figure 1.5
In addition to addressing the limitations of User32 and GDI32, one of WPF's goals was to bring many of the best features from the Web programming model to Windows developers.
HTML, a.k.a. the Web
One of the biggest assets of Web development is a simple entry to creating content. The most basic HTML "program" is really nothing more than a few HTML tags in a text file:
<html> <head> <title>Hello World</title> </head> <body> <p>Welcome to my document!</p> </body> </html>
In fact, all of these tags can be omitted, and we can simply create a file with the text "Welcome to my document!", name it <something>.html, and view it in a browser (Figure 1.7). This amazingly low barrier to entry has made developers out of millions of people who never thought they could program anything.
Figure 1.7 Displaying a simple HTML document in Internet Explorer
In WPF we can accomplish the same thing using a new markup format called XAML (Extensible Application Markup Language), pronounced "zammel." Because XAML is a dialect of XML, it requires a slightly stricter syntax. Probably the most obvious requirement is that the xmlns directive must be used to associate the namespace with each tag:
<FlowDocument xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'> <Paragraph>Welcome to my document!</Paragraph> </FlowDocument>
You can view the file by double-clicking <something>.xaml (Figure 1.8).
Figure 1.8 Displaying a WPF document in Internet Explorer
Of course, we can leverage all the power of WPF in this simple markup. We can trivially implement the button display from Figure 1.5 using markup, and display it in the browser (Figure 1.9).
Figure 1.9 Displaying a WPF document in Internet Explorer using controls and layout from WPF
One of the big limitations of the HTML model is that it really only works for creating applications that are hosted in the browser. With XAML markup, either we can use it in a loose markup format and host it in the browser, as we have just seen, or we can compile it into an application and create a standard Windows application using markup (Figure 1.10):
Figure 1.10 Running an application authored in XAML. The program can be run in a top-level window or hosted in a browser.
<Window xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' Title='Hello World!'> <Button>Hello World!</Button> </Window>
Programming capability in HTML comes in three flavors: declarative, scripting, and server-side. Declarative programming is something that many people don't think of as programming. We can define behavior in HTML with simple markup tags like <form /> that let us perform actions (generally posting data back to the server). Script programming lets us use JavaScript to program against the HTML Document Object Model (DOM). Script programming is becoming much more fashionable because now enough browsers have support for a common scripting model to make scripts run everywhere. Server-side programming lets us write logic on the server that interacts with the user (in the Microsoft platform, that means ASP.NET programming).
ASP.NET provides a very nice way to generate HTML content. Using repeaters, data binding, and event handlers, we can write simple server-side code to create simple applications. One of the more trivial examples is simple markup injection:
<%@ Page %> <html> <body> <p><%=DateTime.Now().ToString()%></p> </body> </html>
The real power of ASP.NET comes in the rich library of server controls and services. Using a single control like DataGrid, we can generate reams of HTML content; and with services like membership we can create Web sites with authentication easily.
The big limitation of this model is the requirement to be online. Modern applications are expected to run offline or in occasionally connected scenarios. WPF takes many of the features from ASP.NET—repeaters and data binding, for example—and gives them to Windows developers with the additional ability to run offline.
One of the primary objectives of WPF was to bring together the best features of both Windows development and the Web model. Before we look at the features of WPF, it is important to understand the new programming model in the .NET Framework 3.0: XAML.