





















































Windows are the typical top level controls in WPF. By default, a MainWindow class is created by the application wizard and automatically shown upon running the application. In this recipe, we'll take a look at creating and showing other windows that may be required during the lifetime of an application.
Make sure Visual Studio is up and running.
We'll create a new class derived from Window and show it when a button is clicked:
<TextBlock Text="This is the other window" FontSize="20"
VerticalAlignment="Center" HorizontalAlignment="Center" />
<Button Content="Open Other Window" FontSize="30"
Click="OnOpenOtherWindow" />
void OnOpenOtherWindow(object sender, RoutedEventArgs e) {
var other = new OtherWindow();
other.Show();
}
A Window is technically a ContentControl, so can contain anything. It's made visible using the Show method. This keeps the window open as long as it's not explicitly closed using the classic close button, or by calling the Close method. The Show method opens the window as modeless—meaning the user can return to the previous window without restriction. We can click the button more than once, and consequently more Window instances would show up.
The first window shown can be configured using the Application.StartupUri property, typically set in App.xaml. It can be changed to any other window. For example, to show the OtherWindow from the previous section as the first window, open App.xaml and change the StartupUri property to OtherWindow.xaml:
StartupUri="OtherWindow.xaml"
Sometimes the first window is not known in advance, perhaps depending on some state or setting. In this case, the StartupUri property is not helpful. We can safely delete it, and provide the initial window (or even windows) by overriding the Application.OnStartup method as follows (you'll need to add a reference to the System.Configuration assembly for the following to compile):
protected override void OnStartup(StartupEventArgs e) { Window mainWindow = null; // check some state or setting as appropriate if(ConfigurationManager.AppSettings["AdvancedMode"] == "1") mainWindow = new OtherWindow(); else mainWindow = new MainWindow(); mainWindow.Show(); }
This allows complete flexibility in determining what window or windows should appear at application startup.
The WPF application created by the New Project wizard does not expose the ubiquitous Main method. WPF provides this for us – it instantiates the Application object and eventually loads the main window pointed to by the StartupUri property.
The Main method, however, is not just a starting point for managed code, but also provides an array of strings as the command line arguments passed to the executable (if any). As Main is now beyond our control, how do we get the command line arguments?
Fortunately, the same OnStartup method provides a StartupEventArgs object, in which the Args property is mirrored from Main. The downloadable source for this chapter contains the project CH05.CommandLineArgs, which shows an example of its usage. Here's the OnStartup override:
protected override void OnStartup(StartupEventArgs e) {
string text = "Hello, default!";
if(e.Args.Length > 0)
text = e.Args[0];
var win = new MainWindow(text);
win.Show();
}
The MainWindow instance constructor has been modified to accept a string that is later used by the window. If a command line argument is supplied, it is used.
A dialog box is a Window that is typically used to get some data from the user, before some operation can proceed. This is sometimes referred to as a modal window (as opposed to modeless, or non-modal). In this recipe, we'll take a look at how to create and manage such a dialog box.
Make sure Visual Studio is up and running.
We'll create a dialog box that's invoked from the main window to request some information from the user:
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Please enter details:" Grid.ColumnSpan="2"
Margin="4,4,4,20" HorizontalAlignment="Center"/>
<TextBlock Text="Name:" Grid.Row="1" Margin="4"/>
<TextBox Grid.Column="1" Grid.Row="1" Margin="4"
x_Name="_name"/>
<TextBlock Text="City:" Grid.Row="2" Margin="4"/>
<TextBox Grid.Column="1" Grid.Row="2" Margin="4"
x_Name="_city"/>
<StackPanel Grid.Row="3" Orientation="Horizontal"
Margin="4,20,4,4" Grid.ColumnSpan="2"
HorizontalAlignment="Center"> <Button Content="OK" Margin="4" />
<Button Content="Cancel" Margin="4" />
</StackPanel>
public string FullName { get; private set; }
public string City { get; private set; }
<Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <Button Content="Enter Data" Click="OnEnterData" Margin="4" FontSize="16"/> <TextBlock FontSize="24" x_Name="_text" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center"/>
private void OnEnterData(object sender, RoutedEventArgs e) {
var dlg = new DetailsDialog();
if(dlg.ShowDialog() == true) {
_text.Text = string.Format(
"Hi, {0}! I see you live in {1}.",
dlg.FullName, dlg.City);
}
}
private void OnOK(object sender, RoutedEventArgs e) {
FullName = _name.Text;
City = _city.Text;
DialogResult = true;
Close();
}
The Close method dismisses the dialog, returning control to the caller. The DialogResult property indicates the returned value from the call to ShowDialog when the dialog is closed.
private void OnCancel(object sender, RoutedEventArgs e) {
DialogResult = false;
Close();
}
A dialog box in WPF is nothing more than a regular window shown using ShowDialog instead of Show. This forces the user to dismiss the window before she can return to the invoking window. ShowDialog returns a Nullable (can be written as bool? in C#), meaning it can have three values: true, false, and null. The meaning of the return value is mostly up to the application, but typically true indicates the user dismissed the dialog with the intention of making something happen (usually, by clicking some OK or other confirmation button), and false means the user changed her mind, and would like to abort. The null value can be used as a third indicator to some other application-defined condition.
The DialogResult property indicates the value returned from ShowDialog because there is no other way to convey the return value from the dialog invocation directly. That's why the OK button handler sets it to true and the Cancel button handler sets it to false (this also happens when the regular close button is clicked, or Alt + F4 is pressed).
Most dialog boxes are not resizable. This is indicated with the ResizeMode property of the Window set to NoResize. However, because of WPF's flexible layout, it certainly is relatively easy to keep a dialog resizable (and still manageable) where it makes sense (such as when entering a potentially large amount of text in a TextBox – it would make sense if the TextBox could grow if the dialog is enlarged).
Most dialogs can be dismissed by pressing Enter (indicating the data should be used) or pressing Esc (indicating no action should take place). This is possible to do by setting the OK button's IsDefault property to true and the Cancel button's IsCancel property to true. The default button is typically drawn with a heavier border to indicate it's the default button, although this eventually depends on the button's control template.
If these settings are specified, the handler for the Cancel button is not needed. Clicking Cancel or pressing Esc automatically closes the dialog (and sets DiaglogResult to false). The OK button handler is still needed as usual, but it may be invoked by pressing Enter, no matter what control has the keyboard focus within the Window. The CH05.DefaultButtons project from the downloadable source for this chapter demonstrates this in action.
A dialog can be show as modeless, meaning it does not force the user to dismiss it before returning to other windows in the application. This is done with the usual Show method call – just like any Window. The term dialog in this case usually denotes some information expected from the user that affects other windows, sometimes with the help of another button labelled "Apply".
The problem here is mostly logical—how to convey the information change. The best way would be using data binding, rather than manually modifying various objects. We'll take an extensive look at data binding in the next chapter.
Windows has its own built-in dialog boxes for common operations, such as opening files, saving a file, and printing. Using these dialogs is very intuitive from the user's perspective, because she has probably used those dialogs before in other applications. WPF wraps some of these (native) dialogs. In this recipe, we'll see how to use some of the common dialogs.
Make sure Visual Studio is up and running.
We'll create a simple image viewer that uses the Open common dialog box to allow the user to select an image file to view:
<Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <Button Content="Open Image" FontSize="20" Click="OnOpenImage" HorizontalAlignment="Center" Margin="4" /> <Image Grid.Row="1" x_Name="_img" Stretch="Uniform" />
void OnOpenImage(object sender, RoutedEventArgs e) {
var dlg = new OpenFileDialog {
Filter = "Image files|*.png;*.jpg;*.gif;*.bmp",
Title = "Select image to open",
InitialDirectory = Environment.GetFolderPath(
Environment.SpecialFolder.MyPictures)
};
if(dlg.ShowDialog() == true) {
try {
var bmp = new BitmapImage(new Uri(dlg.FileName));
_img.Source = bmp;
}
catch(Exception ex) {
MessageBox.Show(ex.Message, "Open Image");
}
}
The OpenFileDialog class wraps the Win32 open/save file dialog, providing easy enough access to its capabilities. It's just a matter of instantiating the object, setting some properties, such as the file types (Filter property) and then calling ShowDialog. This call, in turn, returns true if the user selected a file and false otherwise (null is never returned, although the return type is still defined as Nullable for consistency).
The look of the Open file dialog box may be different in various Windows versions. This is mostly unimportant unless some automated UI testing is done. In this case, the way the dialog looks or operates may have to be taken into consideration when creating the tests.
The filename itself is returned in the FileName property (full path). Multiple selections are possible by setting the MultiSelect property to true (in this case the FileNames property returns the selected files).
WPF similarly wraps the Save As common dialog with the SaveFileDialog class (in the Microsoft.Win32 namespace as well). Its use is very similar to OpenFileDialog (in fact, both inherit from the abstract FileDialog class).
What about folder selection (instead of files)? The WPF OpenFileDialog does not support that. One solution is to use Windows Forms' FolderBrowseDialog class. Another good solution is to use the Windows API Code Pack described shortly.
Another common dialog box WPF wraps is PrintDialog (in System.Windows.Controls). This shows the familiar print dialog, with options to select a printer, orientation, and so on. The most straightforward way to print would be calling PrintVisual (after calling ShowDialog), providing anything that derives from the Visual abstract class (which include all elements). General printing is a complex topic and is beyond the scope of this book.
Windows also provides common dialogs for selecting colors and fonts. However, these are not wrapped by WPF. There are several alternatives:
The first option is possible, but has two drawbacks: first, it requires adding reference to the System.Windows.Forms assembly; this adds a dependency at compile time, and increases memory consumption at run time, for very little gain. The second drawback has to do with the natural mismatch between Windows Forms and WPF. For example, ColorDialog returns a color as a System.Drawing.Color, but WPF uses System.Windows.Media.Color. This requires mapping a GDI+ color (WinForms) to WPF's color, which is cumbersome at best.
The second option of doing your own wrapping is a non-trivial undertaking and requires good interop knowledge. The other downside is that the default color and font common dialogs are pretty old (especially the color dialog), so there's much room for improvement.
The third option is probably the best one. There are more than a few good candidates for color and font pickers. For a color dialog, for example, you can use the ColorPicker or ColorCanvas provided with the Extended WPF toolkit library on CodePlex (http://wpftoolkit.codeplex.com/). Here's how these may look (ColorCanvas on the left-hand side, and one of the possible views of ColorPicker on the right-hand side):
The Windows API Code Pack is a Microsoft project on CodePlex (http://archive.msdn.microsoft.com/WindowsAPICodePack) that provides many .NET wrappers to native Windows features, in various areas, such as shell, networking, Windows 7 features (this is less important now as WPF 4 added first class support for Windows 7), power management, and DirectX. One of the Shell features in the library is a wrapper for the Open dialog box that allows selecting a folder instead of a file. This has no dependency on the WinForms assembly.