Chapter 1. Hello, WPF

WPF is a completely new presentation framework, integrating the capabilities of many frameworks that have come before it, including User, GDI, GDI+, and HTML, as well as being heavily influenced by toolkits targeted at the Web, such as Adobe Flash, and popular Windows applications like Microsoft Word. This chapter will give you the basics of WPF from scratch, and then a whirlwind tour of the things you'll read about in detail in the chapters that follow.

WPF from Scratch

Example 1-1 is pretty much the smallest WPF "application" you can write in C#.

Example 1-1. Minimal C# WPF application

// MyApp.cs
using System;
using System.Windows; // the root WPF namespace
namespace MyFirstWpfApp {
  class MyApp {
    [STAThread]
    static void Main(  ) {
     // the WPF message box
     MessageBox.Show("Hello, WPF");
    }
  }
}

Tip

The STAThread attribute signals .NET to make sure that when COM is initialized on the application's main thread, it's initialized to be compatible with single-threaded UI work, as required by WPF applications.

In fact, this is such a lame WPF application that it doesn't even use any of the services of WPF; the call to MessageBox.Show is just an interop call to Win32. However, it does require the same infrastructure required of other WPF applications, so it serves as a useful starting point for our explorations.

Building Applications

Building this application (Example 1-2) is a matter of firing off the C# compiler from a command shell with the appropriate environment variables.[3] (The command line here has been spread across multiple lines for readability, but you need to put it all on one line.)

Example 1-2. Building a WPF application manually

C:\1st> csc /target:winexe /out:.\1st.exe
  /r:System.dll
  /r:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll"
  /r:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\
PresentationCore.dll"
  /r:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\
PresentationFramework.dll"
  MyApp.cs

Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.312
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

Here, we're telling the C# compiler that we'd like to create a Windows application (instead of a Console application, which we get by default), putting the result, 1st.exe, into the current folder, referencing the three main WPF assemblies (WindowsBase, PresentationCore, and PresentationFramework), along with the core .NET System assembly, and compiling the MyApp.cs source file.

Running the resulting 1st.exe produces the world's lamest WPF application, as shown in Figure 1-1.

A lame WPF application

Figure 1-1. A lame WPF application

In anticipation of less lame WPF applications with more source files and more compilation options, let's refactor the compilation command line into an msbuild project file (Example 1-3).

Example 1-3. A minimal msbuild project file

<!-- 1st.csproj -->
<Project
  DefaultTargets="Build"
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputType>winexe</OutputType>
    <OutputPath>.\</OutputPath>
    <Assembly>1st.exe</Assembly>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="MyApp.cs" >
    <Reference Include="System" />
    <Reference Include="WindowsBase" />
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
  </ItemGroup>
  <Import Project="$(MsbuildBinPath)\Microsoft.CSharp.targets" />
</Project>

The msbuild tool is a .NET 2.0 command-line application that understands XML files in the form shown in Example 1-3. The file format is shared between msbuild and Visual Studio 2005 so that you can use the same project files for both command-line and integrated development environment (IDE) builds. In this .csproj file (which stands for "C# Project"), we're saying the same things we said to the C# compiler—in other words, we'd like a Windows application, we'd like the output to be 1st.exe in the current folder, and we'd like to reference the System assembly and the main WPF assemblies while compiling the MyApp.cs file. The actual smarts of how to turn these minimal settings into a compiled .NET application are contained in the .NET 2.0 Microsoft.CSharp.targets file that's imported at the bottom of the file.

Executing msbuild.exe on the 1st.csproj file looks like Example 1-4.

Example 1-4. Building using msbuild

C:\1st>msbuild 1st.csproj
Microsoft (R) Build Engine Version 2.0.50727.312
[Microsoft .NET Framework, Version 2.0.50727.312]
Copyright (C) Microsoft Corporation 2005. All rights reserved.

Build started 2/4/2007 2:24:46 PM.
_________________________________________________
Project "C:\1st\1st.csproj" (default targets):

Target PrepareForBuild:
    Creating directory "obj\Debug\".

Target CoreCompile:
    C:\Windows\Microsoft.NET\Framework\v2.0.50727\Csc.exe /noconfig/nowarn:1701
,1702 /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0
\PresentationCore.dll" /reference:"C:\Program Files\Reference Assemblies\Microso
ft\Framework\v3.0\PresentationFramework.dll" /reference:C:\Windows\Microsoft.NET
\Framework\v2.0.50727\System.dll /reference:"C:\Program Files\Reference Assembli
es\Microsoft\Framework\v3.0\WindowsBase.dll" /debug+ /out:obj\Debug\1st.exe /tar
get:winexe MyApp.cs
Target _CopyFilesMarkedCopyLocal:
    Copying file from "C:\Program Files\Reference Assemblies\Microsoft\Framework
