Dialog Boxes Overview
In general, standalone applications have a main window that they use to visualize data, and to expose functionality to interact with the data. As an example, a word processor application visualizes a document, and exposes file management and editing functionality to interact with the document.
As part of the typical lifecycle of a non-trivial application, a main window may display one or more secondary windows to display information to users, to gather information from users, or to do both. Secondary windows are known as dialog boxes, and there are two types: modal and modeless.
A modal dialog box is one that is displayed to ask the user for additional information that is needed to complete a task, and commonly prevents users from activating other windows while it remains open. For example, if a user wants to open a document into a word processor, the application will ask the user for the name of the file that they would like to open. Because a document can't be opened until a user provides a name, the dialog box forces users to either accept the dialog box by providing a file name, or cancel the dialog box, typically by pressing a Cancel button.
A modeless dialog box, on the other hand, does not prevent users from activating other windows while it is open. For example, if the user wants to find occurrences of a particular word in a document, a main window will open a dialog box to ask a user what word they are looking for. But, because finding a word doesn't prevent users from editing the document, it doesn't need to be modal. A modeless dialog box is usually closed by clicking a Close button.
Windows Presentation Foundation (WPF) allows you to create several types of dialog boxes, including message boxes, common dialog boxes, and custom dialog boxes. This topic discusses each, and the Dialog Box Sample provides matching examples.
This topic contains the following sections.
- Message Boxes
- Common Dialog Boxes
- Custom Dialog Boxes
- Related Topics
Message Boxes
A message box is a simple dialog box that can be used to both display information and allow users to make a decision. The following figure illustrates a message box that both displays information and elicits a response from the user:
To create message boxes, you use the MessageBox class. MessageBox allows you to configure the message box text, title, icon, and buttons, using the following code:
// Configure the message box
string messageBoxText = "This document needs to be saved ... .";
string caption = "Word Processor";
MessageBoxButton button = MessageBoxButton.YesNoCancel;
MessageBoxImage icon = MessageBoxImage.Warning;
To show a message box, you call the static Show method:
// Display message box
MessageBox.Show(messageBoxText, caption, button, icon);
To detect which button a user clicked, and to respond accordingly, you can retrieve and process the message box result:
// Display message box
MessageBoxResult messageBoxResult = MessageBox.Show(messageBoxText, caption, button, icon);
// Process message box results
switch (messageBoxResult)
{
case MessageBoxResult.Yes: // Save document and exit
SaveDocument();
break;
case MessageBoxResult.No: // Exit without saving
break;
case MessageBoxResult.Cancel: // Don't exit
e.Cancel = true;
break;
}
One important capability of MessageBox is that it can be shown by XAML browser applications (XBAPs) that are running with partial trust (see Windows Presentation Foundation Security).
More information on using message boxes can be found in the following locations: MessageBox, the MessageBox Sample, or the Dialog Box Sample.
Common Dialog Boxes
While message boxes are useful for displaying information and allowing users to make decisions, most dialog boxes typically need to capture more information from the user than which button was clicked.
Windows implements a variety of dialog boxes that are common to all applications, including dialog boxes for opening files, saving files, and printing. These are known as common dialog boxes because all applications can use them. Consequently, this helps to provide a consistent user experience across applications.
Windows Presentation Foundation (WPF) encapsulates the common open file, save file, and print dialog boxes as managed classes for you to use in standalone applications. This topic provides a brief overview of each.
Open File Dialog
The open file dialog box, shown in the following figure, is used by file opening functionality to retrieve the name of the file that a user wants to open.
The common open file dialog box is implemented as the OpenFileDialog class, and the following code shows how to create, configure, and show one, and how to process the result:
void OpenDocument()
{
// Configure open file dialog box
OpenFileDialog dlg = new OpenFileDialog();
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".wpf"; // Default file extension
dlg.Filter = "Word Processor Files (.wpf)|*.wpf"; // Filter files by extension
// Show open file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
// Open document
string filename = dlg.FileName;
}
}
For more information on the open file dialog box, see Microsoft.Win32.OpenFileDialog.
OpenFileDialog can be used to safely retrieve file names by applications running with partial trust (see Windows Presentation Foundation Security). See the Safe File Upload from an XBAP Sample for a demonstration.
Save File Dialog Box
The save file dialog box, shown in the following figure, is used by file saving functionality to retrieve the name of the file that a user wants to save.
The common save file dialog box is implemented as the SaveFileDialog class, and the following code shows how to create, configure, and show one, and how to process the result:
void SaveDocument()
{
// Configure save file dialog box
SaveFileDialog dlg = new SaveFileDialog();
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".wpf"; // Default file extension
dlg.Filter = "Word Processor Files (.wpf)|*.wpf"; // Filter files by extension
// Show save file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog box results
if (result == true)
{
// Save document
string filename = dlg.FileName;
}
}
For more information on the save file dialog box, see Microsoft.Win32.SaveFileDialog.
Print Dialog Box
The print dialog box, shown in the following figure, is used by printing functionality to choose and configure the printer that a user would like to print a file to.
The common print dialog box is implemented as the PrintDialog class, and the following code shows how to create, configure, and show one:
void PrintDocument()
{
// Configure printer dialog box
PrintDialog dlg = new PrintDialog();
dlg.PageRangeSelection = PageRangeSelection.AllPages;
dlg.UserPageRangeEnabled = true;
// Show save file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog box results
if (result == true)
{
// Print document
}
}
For more information on the print dialog box, see System.Windows.Controls.PrintDialog. For detailed discussion of printing in WPF, see Printing Overview.
Custom Dialog Boxes
When you need a dialog box that is more complex than a message box, and is not supported by the common dialog boxes, you need to create your own dialog box. WPF allows you to create both modal and modeless dialog boxes using Window.
Creating a Modal Custom Dialog Box
A modal dialog box like MarginsDialogBox shown in the following figure has a specific set of visual requirements and behaviors:
This topic describes those and illustrates the fundamental elements of dialog box implementation.
Configuring a Modal Dialog Box
The user interface for a typical dialog box has several key behaviors, including
A Button that users click to close the dialog box and keep processing (OK). This button should also have its IsDefault property set to true. This allows a user to press the ENTER key to accept a dialog box.
A Button that users click to close the dialog box and cancel the function (Cancel). This button should have its IsCancel property set to true. This allows a user to press the ESC key to cancel a dialog box.
Showing a Close button in the title bar.
Showing an Icon.
Showing minimize, maximize, and restore buttons.
Showing a System menu to minimize, maximize, restore, and close the dialog box.
Opening above and in the center of the window that opened the dialog box.
Dialog boxes should be resizable where possible so, to prevent the dialog box from being too small, and to provide the user with a useful default size, you need to set both default and a minimum dimensions respectively.
A dialog box does not typically have a task bar button.
You can create a dialog box with configuration using Window, like the Margin dialog box from the .Dialog Box Sample does:
<Window
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MarginsDialogBox"
xmlns:local="clr-namespace:DialogBoxSample"
Title="Margins"
Height="190" Width="300"
MinHeight="190" MinWidth="300"
ResizeMode="CanResizeWithGrip"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
FocusManager.FocusedElement="{Binding ElementName=leftMarginTextBox}">
<Grid>
<!-- Grid column and row definitions -->
<Grid.Resources>
<!-- Grid column and row definitions -->
...
</Grid.Resources>
<!-- Grid column and row definitions -->
<Grid.ColumnDefinitions>
<!-- Column definitions -->
...
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<!-- Row definitions -->
...
</Grid.RowDefinitions>
<!-- Left Margin -->
<Label Grid.Column="0" Grid.Row="0">Left Margin:</Label>
<TextBox Name="leftMarginTextBox" Grid.Column="1" Grid.Row="0">
</TextBox>
<!-- Top Margin -->
<Label Grid.Column="0" Grid.Row="1">Top Margin:</Label>
<TextBox Name="topMarginTextBox" Grid.Column="1" Grid.Row="1">
</TextBox>
<!-- Right Margin -->
<Label Grid.Column="0" Grid.Row="2">Right Margin:</Label>
<TextBox Name="rightMarginTextBox" Grid.Column="1" Grid.Row="2">
</TextBox>
<!-- Bottom Margin -->
<Label Grid.Column="0" Grid.Row="3">Bottom Margin:</Label>
<TextBox Name="bottomMarginTextBox" Grid.Column="1" Grid.Row="3">
</TextBox>
<!-- Accept or Cancel -->
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4">
<Button Name="okButton" IsDefault="True">OK</Button>
<Button Name="cancelButton" IsCancel="True">Cancel</Button>
</StackPanel>
</Grid >
</Window>
The user experience for a dialog box also extends into the menu bar. When a function that shows a dialog box to collect user information, the menu item that exposes that function will have an ellipses in its header, as shown here:
<Window ... >
...
<MenuItem
Name="formatMarginsMenuItem"
Header="_Margins..."
Click="formatMarginsMenuItem_Click" />
...
</Window>
When a dialog box that only displays information is opened, such as an About dialog box box, the corresponding menu item does not need an ellipses.
Opening a Modal Dialog Box
When a user chooses a menu item that causes a dialog box to be displayed, the dialog box needs to be instantiated, configured, and opened, much like the message box and common dialog boxes.
The following code shows how to instantiate a dialog box:
public partial class MainWindow : Window
{
...
void formatMarginsMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
MarginsDialogBox dlg = new MarginsDialogBox();
}
...
}
Next, the dialog box needs to be configured before being used:
public partial class MainWindow : Window
{
...
void formatMarginsMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
MarginsDialogBox dlg = new MarginsDialogBox();
// Configure the dialog box
dlg.Owner = this;
dlg.DocumentMargin = this.documentTextBox.Margin;
}
...
}
Here, the code is passing default information to the dialog box. It is also setting the System.Windows.Window.Owner property with a reference to the window that is showing the dialog box. In general, you should always set the owner for a dialog box to provide window state-related behaviors that are common to all dialog boxes - see Windows Presentation Foundation Windows Overview).
Note: |
---|
You must provide an owner to support user interface (UI) automation for dialog boxes (see UI Automation Overview). |
Once a dialog box is configured, it is ready to be shown. Modal dialog boxes are shown by calling the ShowDialog method:
public partial class MainWindow : Window
{
...
void formatMarginsMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
MarginsDialogBox dlg = new MarginsDialogBox();
// Configure the dialog box
dlg.Owner = this;
dlg.DocumentMargin = this.documentTextBox.Margin;
// Open the dialog box modally
dlg.ShowDialog();
}
...
}
ShowDialog opens the dialog box modally, after which the user enters data before either accepting or canceling the dialog box.
Validating User-Provided Data
Once a dialog box is opened and the user is entering data, a dialog box has the responsibility to ensure that the data the user is entering is valid, for three reasons:
From a security perspective, all input should be validated.
From an application perspective, validating data prevents erroneous data from being processed by the code, which could potentially throw exceptions.
From a user experience perspective, a dialog box can help users by showing them which data they have entered is invalid.
To validate a bound control in WPF, you need to create a validation rule and associate it with the binding.
A validation rule is a custom class that derives from ValidationRule. The following example shows MarginValidationRule, which checks that a bound value is a double, and is in a specified range:
using System.Windows.Controls;
public class MarginValidationRule : ValidationRule
{
double minMargin;
double maxMargin;
public double MinMargin
{
get { return this.minMargin; }
set { this.minMargin = value; }
}
public double MaxMargin
{
get { return this.maxMargin; }
set { this.maxMargin = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
double margin;
// Is a number?
if (!double.TryParse((string)value, out margin))
{
return new ValidationResult(false, "Not a number.");
}
// Is in range?
if ((margin < this.minMargin) || (margin > this.maxMargin ))
{
string msg = string.Format("Margin must be between {0} and {1}.", this.minMargin, this.maxMargin);
return new ValidationResult(false, msg);
}
// Number is valid
return new ValidationResult(true, null);
}
}
To validate a bound value, you override Validate and return an appropriate ValidationResult, as shown in the preceding code.
To associate the validation rule with the bound control, you use the following markup:
<Window ... >
...
<!-- Left Margin -->
<Label Grid.Column="0" Grid.Row="0">Left Margin:</Label>
<TextBox Name="leftMarginTextBox" Grid.Column="1" Grid.Row="0">
<TextBox.Text>
<Binding Path="Left" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
...
</Window>
WPF automatically applies this binding when data is entered into the bound control, and, by default, displays a red border around the invalid control. This is shown in the following figure:
WPF doesn't restrict a user to the invalid control until they have entered valid data. This is good behavior for a dialog box; a user should be able to freely navigate the controls in a dialog box, irrespective of whether data is valid or not. However, this means that they can enter invalid data and press the OK button. For this reason, your code also needs to validate all controls before a dialog box is accepted, to protect against invalid data. You do this from the OK button Click event handler:
public partial class MarginsDialogBox : Window
{
...
void okButton_Click(object sender, RoutedEventArgs e)
{
// Don't accept the dialog box if there is invalid data
if (!IsValid(this)) return;
...
}
...
// Validate all dependency objects in a window
bool IsValid(DependencyObject node)
{
// Check if dependency object was passed
if (node != null)
{
// Check if dependency object is valid.
// NOTE: Validation.GetHasError works for controls that have validation rules attached
bool isValid = !Validation.GetHasError(node);
if (!isValid)
{
// If the dependency object is invalid, and it can receive the focus,
// set the focus
if (node is IInputElement) Keyboard.Focus((IInputElement)node);
return false;
}
}
// If this dependency object is valid, check all child dependency objects
foreach (object subnode in LogicalTreeHelper.GetChildren(node))
{
if (subnode is DependencyObject)
{
// If a child dependency object is invalid, return false immediately,
// otherwise keep checking
if (IsValid((DependencyObject)subnode) == false) return false;
}
}
// All dependency objects are valid
return true;
}
}
This code enumerates all dependency objects on a window and, if any are invalid (as returned by GetHasError, the invalid control gets the focus, IsValid returns false, and the window is considered invalid.
Once a dialog box is valid, it can safely close and return. This means first setting a dialog result.
Setting the Modal Dialog Result
Opening a dialog box using ShowDialog is fundamentally like a method call: the code in the calling code waits until ShowDialog returns. When ShowDialog does return, the calling code needs to first determine whether the user accepted or canceled the dialog box, which determines whether or not the data collected by the dialog box is applied. To report whether a user accepted or canceled the dialog box, the dialog box must return a dialog box result value.
When a dialog box is accepted, it should return a dialog box result of true, which is achieved by setting the DialogResult property when the OK button is clicked:
<Window ... >
...
<!-- Accept or Cancel -->
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4">
<Button Name="okButton" Click="okButton_Click" IsDefault="True">OK</Button>
...
</StackPanel>
</Grid >
</Window>
public partial class MarginsDialogBox : Window
{
...
void okButton_Click(object sender, RoutedEventArgs e)
{
// Dialog box accepted
this.DialogResult = true;
}
...
}
Note that setting the DialogResult property also causes the window to close automatically, which alleviates the need to explicitly call Close.
A dialog box that is canceled should have a dialog box result of false:
<Window ... >
...
<!-- Accept or Cancel -->
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4">
...
<Button Name="cancelButton" Click="cancelButton_Click" IsCancel="True">Cancel</Button>
</StackPanel>
</Grid >
</Window>
public partial class MarginsDialogBox : Window
{
...
void cancelButton_Click(object sender, RoutedEventArgs e)
{
// Dialog box canceled
this.DialogResult = false;
}
...
}
However, when you have a button whose IsCancel property is set to true and the user presses either the button or the ESC key, DialogResult is automatically set to false. The following code achieves the same effect as the preceding code, without the Click event handler:
<Window ... >
...
<!-- Accept or Cancel -->
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4">
...
<Button Name="cancelButton" IsCancel="True">Cancel</Button>
</StackPanel>
</Grid >
</Window>
A dialog box will automatically return true when a user presses the Close button in the title bar or chooses the Close menu item from the System menu.
Processing Data Returned from a Modal Dialog Box
When DialogResult is set by a dialog box, the calling code can get the dialog box result by inspecting the DialogResult property when the ShowDialog method returns:
public partial class MainWindow : Window
{
...
void formatMarginsMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
MarginsDialogBox dlg = new MarginsDialogBox();
// Configure the dialog box
dlg.Owner = this;
dlg.DocumentMargin = this.documentTextBox.Margin;
// Open the dialog box modally
dlg.ShowDialog();
// Process data entered by user if dialog box is accepted
if (dlg.DialogResult == true)
{
// Update fonts
this.documentTextBox.Margin = dlg.DocumentMargin;
}
}
...
}
If the user accepted the dialog box, the calling code uses that as a cue to retrieve and process the values that were entered by the user into the dialog box.
Note: |
---|
Once ShowDialog has returned, a dialog box cannot be reopened. Instead, you need to create a new instance. |
If a user cancels a dialog box, the calling code should not process data that is provided by the user.
Creating a Modeless Custom Dialog Box
A modeless dialog box, like FindDialogBox shown in the following figure, has the same fundamental appearance as the modal dialog box.
However, the behavior is slightly different, as described in the following topics.
Opening a Modeless Dialog Box
A modeless dialog box is opened by calling the Show method:
void editFindMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
FindDialogBox dlg = new FindDialogBox(this.documentTextBox.Text);
// Open the dialog box modally
dlg.Show();
}
Unlike using ShowDialog to open a dialog box, Show returns immediately. Consequently, the calling window cannot tell when the modeless dialog box is closed and, therefore, won't know when to check for a dialog box result or get data from the dialog box for further processing. Instead, the dialog box needs to create an alternative way to return data to the calling window for processing.
Processing Data Returned from a Modeless Dialog Box
In this example, the FindDialogBox may return one or more find results to the main window, depending on the text being searched for without any specific frequency. As with a modal dialog box, a modeless dialog box can return results using properties. However, the window that owns the dialog box needs to know when to check those properties. One way to enable this is for the dialog box to implement an event that it raises whenever text is found. FindDialogBox implements the TextFoundEvent for this purpose, which first requires a delegate:
public delegate void TextFoundEventHandler(object sender, EventArgs e);
Using the TextFoundEventHandler delegate, FindDialogBox implements the TextFoundEvent like so:
public partial class FindDialogBox : Window
{
...
public event TextFoundEventHandler TextFound;
...
protected virtual void OnTextFound()
{
TextFoundEventHandler textFound = this.TextFound;
if (textFound != null) textFound(this, EventArgs.Empty);
}
}
Consequently, Find can raise the event when a search result is found:
public partial class FindDialogBox : Window
{
...
void findButton_Click(object sender, RoutedEventArgs e) {
...
// Text found
this.index = match.Index;
this.length = match.Length;
OnTextFound();
...
}
...
}
The owner window then needs to register with and handle this event:
public partial class MainWindow : Window
{
...
void dlg_TextFound(object sender, EventArgs e)
{
// Get the find dialog box that raised the event
FindDialogBox dlg = (FindDialogBox)sender;
// Get find results and select found text
this.documentTextBox.Select(dlg.Index, dlg.Length);
this.documentTextBox.Focus();
}
}
Closing a Modeless Dialog Box
Since DialogResult does not need to be set, a modeless dialog can be closed using system provide mechanisms, including:
Clicking the Close button in the title bar
Pressing ALT+F4.
Choosing System Menu | Close.
Alternatively, your code can call Close when the Cancel button is clicked:
public partial class FindDialogBox : Window
{
...
void cancelButton_Click(object sender, RoutedEventArgs e)
{
// Close dialog box
this.Close();
}
}
See Also
Concepts
Other Resources
Dialog Box Sample
Wizard Sample
ColorPicker Custom Control Sample
Font Dialog Box Demo