Given all of the functionality provided by the parent classes of the Window type, it is possible to represent a window in your application by either directly creating a Window
object or using this class as the parent to a strongly typed
descendent. Let's examine both approaches in the following code example.
Although most WPF applications will make use of XAML, doing so is
entirely optional. Anything that can be expressed in XAML can be
expressed in code and (for the most part) vice versa. If you wish, it is
possible to build a complete WPF project using the underlying object
model and procedural code.
To illustrate, let's create a minimal but complete application without the use of XAML using the Application and Window
classes directly. Begin by creating a new Console Application named
WpfAppAllCode. Next, access the Project | Add
Reference dialog box and add a reference to WindowsBase.dll, PresentationCore.dll, System.Xaml.dll and PresentationFramework.dll.
Now, update your initial C# file with the following code, which creates a window of modest functionality:
// A simple WPF application, written without XAML.
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfAppAllCode
{
// In this first example, you are defining a single class type to
// represent the application itself and the main window.
class Program : Application
{
[STAThread]
static void Main(string[] args)
{
// Handle the Startup and Exit events, and then run the application.
Program app = new Program();
app.Startup += AppStartUp;
app.Exit += AppExit;
app.Run(); // Fires the Startup event.
}
static void AppExit(object sender, ExitEventArgs e)
{
MessageBox.Show("App has exited");
}
static void AppStartUp(object sender, StartupEventArgs e)
{
// Create a Window object and set some basic properties.
Window mainWindow = new Window();
mainWindow.Title = "My First WPF App!";
mainWindow.Height = 200;
mainWindow.Width = 300;
mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
mainWindow.Show();
}
}
}
NOTE
The Main() method of a WPF application must be attributed with the [STAThread] attribute, which ensures any legacy COM objects used by your application are thread-safe. If you do not annotate Main() in this way, you will encounter a runtime exception.
Note that the Program class extends the System.Windows.Application class. Within the Main() method, you create an instance of the application object and handle the Startup and Exit events using method group conversion syntax.
Of course, if you wish, you can specify the underlying delegates directly by name. In the following modified Main() method, notice that the Startup event works in conjunction with the StartupEventHandler delegate, which can only point to methods taking an Object as the first parameter and a StartupEventArgs as the second. The Exit event, on the other hand, works with theExitEventHandler delegate, which demands that the method pointed to take an ExitEventArgs type as the second parameter:
[STAThread]
static void Main(string[] args)
{
// This time, specify the underlying delegates.
MyWPFApp app = new MyWPFApp();
app.Startup += new StartupEventHandler(AppStartUp);
app.Exit += new ExitEventHandler(AppExit);
app.Run(); // Fires the Startup event.
}
In any case, the AppStartUp() method has been configured to create a Window object, establish some very basic property settings, and call Show() to display the window on the screen in a modeless fashion (the ShowDialog() method can be used to launch a modal dialog). The AppExit() method simply makes use of the WPF MessageBox class to display a diagnostic message when the application is being terminated.
Once you compile and run the
project, you will find a very simple main window that can be minimized,
maximized, and closed. To spice things up a bit, you need to add some
user interface elements. Before you do, however, you should refactor
your code base to account for a strongly typed and well-encapsulated Window-derived class.
1. Creating a Strongly Typed Window
Currently, the Application-derived class directly creates an instance of the Window type upon application startup. Ideally, you would create a class deriving from Window
in order to encapsulate itsappearance and functionality. Assume that
you have created the following class definition within your current WpfAppAllCode namespace (if you place this class in a new C# file, be sure to import the System.Windows namespace):
class MainWindow : Window
{
public MainWindow(string windowTitle, int height, int width)
{
this.Title = windowTitle;
this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
this.Height = height;
this.Width = width;
}
}
You can now update your Startup event handler to simply directly create an instance of MainWindow:
static void AppStartUp(object sender, StartupEventArgs e)
{
// Create a MainWindow object.
MainWindow wnd = new MainWindow("My better WPF App!", 200, 300);
wnd.Show();
}
Once the program is
recompiled and executed, the output is identical. The obvious benefit is
that you now have a strongly typed window class to build upon.
NOTE
When you create a Window (or Window-derived) object, it will automatically be added to the windows collection of the Application class (via some constructor logic found in the Window class itself). You can use the Application.Windows property to iterate over the list of Window objects currently in memory.
2. Creating a Simple User Interface
Adding a UI element to a Window in C# code will involve the following basic steps:
Define a member variable to represent the control.
Configure the control's look and feel upon Window construction.
Assign the control to the inherited Content property, or alternatively, as a parameter to the inherited AddChild() method.
Recall that the WPF control content model demands that the Content
property is set only once. Of course, a Window that only contained a
single UI control would be quite useless. Therefore, in almost every
case, the "single piece of content" that is assigned to the Content property is, in reality, a layout manager, such as DockPanel, Grid, Canvas, or StackPanel.
Within the layout manager, you can have any combination of internal
controls, including other nested layout managers.
For now, you will add a single Button control to your Window
derived class. When you click the button, you will close the current
window, which will indirectly terminate the application, as you have no
other windows in memory. Ponder the following update to the MainWindow class (be sure you have imported System.Windows.Controls to gain access to the Button class):
class MainWindow : Window
{
// Our UI element.
private Button btnExitApp = new Button();
public MainWindow(string windowTitle, int height, int width)
{
// Configure button and set the child control.
btnExitApp.Click += new RoutedEventHandler(btnExitApp_Clicked);
btnExitApp.Content = "Exit Application";
btnExitApp.Height = 25;
btnExitApp.Width = 100;
// Set the content of this window to a single button.
this.Content = btnExitApp;
// Configure the window.
this.Title = windowTitle;
this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
this.Height = height;
this.Width = width;
this.Show();
}
private void btnExitApp_Clicked(object sender, RoutedEventArgs e)
{
// Close the window.
this.Close();
}
}
In any case, once you recompile and run this application, you will find the customized window shown in Figure 1.
Notice that your button is automatically placed in the dead center of
the window's client area; this is the default behavior when content is
not placed within a WPF panel type.