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 MyFirstAvalonApp {
class MyApp {
[STAThread]
static void Main( ) {
// the WPF message box
MessageBox.Show("Hello, Avalon");
}
}
}
 |
If you're not familiar with the STAThread attribute,
it's a signal to .NET that when COM is initialized on the application's main
thread, to make sure it's initialized to be compatible with single-threaded UI
work , as required by WPF applications.
|
|
1.1.1. Building Applications
Building this application is a matter of firing off the C#
compiler from a command shell with the appropriate environment variables,
as in Example 1-2.
Example 1-2. Building a WPF application manually
C:\1st>csc /target:winexe /out:.\1st.exe
/r:System.dll
/r:c:\WINDOWS\Microsoft.NET\Windows\v6.0.4030\WindowsBase
.dll
/r:c:\WINDOWS\Microsoft.NET\Windows\v6.0.4030\PresentationCore
.dll
/r:c:\WINDOWS\Microsoft.NET\Windows\v6.0.4030\PresentationFramework
.dll
MyApp.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50215.44
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50215
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, bringing
in 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, refactoring the
compilation command line into an msbuild project file
is recommended, as in 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>
Msbuild is a .NET 2.0 command-line tool 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 IDE builds. In this .csproj file (which
stands for "C# Project"), we're saying the same things that we said to the C#
compileri.e., that we'd like a Windows application, that we'd like the output
to be 1st.exe in the current folder, and that we'd like to reference the
main WPF assemblies while compiling the MyApp.cs file. The actual smarts
of how to turn these minimal settings into a compiled WPF application are
contained in the .NET 2.0 Microsoft.CSharp.targets file that 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.50215.44
[Microsoft .NET Framework, Version 2.0.50215.44]
Copyright (C) Microsoft Corporation 2005. All rights reserved.
Build started 7/6/2005 8:20:39 PM.
________________________________________________ _ _
Project "C:\1st\1st.csproj" (default targets):
Target PrepareForBuild:
Creating directory "obj\Release\".
Target CompileRdlFiles:
Skipping target "CompileRdlFiles" because it has no inputs.
Target CoreCompile:
Csc.exe /noconfig /nowarn:"1701;1702" /reference:C:\WINDOWS\Microsoft.net\Wi
ndows\v6.0.4030\PresentationCore.dll /reference:C:\WINDOWS\Microsoft.net\Windows
\v6.0.4030\PresentationFramework.dll /reference:C:\WINDOWS\Microsoft.NET\Framewo
rk\v2.0.50215\System.dll /reference:C:\WINDOWS\Microsoft.net\Windows\v6.0.4030\W
indowsBase.dll /out:obj\Release\1st.exe /target:winexe MyApp.cs
Target CopyAppConfigFile:
Skipping target "CopyAppConfigFile" because it has no outputs.
Target CopyFilesToOutputDirectory:
Copying file from "obj\Release\1st.exe" to ".\1st.exe".
1st -> C:\1st\1st.exe
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.98
As I mentioned, msbuild and Visual Studio 2005 share a project
file format, so loading the project file into VS is as easy as double-clicking
on 1st.csproj, which provides us all of the rights and privileges
thereof (as shown in Figure 1-2).
Unfortunately, as nice as the project file makes building our
WPF application, the application itself is still lame.
1.1.2. The Application Object
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 events like StartingUp and ShuttingDown
for tracking lifetime; methods like Run for starting the application;
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 behavior, as in
Example 1-5.
Example 1-5. A less minimal WPF application
// MyApp.cs
using System;
using System.Windows;
namespace MyFirstAvalonApp {
class MyApp : Application {
[STAThread]
static void Main(string[] args) {
MyApp app = new MyApp( );
app.StartingUp += app.AppStartingUp;
app.Run(args);
}
void AppStartingUp(object sender, StartingUpCancelEventArgs
e) {
// By default, when all top level windows
// are closed, the app shuts down
Window window = new Window( );
window.Text = "Hello, Avalon";
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 StartingUp event, and kick things off with a call
to the Run method, passing the command-line arguments passed to Main.
Those same command-line arguments are available in the StartingUpCancelEventArgs
passed to the StartingUp event handler. (The StartingUp event
handler will show its value as we move responsibility for the application's
entry point to WPF later in this chapter.)
Our StartingUp 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.
While we can create instances of the built-in classes of WPF
like 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 in
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 MyFirstAvalonApp {
class Window1 : Window {
public Window1( ) {
this.Text = "Hello, Avalon";
// 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.AddChild(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 itself has gotten "richer"), as in
Example 1-7.
Example 1-7. Simplified Application instance
// MyApp.cs
using System;
using System.Windows;
namespace MyFirstAvalonApp {
class MyApp : Application {
[STAThread]
static void Main(string[] args) {
MyApp app = new MyApp( );
app.StartingUp += app.AppStartingUp;
app.Run(args);
}
void AppStartingUp(object sender, StartingUpCancelEventArgs e) {
// Let the Window1 initialize itself
Window window = new Window1( );
window.Show( );
}
}
}
The results, shown in Figure
1-4, 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 'n' drop magic. For WPF, that format is
XAML.
1.1.3. XAML
XAML is an XML-based language for creating and initializing .NET
objects. It's used in WPF as a serialization format for objects from the WPF
presentation stack, although it can be used for a much larger range of objects
than that. Example 1-8 shows
how our Window-derived class is declared using XAML.
Example 1-8. Declaring a Window in XAML
<!-- Window1.xaml -->
<Window
x:Class="MyFirstAvalonApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Text="Hello, Avalon">
<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 and the one for WPF. 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 MyFirstAvalonApp {
partial class Window1 : Window {
Button button;
void InitializeComponent( ) {
// Initialize Window1
this.Text = "Hello, Avalon";
// 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, every XAML element is a .NET class name and every 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 initialization
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 "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-9 in place, our single-buttoned main window code-behind file
can be reduced 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 MyFirstAvalonApp {
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 I mentioned, XAML is not just for visuals. For
example, there's nothing stopping us from moving most of the definition of our
custom MyApp class into a XAML file, as in
Example 1-11.
Example 1-11. Declaring an Application in XAML
<!-- MyApp.xaml -->
<Application
x:Class="MyFirstAvalonApp.MyApp"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
StartingUp="AppStartingUp">
</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 MyFirstAvalonApp {
public partial class MyApp : Application {
void AppStartingUp(object sender, StartingUpCancelEventArgs 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, as in Example 1-13.
Example 1-13. Specifying the application's XAML in the
project file
<!-- MyFirstAvalonApp.csproj -->
<Project ...>
<PropertyGroup>
<OutputType>winexe</OutputType>
<OutputPath>.\</OutputPath>
<Assembly>1st.exe</Assembly>
</PropertyGroup>
<ItemGroup>
<ApplicationDefinition Include="MyApp.xaml" />
<Compile Include="Window1.xaml.cs" />
<Compile Include="MyApp.xaml.cs" />
<Reference Include="System" />
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Page Include="Window1.xaml" />
<Page Include="MyApp.xaml" />
</ItemGroup>
<Import Project="$(MsbuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildBinPath)\Microsoft.WinFX.targets" />
</Project>
The combination of the ApplicationDefinition element
and the WinFX-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.c file, and included the two corresponding
XAML files as Page elements. The XAML files will be compiled into
partial class definitions using the instructions in the Microsoft.WinFX.targets
file.
This basic arrangement of artifactsi.e., application and main
window each split into a XAML and a code-behind fileis such a desirable
starting point for a WPF application that creating a new project using the
Avalon Application project template from within Visual Studio 2005 gives you
just that initial configuration, as shown in
Figure 1-5.
|