\v3.0\PresentationCore.dll" to ".\PresentationCore.dll".
    Copying file from "C:\Program Files\Reference Assemblies\Microsoft\Framework
\v3.0\System.Printing.dll" to ".\System.Printing.dll".
    Copying file from "C:\Program Files\Reference Assemblies\Microsoft\Framework
\v3.0\PresentationCore.xml" to ".\PresentationCore.xml".
   Copying file from "C:\Program Files\Reference Assemblies\Microsoft\Framework
\v3.0\System.Printing.xml" to ".\System.Printing.xml".
Target CopyFilesToOutputDirectory:
    Copying file from "obj\Debug\1st.exe" to ".\1st.exe".
    1st -> C:\1st\1st.exe
    Copying file from "obj\Debug\1st.pdb" to ".\1st.pdb".

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:04.15

As I mentioned, msbuild and Visual Studio 2005 share a project file format, so loading the project file into Visual Studio is as easy as double-clicking on 1st.csproj (as shown in Figure 1-2.

Loading the minimal msbuild project file into Visual Studio

Figure 1-2. Loading the minimal msbuild project file into Visual Studio

Unfortunately, as nice as the project file makes building our WPF application, the application itself is still lame.

WPF Applications

A real WPF application is going to need more than a message box. WPF applications have an instance of the Application class from the System.Windows namespace. The Application class provides methods like Run for starting the application, events like Startup and SessionEnding for tracking lifetime, and properties like Current, ShutdownMode, and MainWindow for finding the global application object, choosing when it shuts down, and getting the application's main window. Typically, the Application class serves as a base for custom application-wide data and behavior Example 1-5.

Example 1-5. A less minimal WPF application

// MyApp.cs
using System;
using System.Windows;

namespace MyFirstWpfApp {
  class MyApp : Application {
    [STAThread]
    static void Main(  ) {
      MyApp app = new MyApp(  );
      app.Startup += app.AppStartup;
      app.Run(  );
    }
    void AppStartup(object sender, StartupEventArgs e) {
      // By default, when all top level windows
      // are closed, the app shuts down
      Window window = new Window(  );
      window.Title = "Hello, WPF";
      window.Show(  );
    }
  }
}

Here, our MyApp class derives from the Application base class. In Main, we create an instance of the MyApp class, add a handler to the Startup event, and kick things off with a call to the Run method. Our Startup handler creates our sample's top-level window, which is an instance of the built-in WPF Window class, making our sample WPF application more interesting from a developer point of view, although visually less so, as shown in Figure 1-3.

A less lame WPF application

Figure 1-3. A less lame WPF application

Although we can create instances of the built-in classes of WPF, such as Window, populating them and wiring them up from the application, it's much more encapsulating (not to mention abstracting) to create custom classes for such things, like the Window1 class Example 1-6.

Example 1-6. Window class declaring its own controls

// Window1.cs
using System;
using System.Windows;
using System.Windows.Controls; // Button et al

namespace MyFirstWpfApp {
    class Window1 : Window {
    public Window1(  ) {
      this.Title = "Hello, WPF";

      // Do something interesting (sorta...)
      Button button = new Button(  );
      button.Content = "Click me, baby, one more time!";
      button.Width = 200;
      button.Height = 25;
      button.Click += button_Click;

      this.Content = button;
    }

    void button_Click(object sender, RoutedEventArgs e) {
      MessageBox.Show(
        "You've done that before, haven't you...",
        "Nice!");
    }
  }
}

In addition to setting its caption text, an instance of our Window1 class will include a button with its Content, Width, and Height properties set, and its Click event handled. With this initialization handled in the Window1 class itself, our app's startup code looks a bit simpler (even though the application behavior itself has gotten "richer"; see Example 1-7).

Example 1-7. Simplified Application instance

// MyApp.cs
using System;
using System.Windows;

namespace MyFirstWpfApp {
  class MyApp : Application {
    [STAThread]
    static void Main(string[] args) {
      MyApp app = new MyApp(  );
      app.Startup += app.AppStartup;
      app.Run(  );
    }

    void AppStartup(object sender, StartupEventArgs e) {
      // Let the Window1 initialize itself
      Window window = new Window1(  );
      window.Show(  );
    }
  }
}

The results (after updating the .csproj file appropriately) are shown in Figure 1-4 and are unlikely to surprise you much.

A slightly more interesting WPF application

Figure 1-4. A slightly more interesting WPF application

As the Window1 class gets more interesting, we're mixing two very separate kinds of code: the "look," represented by the initialization code that sets the window and child window properties, and the "behavior," represented by the event handling code. As the look is something that you're likely to want handled by someone with artistic sensibilities (a.k.a. turtleneck-wearing designer types) whereas the behavior is something you'll want to leave to the coders (a.k.a. pocket-protector-wearing engineer types), separating the former from the latter would be a good idea. Ideally, we'd like to move the imperative "look" code into a declarative format suitable for tools to create with some drag-and-drop magic. For WPF, that format is XAML.

XAML

XAML is an XML-based language for creating and initializing .NET objects. It's used in WPF as a human-authorable way of describing the UI, although you can use it for a much larger range of CLR types than just those in WPF. Example 1-8 shows how we declare the UI of our Window-derived class using XAML.

Example 1-8. Declaring a Window in XAML

<!-- Window1.xaml -->
<Window
  x:Class="MyFirstWpfApp.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Hello, WPF">

  <Button
    x:Name="button"
    Width="200"
    Height="25"
    Click="button_Click">Click me, baby, one more time!</Button>

</Window>

The root element, Window, is used to declare a portion of a class, the name of which is contained in the Class attribute from the XAML XML namespace (declared with a prefix of "x" using the "xmlns" XML namespace syntax). The two XML namespace declarations pull in two commonly used namespaces for XAML work, the one for XAML itself (the one with the "x" prefix) and the one for WPF (which we've declared as the default for this XML file). You can think of the XAML in Example 1-8 as creating the partial class definition in Example 1-9.

Example 1-9. C# equivalent of XAML from Example 1-8

namespace MyFirstWpfApp {
  partial class Window1 : Window {
    Button button;

    void InitializeComponent(  ) {
      // Initialize Window1
      this.Title = "Hello, WPF";

      // Initialize button
      button = new Button(  );
      button.Width = 200;
      button.Height = 25;
      button.Click += button_Click;

      this.AddChild(button);
    }
  }
}

XAML was built to be as direct a mapping from XML to .NET as possible. Generally, a XAML element is a .NET class name and a XAML attribute is the name of a property or an event on that class. This makes XAML useful for more than just WPF classes; pretty much any old .NET class that exposes a default constructor can be initialized in a XAML file.

Notice that we don't have the definition of the click event handler in this generated class. For event handlers and other initializations and helpers, a XAML file is meant to be matched with a corresponding code-behind file, which is a .NET language code file that implements behavior in code "behind" the look defined in the XAML. Traditionally, this file is named with a .xaml.cs extension and contains only the things not defined in the XAML. With the XAML from Example 1-8 in place, we can reduce our single-buttoned main window code-behind file to the code in Example 1-10.

Example 1-10. C# code-behind file

// Window1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;

namespace MyFirstWpfApp {
  public partial class Window1 : Window {
    public Window1(  ) {
      InitializeComponent(  );
    }

    void button_Click(object sender, RoutedEventArgs e) {
      MessageBox.Show(...);
    }
  }
}

Notice the partial keyword modifying the Window1 class, which signals to the compiler that the XAML-generated class is to be paired with this human-generated class to form one complete class, each depending on the other. The partial Window1 class defined in XAML depends on the code-behind partial class to call the InitializeComponent method and to handle the click event. The code-behind class depends on the partial Window1 class defined in XAML to implement InitializeComponent, thereby providing the look of the main window (and related child controls).

Further, as mentioned, XAML is not just for visuals. For example, nothing is stopping us from moving most of the definition of our custom MyApp class into a XAML file (Example 1-11).

Example 1-11. Declaring an application in XAML

<!-- MyApp.xaml -->
<Application
  x:Class="MyFirstWpfApp.MyApp"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Startup="AppStartup">
</Application>

This reduces the MyApp code-behind file to the event handler in Example 1-12.

Example 1-12. Application code-behind file

// MyApp.xaml.cs
using System;
using System.Windows;

namespace MyFirstWpfApp {
  public partial class MyApp : Application {
    void AppStartup(object sender, StartupEventArgs e) {
      Window window = new Window1(  );
      window.Show(  );
    }
  }
}

You may have noticed that we no longer have a Main entry point to create the instance of the application-derived class and call its Run method. That's because WPF has a special project setting to specify the XAML file that defines the application class, which appears in the msbuild project file (Example 1-13).

Example 1-13. Specifying the application's XAML in the project file

<!-- MyFirstWpfApp.csproj -->
<Project ...>
  <PropertyGroup>
    <OutputType>winexe</OutputType>
    <OutputPath>.\</OutputPath>
    <Assembly>1st.exe</Assembly>
  </PropertyGroup>
  <ItemGroup>
    <ApplicationDefinition Include="MyApp.xaml" />
    <Page Include="Window1.xaml" />
    <Compile Include="Window1.xaml.cs">
    <DependentUpon>Window1.xaml</DependentUpon>
    </Compile>
    <Compile Include="MyApp.xaml.cs" />
    <DependentUpon>MyApp.xaml</DependentUpon>
    </Compile>
    <Reference Include="System" />
    <Reference Include="WindowsBase" />
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
  </ItemGroup>
  <Import Project="$(MsbuildBinPath)\Microsoft.CSharp.targets" />
  <Import Project="$(MSBuildBinPath)\Microsoft.WinFX.targets" />
</Project>

The combination of the ApplicationDefinition element and the .NET 3.0-specific Microsoft.WinFX.targets file produces an application entry point that will create our application for us. Also notice in Example 1-13 that we've replaced the MyApp.cs file with the MyApp.xaml.cs file, added the Window1.xaml.cs file, and included the window's corresponding XAML file as a Page element (we don't do the same thing for the application's XAML file, as it's already referenced in the ApplicationDefinition element). The XAML files will be compiled into partial class definitions using the instructions in the Microsoft.WinFX.targets file. The DependentUpon element is there to associate a code-behind file with its XAML file. This isn't necessary for the build process, but it's useful for tools that want to show the association. For example, Visual Studio uses DependentUpon to show the code-behind file nested under the XAML file.

This basic arrangement of artifacts (i.e., application and main windows each split into a XAML and a code-behind file) is such a desirable starting point for a WPF application that creating a new project using the "Windows Application (WPF)" project template from within Visual Studio 2005 gives you the same initial configuration, as shown in Figure 1-5.

The result of running the WPF Application project template

Figure 1-5. The result of running the WPF Application project template

Editing XAML

Now that we've seen the wonder that is declarative UI description in XAML, you may wonder, "Do I get all the fun of editing the raw XML, or are there some tools that can join in the fun, too?" The answer is "sort of." For example, if you've got the .NET Framework 3.0 extensions for Visual Studio 2005 (the same extensions that give you the WPF project templates in VS05), you will have a visual editor for XAML files that works very similarly to the built-in Windows Forms Designer. It will trigger by default when you double-click a file in the Solution Explorer, or you can right-click on a XAML file in the Solution Expression and choose Open With. One of the options offered will be "WPF Designer (Cider)" (where "Cider" is the codename for the WPF Designer still under development). The WPF Designer allows for drag-and-drop-style construction of XAML files with elements from the Toolbox and setting properties in the property browser. In addition, you can see the XAML as the designer makes changes, and in fact, you can make changes in the XAML view itself and see those reflected in the designer. Figure 1-6 shows the WPF Designer in action.

The WPF Designer in action

Figure 1-6. The WPF Designer in action

Tip

Unfortunately, as of the writing of this book, the WPF Designer is still very much under development and such basic features as visually adding event handlers, let alone more advanced features like data binding, styles, control templates, and animation, are not supported, which is why you're unlikely to do much with it. If you're following along with the Visual Studio "Orcas" beta, you'll get more current (and more full-featured) versions of the WPF Designer, but if you can't wait, you have other choices, including two XAML designer tools (Microsoft Expression Blend and Microsoft Expression Design), a third-party XAML 3D editor (ZAM 3D), and several conversion tools from other popular vector drawing formats (e.g., Adobe Illustrator and Flash), all of which are currently downloadable at the time of this writing.[4]

Another very useful tool for playing with XAML is the XamlPad tool that comes with the Windows SDK. It actually shows the visual representation of your XAML as you type it, as shown in Figure 1-7.

XamlPad in action

Figure 1-7. XamlPad in action

XamlPad has some limitations; the most important is that it doesn't allow code (e.g., x:Class or event handler declarations), but as instant gratification, it can't be beat.

WPF provides a number of services for applications that we haven't covered, including lifetime management and ClickOnce-based deployment. In addition, although WPF doesn't provide any direct support for application instance management or settings, the .NET 2.0 support for both of these features integrates with WPF. Chapter 2 covers all of these topics.



[3] * Start → All Programs → Microsoft Windows SDK → CMD Shell.

[4] * Michael Swanson, the general manager of the Microsoft Platform Evangelist team, maintains a wonderful list of WPF-related first- and third-party tools and controls for your development enjoyment at http://blogs.msdn.com/mswanson/articles/wpftoolsandcontrols.aspx (http://tinysells.com/88

Get Programming WPF, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.