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.
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 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.
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.
Unfortunately, as nice as the project file makes building our WPF application, the application itself is still lame.
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.
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.
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 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 { publicpartial
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" /> <CompileInclude="Window1.xaml.cs"
> <DependentUpon>Window1.xaml</DependentUpon> </Compile> <CompileInclude="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.
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.
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 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.