Condividi tramite


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:

Word Processor dialog box

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.

Open dialog box

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.

Save As dialog box

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.

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.

Print dialog box

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:

Margins dialog box

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).

NoteNote:

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:

Invalid left margin

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.

NoteNote:

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.

Find 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

Popup Overview

Other Resources

Dialog Box Sample
Wizard Sample
ColorPicker Custom Control Sample
Font Dialog Box Demo