Общие сведения о диалоговых окнах
Автономные приложения обычно имеют главное окно, отображающее основные данные, с которыми приложение работает, и предоставляющее функциональные возможности для обработки этих данных через такие механизмы UI, как строки меню, панели инструментов и строки состояния. Нетривиальное приложение может также отображать дополнительные окна для следующих целей:
отображения определенных сведений для пользователей;
сбора сведений от пользователей;
одновременно сбора и отображения сведений.
Такие типы окон называются диалоговыми окнами, и бывают двух типов: модальные и немодальные.
Модальное диалоговое окно отображается функцией, когда для продолжения ей требуются дополнительные данные от пользователя. Поскольку функция зависит от модального диалогового окна для сбора данных, это окно также не разрешает пользователю активизировать другие окна в приложении, пока оно остается открытым. В большинстве случаев модальное диалоговое окно разрешает пользователю завершить работу с модальным диалоговым окном, нажав кнопки ОК или Отмена. Нажатие кнопки ОК обозначает, что пользователь ввел данные и желает, чтобы функция продолжила работу с этими данными. Нажатие кнопки Отмена обозначает, что пользователь хочет остановить выполнение функции. Наиболее распространенными примерами модальных диалоговых окон являются окна, которые отображаются для открытия, сохранения и печати данных.
Немодальное диалоговое окно, с другой стороны, не запрещает пользователю активацию других окон, когда оно открыто. Например, если пользователь хочет найти вхождения конкретного слова в документе, главное окно часто открывает диалоговое окно с запросом слова для поиска. Так как поиск слова не мешает пользователю редактировать документ, диалоговое окно не обязательно должно быть модальным. Немодальное диалоговое окно обычно содержит как минимум кнопку Закрыть и может предоставлять дополнительные кнопки для выполнения определенных функций, таких как Найти далее, чтобы найти следующее слово, соответствующее критерию поиска.
Windows Presentation Foundation (WPF) позволяет создавать несколько типов диалоговых окон, включая окна сообщений, общие диалоговые окна и пользовательские диалоговые окна. В этом разделе обсуждаются эти виды диалоговых окон, а в разделе примеры диалоговых окон приведены соответствующие примеры.
Окна сообщений
Окно сообщения является диалоговым окном, которое может использоваться для отображения текстовых данных и позволяет пользователям принимать решения с помощью кнопок. На следующем рисунке показано окно сообщения, в котором отображается текстовая информация, задается вопрос и предоставляются три кнопки для ответа на этот вопрос.
Для создания окна сообщения используйте класс MessageBox. MessageBox позволяет настроить текст в окне сообщения, заголовок, значок и кнопки с помощью следующего кода.
// Configure the message box to be displayed
string messageBoxText = "Do you want to save changes?";
string caption = "Word Processor";
MessageBoxButton button = MessageBoxButton.YesNoCancel;
MessageBoxImage icon = MessageBoxImage.Warning;
Чтобы отобразить окно сообщения, вызовите static
метод Show, как показано в следующем коде.
// Display message box
MessageBox.Show(messageBoxText, caption, button, icon);
Когда коду, который показывает окно сообщения, нужно определить и обработать решение пользователя (какая кнопка была нажата), он может проверить результат окна сообщения, как показано в следующем коде.
// Display message box
MessageBoxResult result = MessageBox.Show(messageBoxText, caption, button, icon);
// Process message box results
switch (result)
{
case MessageBoxResult.Yes:
// User pressed Yes button
// ...
break;
case MessageBoxResult.No:
// User pressed No button
// ...
break;
case MessageBoxResult.Cancel:
// User pressed Cancel button
// ...
break;
}
Дополнительные сведения об использовании окон сообщений см. в разделах MessageBox, пример MessageBox и примеры диалоговых окон.
Несмотря на то, что MessageBox предлагает лишь достаточно ограниченный пользовательский интерфейс диалогового окна, преимуществом использования MessageBox является то, что это единственный тип окна, который могут отображать приложения, работающие в песочнице с частичным доверием (см. в разделе безопасности), такие как XAML-приложения браузера (XBAP).
Большинство диалоговых окон отображают и собирают более сложные данные, чем результат окна сообщения, в том числе текст, выбранные варианты (флажки), взаимоисключающий выбор (переключатели) и списки выбора (списки, поля со списком, поля с раскрывающимся списком). Для этого Windows Presentation Foundation (WPF) предоставляет несколько общих диалоговых окон и позволяет создавать пользовательские диалоговые окна, хотя их использование возможно только для приложений, выполняемых с полным доверием.
Общие диалоговые окна
Windows реализует различные часто используемые диалоговые окна, которые являются общими для всех приложений, включая диалоговые окна для открытия и сохранения файлов, а также печати. Поскольку эти диалоговые окна реализованы операционной системой, они могут совместно использоваться всеми приложениями, работающими в операционной системе, что помогает поддерживать единообразие пользовательского интерфейса; если пользователи знакомы с диалоговым окном, предоставляемым операционной системой в одном приложении, им не нужно учиться пользоваться этим диалоговым окном в других приложениях. Поскольку эти диалоговые доступны для всех приложений и обеспечивают согласованный пользовательский интерфейс, они называются общими.
Windows Presentation Foundation (WPF) инкапсулирует диалоговые окна открытия файла, сохранения файла и печати и предоставляет их как управляемые классы для использования в автономных приложениях. В этом разделе приводится краткий обзор каждого типа диалоговых окон.
Диалоговое окно открытия файлов
Диалоговое окно открытия файлов, показанное на следующем рисунке, используется функцией открытия файла для получения имени открываемого файла.
Общее диалоговое окно открытия файла реализуется как класс OpenFileDialog, который расположен в пространстве имен Microsoft.Win32. Следующий код показывает, как создавать, настраивать и отображать такое окно, а также как обрабатывать результат.
// Configure open file dialog box
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".txt"; // Default file extension
dlg.Filter = "Text documents (.txt)|*.txt"; // 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;
}
Дополнительные сведения о диалоговом окне открытия файла см. в разделе Microsoft.Win32.OpenFileDialog.
Note
OpenFileDialog может использоваться для безопасного извлечения имен файлов приложениями, выполняющимися с частичным доверием (см. в разделе безопасность).
Диалоговое окно сохранения файлов
Диалоговое окно сохранения файлов, показанное на следующем рисунке, используется функцией сохранения файла для получения имени сохраняемого файла.
Общее диалоговое окно сохранения файла реализуется как класс SaveFileDialog и находится в пространстве имен Microsoft.Win32. Следующий код показывает, как создавать, настраивать и отображать такое окно, а также как обрабатывать результат.
// Configure save file dialog box
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".txt"; // Default file extension
dlg.Filter = "Text documents (.txt)|*.txt"; // 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;
}
Дополнительные сведения о диалоговом окне сохранения файла см. в разделе Microsoft.Win32.SaveFileDialog.
Диалоговое окно печати
Диалоговое окно печати, показанное на следующем рисунке, используется функциональными возможностями печати для выбора и настройки принтера, на котором пользователь хочет печатать данные.
Общее диалоговое окно печати реализуется как класс PrintDialog и находится в пространстве имен System.Windows.Controls. Следующий код показывает, как создавать, настраивать и отображать такое окно.
// Configure printer dialog box
System.Windows.Controls.PrintDialog dlg = new System.Windows.Controls.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
}
Дополнительные сведения о диалоговом окне печати см. в разделе System.Windows.Controls.PrintDialog. Подробное описание печати в WPF см. в разделе Обзор печати.
Пользовательские диалоговые окна
Хотя общие диалоговые окна полезны и должны использоваться, когда это возможно, они не поддерживают требований диалоговых окон, относящихся к особой предметной области. В этих случаях необходимо создавать собственные диалоговые окна. Как мы увидим, диалоговое окно является окном со специальным поведением. Это поведение реализуется Window и, следовательно, для создания пользовательских модальных и немодальных диалоговых окон используется класс Window.
Создание модального пользовательского диалогового окна
В этом разделе показано, как использовать Window для реализации типичного модального диалогового окнп, используя диалоговое окно Margins
в качестве примера (см. в разделе примеры диалоговых окон). На следующем рисунке показано диалоговое окно Margins
.
Настройка модального диалогового окна
Пользовательский интерфейс для типичного диалогового окна включает следующее.
Различные элементы управления, которые необходимы для сбора нужных данных.
Кнопка ОК, чтобы закрыть диалоговое окно и вернуться в функцию для продолжения обработки.
Кнопка Отмена, чтобы закрыть диалоговое окно и остановить дальнейшую обработку функции.
Кнопка Закрыть в заголовке окна.
Значок.
Кнопки Свернуть, Развернуть и Восстановить.
Системное меню, которое позволяет свернуть, развернуть и закрыть окно.
Диалоговые окна должны быть регулируемого размера, где это возможно, и, чтобы предотвратить появление окна слишком малого размера, необходимо установить минимальные размеры окна и размеры по умолчанию.
Нажатие клавиши ESC следует настроить как сочетание клавиш, которое вызывает нажатие кнопки Отмена. Это достигается путем установки свойства IsCancel кнопки Отмена равным значению
true
.Нажатие клавиши ВВОД (RETURN) следует настроить как сочетание клавиш, которое вызывает нажатие кнопки ОК. Это достигается путем установки свойства IsDefault кнопки ОК равным значению
true
.
Следующий код демонстрирует такую конфигурацию.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.MarginsDialogBox"
xmlns:local="clr-namespace:SDKSample"
Title="Margins"
Height="190"
Width="300"
MinHeight="10"
MinWidth="300"
ResizeMode="CanResizeWithGrip"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
FocusManager.FocusedElement="{Binding ElementName=leftMarginTextBox}">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Margin" Value="0,3,5,5" />
<Setter Property="Padding" Value="0,0,0,5" />
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="0,0,0,5" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Width" Value="70" />
<Setter Property="Height" Value="25" />
<Setter Property="Margin" Value="5,0,0,0" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- 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>
<!-- Top Margin -->
<Label Grid.Column="0" Grid.Row="1">Top Margin:</Label>
<TextBox Name="topMarginTextBox" Grid.Column="1" Grid.Row="1">
<TextBox.Text>
<Binding Path="Top" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Right Margin -->
<Label Grid.Column="0" Grid.Row="2">Right Margin:</Label>
<TextBox Name="rightMarginTextBox" Grid.Column="1" Grid.Row="2">
<TextBox.Text>
<Binding Path="Right" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Bottom Margin -->
<Label Grid.Column="0" Grid.Row="3">Bottom Margin:</Label>
<TextBox Name="bottomMarginTextBox" Grid.Column="1" Grid.Row="3">
<TextBox.Text>
<Binding Path="Bottom" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Accept or Cancel -->
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4">
<Button Name="okButton" Click="okButton_Click" IsDefault="True">OK</Button>
<Button Name="cancelButton" IsCancel="True">Cancel</Button>
</StackPanel>
</Grid >
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SDKSample
{
public partial class MarginsDialogBox : Window
{
public MarginsDialogBox()
{
InitializeComponent();
}
public Thickness DocumentMargin
{
get { return (Thickness)this.DataContext; }
set { this.DataContext = value; }
}
void cancelButton_Click(object sender, RoutedEventArgs e)
{
// Dialog box canceled
this.DialogResult = false;
}
void okButton_Click(object sender, RoutedEventArgs e)
{
// Don't accept the dialog box if there is invalid data
if (!IsValid(this)) return;
// Dialog box accepted
this.DialogResult = true;
}
// 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;
}
}
}
Пользовательский интерфейс для диалогового окна также распространяется на строку меню окна, открывающего диалоговое окно. Если пункт меню вызывает функцию, которая перед продолжением выполнения требует взаимодействия с пользователем посредством диалогового окна, то в названии этого пункта меню должно быть многоточие, как показано ниже.
<Window
x:Class="SDKSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Document Window"
Height="300"
Width="350"
ResizeMode="CanResizeWithGrip"
Closing="mainWindow_Closing"
>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open" Click="fileOpen_Click" />
<MenuItem Header="_Save" Click="fileSave_Click" />
<MenuItem Header="_Print" Click="filePrint_Click" />
<Separator />
<MenuItem Header="_Exit" Click="fileExit_Click" />
</MenuItem>
<MenuItem Header="_Edit">
<!--Main Window-->
<MenuItem Name="editFindMenuItem" Header="_Find" InputGestureText="Ctrl+F" Click="editFindMenuItem_Click" />
</MenuItem>
<MenuItem Header="F_ormat">
<MenuItem Name="formatFontMenuItem" Header="_Font..." Click="formatFontMenuItem_Click" />
<!--Main Window-->
<MenuItem Name="formatMarginsMenuItem" Header="_Margins..." Click="formatMarginsMenuItem_Click" />
</MenuItem>
</Menu>
<TextBox
Name="documentTextBox"
TextChanged="documentTextBox_TextChanged"
ScrollViewer.CanContentScroll="True"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollBarVisibility="Visible"
AcceptsReturn="True"
AcceptsTab="True"
BorderThickness="0" >
The quick brown fox jumps over the lazy old brown dog.
</TextBox>
</DockPanel>
</Window>
Если пункт меню вызывает функцию, которая отображает диалоговое окно, не требующее взаимодействия с пользователем, например диалоговое окно "О программе", многоточие не требуется.
Открытие модального диалогового окна
Диалоговое окно обычно отображается в результате выбора пользователем пункта меню для выполнения функции предметной области, такой как установка полей документа в текстовом редакторе. Отображение диалогового окна похоже на отображение обычного окна, хотя для диалогового окна требуется дополнительная настройка. Весь процесс создания, настройки и открытия диалогового окна показан в следующем коде.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
namespace SDKSample
{
public partial class MainWindow : Window
{
bool needsToBeSaved;
public MainWindow()
{
InitializeComponent();
}
// Closing
void mainWindow_Closing(object sender, CancelEventArgs e)
{
// If the document needs to be saved
if (this.needsToBeSaved)
{
// Configure the message box
string messageBoxText = "This document needs to be saved. Click Yes to save and exit, No to exit without saving, or Cancel to not exit.";
string caption = "Word Processor";
MessageBoxButton button = MessageBoxButton.YesNoCancel;
MessageBoxImage icon = MessageBoxImage.Warning;
// 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;
}
}
}
void fileOpen_Click(object sender, RoutedEventArgs e)
{
OpenDocument();
}
void fileSave_Click(object sender, RoutedEventArgs e)
{
SaveDocument();
}
void filePrint_Click(object sender, RoutedEventArgs e)
{
PrintDocument();
}
void fileExit_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
void editFindMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
FindDialogBox dlg = new FindDialogBox(this.documentTextBox);
// Configure the dialog box
dlg.Owner = this;
dlg.TextFound += new TextFoundEventHandler(dlg_TextFound);
// Open the dialog box modally
dlg.Show();
}
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;
}
}
void formatFontMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
FontDialogBox dlg = new FontDialogBox();
// Configure the dialog box
dlg.Owner = this;
dlg.FontFamily = this.documentTextBox.FontFamily;
dlg.FontSize = this.documentTextBox.FontSize;
dlg.FontWeight = this.documentTextBox.FontWeight;
dlg.FontStyle = this.documentTextBox.FontStyle;
// 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.FontFamily = dlg.FontFamily;
this.documentTextBox.FontSize = dlg.FontSize;
this.documentTextBox.FontWeight = dlg.FontWeight;
this.documentTextBox.FontStyle = dlg.FontStyle;
}
}
// Detect when document has been altered
void documentTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
this.needsToBeSaved = true;
}
void OpenDocument()
{
// Instantiate the dialog box
OpenFileDialog dlg = new OpenFileDialog();
// Configure open file dialog box
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".wpf"; // Default file extension
dlg.Filter = "Word Processor Files (.wpf)|*.wpf"; // Filter files by extension
// Open the dialog box modally
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
// Open document
string filename = dlg.FileName;
}
}
void SaveDocument()
{
// Configure save file dialog
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
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog results
if (result == true)
{
// Save document
string filename = dlg.FileName;
}
}
void PrintDocument()
{
// Configure printer dialog
PrintDialog dlg = new PrintDialog();
dlg.PageRangeSelection = PageRangeSelection.AllPages;
dlg.UserPageRangeEnabled = true;
// Show save file dialog
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog results
if (result == true)
{
// Print document
}
}
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();
}
}
}
Здесь код передает в диалоговое окно сведения по умолчанию (о текущих полях). Он также присваивает свойству Window.Owner ссылку на окно, которое отображает диалоговое окно. Как правило, следует всегда устанавливать владельца для диалогового окна, чтобы задать поведения, связанные с состоянием окна, которые являются общими для всех диалоговых окон (подробнее см. в разделе Общие сведения об окнах WPF).
Note
Необходимо указать владельца для поддержки автоматизации UI диалоговых окон (см. в разделе обзор автоматизации пользовательского интерфейса).
После настройки диалогового окна, его можно отобразить как модальное путем вызова ShowDialog.
Проверка пользовательских данных
Если открывается диалоговое окно и пользователь предоставляет требуемые данные, диалоговое окно отвечает за проверку допустимости предоставленных данных по следующим причинам.
С точки зрения безопасности, следует проверять все входные данные.
С точки зрения конкретной предметной области, следует предотвращать обработку ошибочных данных, которые могут вызывать исключения.
С точки зрения взаимодействия с пользователем, диалоговое окно может помочь пользователям, показывая, какие введенные ими данные являются недопустимыми.
С точки зрения производительности, проверка данных в многоуровневом приложении может уменьшить количество циклов обработки между уровнями клиента и приложения, особенно в том случае, если в приложение входят веб-службы или серверные базы данных.
Чтобы проверить связанный элемент управления в WPF, необходимо определить правило проверки и связать его с привязкой. Правило проверки — пользовательский класс, производный от ValidationRule. В следующем примере показано правило проверки, MarginValidationRule
, которое проверяет, что связанное значение имеет тип Double и находится в пределах указанного диапазона.
В этом коде логика правила проверки реализована путем переопределения Validate, который проверяет данные и возвращает соответствующий ValidationResult.
Чтобы сопоставить это правило проверки со связанным элементом управления, используется следующая разметка.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.MarginsDialogBox"
xmlns:local="clr-namespace:SDKSample"
Title="Margins"
Height="190"
Width="300"
MinHeight="10"
MinWidth="300"
ResizeMode="CanResizeWithGrip"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
FocusManager.FocusedElement="{Binding ElementName=leftMarginTextBox}">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Margin" Value="0,3,5,5" />
<Setter Property="Padding" Value="0,0,0,5" />
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="0,0,0,5" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Width" Value="70" />
<Setter Property="Height" Value="25" />
<Setter Property="Margin" Value="5,0,0,0" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- 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>
<!-- Top Margin -->
<Label Grid.Column="0" Grid.Row="1">Top Margin:</Label>
<TextBox Name="topMarginTextBox" Grid.Column="1" Grid.Row="1">
<TextBox.Text>
<Binding Path="Top" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Right Margin -->
<Label Grid.Column="0" Grid.Row="2">Right Margin:</Label>
<TextBox Name="rightMarginTextBox" Grid.Column="1" Grid.Row="2">
<TextBox.Text>
<Binding Path="Right" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Bottom Margin -->
<Label Grid.Column="0" Grid.Row="3">Bottom Margin:</Label>
<TextBox Name="bottomMarginTextBox" Grid.Column="1" Grid.Row="3">
<TextBox.Text>
<Binding Path="Bottom" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Accept or Cancel -->
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4">
<Button Name="okButton" Click="okButton_Click" IsDefault="True">OK</Button>
<Button Name="cancelButton" IsCancel="True">Cancel</Button>
</StackPanel>
</Grid >
</Window>
После связывания, правила проверки WPF автоматически применяются при вводе данных в связанном элементе управления. Если элемент управления содержит недопустимые данные, WPF отобразит красные границы вокруг недопустимого элемента, как показано на рисунке ниже.
WPF не ограничивает переход пользователя из элемента управления с недопустимым значением, пока не будут введены допустимые данные. Это правильное поведение диалогового окна; пользователь должен иметь возможность свободно перемещаться по элементам управления в диалоговом окне, независимо от того, правильны ли введенные данные. Тем не менее, это означает, что пользователь может ввести недопустимые данные и нажать кнопку ОК. По этой причине код также должен проверить все элементы управления в диалоговом окне при нажатии ОК путем обработки события Click.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SDKSample
{
public partial class MarginsDialogBox : Window
{
public MarginsDialogBox()
{
InitializeComponent();
}
public Thickness DocumentMargin
{
get { return (Thickness)this.DataContext; }
set { this.DataContext = value; }
}
void cancelButton_Click(object sender, RoutedEventArgs e)
{
// Dialog box canceled
this.DialogResult = false;
}
void okButton_Click(object sender, RoutedEventArgs e)
{
// Don't accept the dialog box if there is invalid data
if (!IsValid(this)) return;
// Dialog box accepted
this.DialogResult = true;
}
// 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;
}
}
}
Этот код перечисляет все объекты зависимости в окне, и, если какой-либо имеет недопустимое значение (возвращается GetHasError), этот элемент управления получает фокус, IsValid
возвращает false
, и окно считается недопустимым.
Если диалоговое окно является допустимым, оно может быть безопасно закрыто и выполнен возврат. В рамках процесса возврата необходимо возвращать результат в вызывающую функцию.
Установка результата модального диалогового окна
Открытие диалогового окна при помощи ShowDialog, по сути, аналогично вызову метода: код, открывающий диалоговое окно с помощью ShowDialog ожидает возвращаемое значение ShowDialog. Когда ShowDialog возвращает управление, код, который его вызвал, должен решить, продолжать обработку или прекратить ее, в зависимости от того, была ли нажата клавиша ОК или Отмена. Для облегчения принятия этого решения, диалоговое окно должно возвращать выбор пользователя в виде значения Boolean, возвращаемого из ShowDialog.
При нажатии ОК, ShowDialog должен возвращать true
. Это достигается путем установки DialogResult при нажатии кнопки ОК.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SDKSample
{
public partial class MarginsDialogBox : Window
{
public MarginsDialogBox()
{
InitializeComponent();
}
public Thickness DocumentMargin
{
get { return (Thickness)this.DataContext; }
set { this.DataContext = value; }
}
void cancelButton_Click(object sender, RoutedEventArgs e)
{
// Dialog box canceled
this.DialogResult = false;
}
void okButton_Click(object sender, RoutedEventArgs e)
{
// Don't accept the dialog box if there is invalid data
if (!IsValid(this)) return;
// Dialog box accepted
this.DialogResult = true;
}
// 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;
}
}
}
Обратите внимание, что DialogResult также вызывает автоматическое закрытие окна, что устраняет необходимость явно вызывать Close.
При нажатии Отмена, ShowDialog должен возвращать false
, что также требует установки DialogResult.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SDKSample
{
public partial class MarginsDialogBox : Window
{
public MarginsDialogBox()
{
InitializeComponent();
}
public Thickness DocumentMargin
{
get { return (Thickness)this.DataContext; }
set { this.DataContext = value; }
}
void cancelButton_Click(object sender, RoutedEventArgs e)
{
// Dialog box canceled
this.DialogResult = false;
}
void okButton_Click(object sender, RoutedEventArgs e)
{
// Don't accept the dialog box if there is invalid data
if (!IsValid(this)) return;
// Dialog box accepted
this.DialogResult = true;
}
// 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;
}
}
}
Когда IsCancel равно true
и пользователь нажимает кнопку Отмена или клавишу ESC, DialogResult автоматически присваивается false
. Следующая разметка действует так же, как предыдущий код, без необходимости обрабатывать событие Click.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.MarginsDialogBox"
xmlns:local="clr-namespace:SDKSample"
Title="Margins"
Height="190"
Width="300"
MinHeight="10"
MinWidth="300"
ResizeMode="CanResizeWithGrip"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
FocusManager.FocusedElement="{Binding ElementName=leftMarginTextBox}">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Margin" Value="0,3,5,5" />
<Setter Property="Padding" Value="0,0,0,5" />
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="0,0,0,5" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Width" Value="70" />
<Setter Property="Height" Value="25" />
<Setter Property="Margin" Value="5,0,0,0" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- 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>
<!-- Top Margin -->
<Label Grid.Column="0" Grid.Row="1">Top Margin:</Label>
<TextBox Name="topMarginTextBox" Grid.Column="1" Grid.Row="1">
<TextBox.Text>
<Binding Path="Top" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Right Margin -->
<Label Grid.Column="0" Grid.Row="2">Right Margin:</Label>
<TextBox Name="rightMarginTextBox" Grid.Column="1" Grid.Row="2">
<TextBox.Text>
<Binding Path="Right" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Bottom Margin -->
<Label Grid.Column="0" Grid.Row="3">Bottom Margin:</Label>
<TextBox Name="bottomMarginTextBox" Grid.Column="1" Grid.Row="3">
<TextBox.Text>
<Binding Path="Bottom" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MarginValidationRule MinMargin="0" MaxMargin="10" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Accept or Cancel -->
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4">
<Button Name="okButton" Click="okButton_Click" IsDefault="True">OK</Button>
<Button Name="cancelButton" IsCancel="True">Cancel</Button>
</StackPanel>
</Grid >
</Window>
Диалоговое окно автоматически возвращает false
, когда пользователь нажимает кнопку Закрыть в строке заголовка или выбирает Закрыть в системном меню.
Обработка данных, возвращенных из модального диалогового окна
Когда DialogResult устанавливается диалоговым окном, функция, которая его открыла, может получить результат диалогового окна, проверив значение DialogResult, которое возвращает ShowDialog.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
namespace SDKSample
{
public partial class MainWindow : Window
{
bool needsToBeSaved;
public MainWindow()
{
InitializeComponent();
}
// Closing
void mainWindow_Closing(object sender, CancelEventArgs e)
{
// If the document needs to be saved
if (this.needsToBeSaved)
{
// Configure the message box
string messageBoxText = "This document needs to be saved. Click Yes to save and exit, No to exit without saving, or Cancel to not exit.";
string caption = "Word Processor";
MessageBoxButton button = MessageBoxButton.YesNoCancel;
MessageBoxImage icon = MessageBoxImage.Warning;
// 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;
}
}
}
void fileOpen_Click(object sender, RoutedEventArgs e)
{
OpenDocument();
}
void fileSave_Click(object sender, RoutedEventArgs e)
{
SaveDocument();
}
void filePrint_Click(object sender, RoutedEventArgs e)
{
PrintDocument();
}
void fileExit_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
void editFindMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
FindDialogBox dlg = new FindDialogBox(this.documentTextBox);
// Configure the dialog box
dlg.Owner = this;
dlg.TextFound += new TextFoundEventHandler(dlg_TextFound);
// Open the dialog box modally
dlg.Show();
}
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;
}
}
void formatFontMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
FontDialogBox dlg = new FontDialogBox();
// Configure the dialog box
dlg.Owner = this;
dlg.FontFamily = this.documentTextBox.FontFamily;
dlg.FontSize = this.documentTextBox.FontSize;
dlg.FontWeight = this.documentTextBox.FontWeight;
dlg.FontStyle = this.documentTextBox.FontStyle;
// 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.FontFamily = dlg.FontFamily;
this.documentTextBox.FontSize = dlg.FontSize;
this.documentTextBox.FontWeight = dlg.FontWeight;
this.documentTextBox.FontStyle = dlg.FontStyle;
}
}
// Detect when document has been altered
void documentTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
this.needsToBeSaved = true;
}
void OpenDocument()
{
// Instantiate the dialog box
OpenFileDialog dlg = new OpenFileDialog();
// Configure open file dialog box
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".wpf"; // Default file extension
dlg.Filter = "Word Processor Files (.wpf)|*.wpf"; // Filter files by extension
// Open the dialog box modally
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
// Open document
string filename = dlg.FileName;
}
}
void SaveDocument()
{
// Configure save file dialog
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
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog results
if (result == true)
{
// Save document
string filename = dlg.FileName;
}
}
void PrintDocument()
{
// Configure printer dialog
PrintDialog dlg = new PrintDialog();
dlg.PageRangeSelection = PageRangeSelection.AllPages;
dlg.UserPageRangeEnabled = true;
// Show save file dialog
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog results
if (result == true)
{
// Print document
}
}
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();
}
}
}
Если результатом диалогового окна является true
, функция использует его в качестве указания, что нужно получить и обработать данные, предоставленные пользователем.
Note
После возврата ShowDialog диалоговое окно нельзя открыть повторно. Вместо этого придется создать новый экземпляр.
Если результатом диалогового окна является false
, функция должна соответствующим образом завершить обработку.
Создание немодального пользовательского диалогового окна
Немодальное диалоговое окно, например диалоговое окно поиска, показанное на следующем рисунке, в основном имеет такой же внешний вид, как и модальное диалоговое окно.
Однако поведение несколько отличается, как показано в следующих разделах.
Открытие немодального диалогового окна
Немодальное диалоговое окно открывается путем вызова метода Show.
<Window
x:Class="SDKSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Document Window"
Height="300"
Width="350"
ResizeMode="CanResizeWithGrip"
Closing="mainWindow_Closing"
>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open" Click="fileOpen_Click" />
<MenuItem Header="_Save" Click="fileSave_Click" />
<MenuItem Header="_Print" Click="filePrint_Click" />
<Separator />
<MenuItem Header="_Exit" Click="fileExit_Click" />
</MenuItem>
<MenuItem Header="_Edit">
<!--Main Window-->
<MenuItem Name="editFindMenuItem" Header="_Find" InputGestureText="Ctrl+F" Click="editFindMenuItem_Click" />
</MenuItem>
<MenuItem Header="F_ormat">
<MenuItem Name="formatFontMenuItem" Header="_Font..." Click="formatFontMenuItem_Click" />
<!--Main Window-->
<MenuItem Name="formatMarginsMenuItem" Header="_Margins..." Click="formatMarginsMenuItem_Click" />
</MenuItem>
</Menu>
<TextBox
Name="documentTextBox"
TextChanged="documentTextBox_TextChanged"
ScrollViewer.CanContentScroll="True"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollBarVisibility="Visible"
AcceptsReturn="True"
AcceptsTab="True"
BorderThickness="0" >
The quick brown fox jumps over the lazy old brown dog.
</TextBox>
</DockPanel>
</Window>
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
namespace SDKSample
{
public partial class MainWindow : Window
{
bool needsToBeSaved;
public MainWindow()
{
InitializeComponent();
}
// Closing
void mainWindow_Closing(object sender, CancelEventArgs e)
{
// If the document needs to be saved
if (this.needsToBeSaved)
{
// Configure the message box
string messageBoxText = "This document needs to be saved. Click Yes to save and exit, No to exit without saving, or Cancel to not exit.";
string caption = "Word Processor";
MessageBoxButton button = MessageBoxButton.YesNoCancel;
MessageBoxImage icon = MessageBoxImage.Warning;
// 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;
}
}
}
void fileOpen_Click(object sender, RoutedEventArgs e)
{
OpenDocument();
}
void fileSave_Click(object sender, RoutedEventArgs e)
{
SaveDocument();
}
void filePrint_Click(object sender, RoutedEventArgs e)
{
PrintDocument();
}
void fileExit_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
void editFindMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
FindDialogBox dlg = new FindDialogBox(this.documentTextBox);
// Configure the dialog box
dlg.Owner = this;
dlg.TextFound += new TextFoundEventHandler(dlg_TextFound);
// Open the dialog box modally
dlg.Show();
}
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;
}
}
void formatFontMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
FontDialogBox dlg = new FontDialogBox();
// Configure the dialog box
dlg.Owner = this;
dlg.FontFamily = this.documentTextBox.FontFamily;
dlg.FontSize = this.documentTextBox.FontSize;
dlg.FontWeight = this.documentTextBox.FontWeight;
dlg.FontStyle = this.documentTextBox.FontStyle;
// 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.FontFamily = dlg.FontFamily;
this.documentTextBox.FontSize = dlg.FontSize;
this.documentTextBox.FontWeight = dlg.FontWeight;
this.documentTextBox.FontStyle = dlg.FontStyle;
}
}
// Detect when document has been altered
void documentTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
this.needsToBeSaved = true;
}
void OpenDocument()
{
// Instantiate the dialog box
OpenFileDialog dlg = new OpenFileDialog();
// Configure open file dialog box
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".wpf"; // Default file extension
dlg.Filter = "Word Processor Files (.wpf)|*.wpf"; // Filter files by extension
// Open the dialog box modally
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
// Open document
string filename = dlg.FileName;
}
}
void SaveDocument()
{
// Configure save file dialog
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
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog results
if (result == true)
{
// Save document
string filename = dlg.FileName;
}
}
void PrintDocument()
{
// Configure printer dialog
PrintDialog dlg = new PrintDialog();
dlg.PageRangeSelection = PageRangeSelection.AllPages;
dlg.UserPageRangeEnabled = true;
// Show save file dialog
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog results
if (result == true)
{
// Print document
}
}
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();
}
}
}
В отличие от ShowDialog, Show возвращает немедленно. Следовательно, вызывающее окно не может определить, когда немодальное диалоговое окно закрывается, и поэтому не знает, когда следует проверить результат диалогового окна или получить данные из диалогового окна для дальнейшей обработки. Поэтому диалоговому окну необходимо создать альтернативный способ возврата данных в вызывающее окно для обработки.
Обработка данных, возвращенных из немодального диалогового окна
В этом примере FindDialogBox
может возвращать один или несколько результатов в главное окно в зависимости от искомого текста. Как и в случае с модальным диалоговым окном, немодальное диалоговое окно может возвращать результаты с помощью свойств. Однако окну, которому принадлежит данное диалоговое окно, нужно знать, когда следует проверять эти свойства. Один из способов сделать это — реализовать для диалогового окна событие, которое возникает всякий раз, когда текст найден. FindDialogBox
реализует для этой цели TextFoundEvent
.
using System;
namespace SDKSample
{
public delegate void TextFoundEventHandler(object sender, EventArgs e);
}
С помощью делегата TextFoundEventHandler
, FindDialogBox
реализует TextFoundEvent
.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Text.RegularExpressions;
namespace SDKSample
{
public partial class FindDialogBox : Window
{
public event TextFoundEventHandler TextFound;
protected virtual void OnTextFound()
{
TextFoundEventHandler textFound = this.TextFound;
if (textFound != null) textFound(this, EventArgs.Empty);
}
public FindDialogBox(TextBox textBoxToSearch)
{
InitializeComponent();
this.textBoxToSearch = textBoxToSearch;
// If text box that's being searched is changed, reset search
this.textBoxToSearch.TextChanged += textBoxToSearch_TextChanged;
}
// Text to search
TextBox textBoxToSearch;
// Find results
MatchCollection matches;
int matchIndex = 0;
// Search results
int index = 0;
int length = 0;
public int Index
{
get { return this.index; }
set { this.index = value; }
}
public int Length
{
get { return this.length; }
set { this.length = value; }
}
void findNextButton_Click(object sender, RoutedEventArgs e)
{
// Find matches
if (this.matches == null)
{
string pattern = this.findWhatTextBox.Text;
// Match whole word?
if ((bool)this.matchWholeWordCheckBox.IsChecked) pattern = @"(?<=\W{0,1})" + pattern + @"(?=\W)";
// Case sensitive
if (!(bool)this.caseSensitiveCheckBox.IsChecked) pattern = "(?i)" + pattern;
// Find matches
this.matches = Regex.Matches(this.textBoxToSearch.Text, pattern);
this.matchIndex = 0;
// Word not found?
if (this.matches.Count == 0)
{
MessageBox.Show("'" + this.findWhatTextBox.Text + "' not found.", "Find");
this.matches = null;
return;
}
}
// Start at beginning of matches if the last find selected the last match
if (this.matchIndex == this.matches.Count)
{
MessageBoxResult result = MessageBox.Show("Nmore matches found. Start at beginning?", "Find", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No) return;
// Reset
this.matchIndex = 0;
}
// Return match details to client so it can select the text
Match match = this.matches[this.matchIndex];
if (TextFound != null)
{
// Text found
this.index = match.Index;
this.length = match.Length;
OnTextFound();
}
this.matchIndex++;
}
void textBoxToSearch_TextChanged(object sender, TextChangedEventArgs e)
{
ResetFind();
}
void findWhatTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
ResetFind();
}
void criteria_Click(object sender, RoutedEventArgs e)
{
ResetFind();
}
void ResetFind()
{
this.findNextButton.IsEnabled = true;
this.matches = null;
}
void closeButton_Click(object sender, RoutedEventArgs e)
{
// Close dialog box
this.Close();
}
}
}
Следовательно, Find
может создать событие, когда найден результат поиска.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Text.RegularExpressions;
namespace SDKSample
{
public partial class FindDialogBox : Window
{
public event TextFoundEventHandler TextFound;
protected virtual void OnTextFound()
{
TextFoundEventHandler textFound = this.TextFound;
if (textFound != null) textFound(this, EventArgs.Empty);
}
public FindDialogBox(TextBox textBoxToSearch)
{
InitializeComponent();
this.textBoxToSearch = textBoxToSearch;
// If text box that's being searched is changed, reset search
this.textBoxToSearch.TextChanged += textBoxToSearch_TextChanged;
}
// Text to search
TextBox textBoxToSearch;
// Find results
MatchCollection matches;
int matchIndex = 0;
// Search results
int index = 0;
int length = 0;
public int Index
{
get { return this.index; }
set { this.index = value; }
}
public int Length
{
get { return this.length; }
set { this.length = value; }
}
void findNextButton_Click(object sender, RoutedEventArgs e)
{
// Find matches
if (this.matches == null)
{
string pattern = this.findWhatTextBox.Text;
// Match whole word?
if ((bool)this.matchWholeWordCheckBox.IsChecked) pattern = @"(?<=\W{0,1})" + pattern + @"(?=\W)";
// Case sensitive
if (!(bool)this.caseSensitiveCheckBox.IsChecked) pattern = "(?i)" + pattern;
// Find matches
this.matches = Regex.Matches(this.textBoxToSearch.Text, pattern);
this.matchIndex = 0;
// Word not found?
if (this.matches.Count == 0)
{
MessageBox.Show("'" + this.findWhatTextBox.Text + "' not found.", "Find");
this.matches = null;
return;
}
}
// Start at beginning of matches if the last find selected the last match
if (this.matchIndex == this.matches.Count)
{
MessageBoxResult result = MessageBox.Show("Nmore matches found. Start at beginning?", "Find", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No) return;
// Reset
this.matchIndex = 0;
}
// Return match details to client so it can select the text
Match match = this.matches[this.matchIndex];
if (TextFound != null)
{
// Text found
this.index = match.Index;
this.length = match.Length;
OnTextFound();
}
this.matchIndex++;
}
void textBoxToSearch_TextChanged(object sender, TextChangedEventArgs e)
{
ResetFind();
}
void findWhatTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
ResetFind();
}
void criteria_Click(object sender, RoutedEventArgs e)
{
ResetFind();
}
void ResetFind()
{
this.findNextButton.IsEnabled = true;
this.matches = null;
}
void closeButton_Click(object sender, RoutedEventArgs e)
{
// Close dialog box
this.Close();
}
}
}
Затем окну-владельцу нужно зарегистрировать и обработать это событие.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
namespace SDKSample
{
public partial class MainWindow : Window
{
bool needsToBeSaved;
public MainWindow()
{
InitializeComponent();
}
// Closing
void mainWindow_Closing(object sender, CancelEventArgs e)
{
// If the document needs to be saved
if (this.needsToBeSaved)
{
// Configure the message box
string messageBoxText = "This document needs to be saved. Click Yes to save and exit, No to exit without saving, or Cancel to not exit.";
string caption = "Word Processor";
MessageBoxButton button = MessageBoxButton.YesNoCancel;
MessageBoxImage icon = MessageBoxImage.Warning;
// 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;
}
}
}
void fileOpen_Click(object sender, RoutedEventArgs e)
{
OpenDocument();
}
void fileSave_Click(object sender, RoutedEventArgs e)
{
SaveDocument();
}
void filePrint_Click(object sender, RoutedEventArgs e)
{
PrintDocument();
}
void fileExit_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
void editFindMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
FindDialogBox dlg = new FindDialogBox(this.documentTextBox);
// Configure the dialog box
dlg.Owner = this;
dlg.TextFound += new TextFoundEventHandler(dlg_TextFound);
// Open the dialog box modally
dlg.Show();
}
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;
}
}
void formatFontMenuItem_Click(object sender, RoutedEventArgs e)
{
// Instantiate the dialog box
FontDialogBox dlg = new FontDialogBox();
// Configure the dialog box
dlg.Owner = this;
dlg.FontFamily = this.documentTextBox.FontFamily;
dlg.FontSize = this.documentTextBox.FontSize;
dlg.FontWeight = this.documentTextBox.FontWeight;
dlg.FontStyle = this.documentTextBox.FontStyle;
// 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.FontFamily = dlg.FontFamily;
this.documentTextBox.FontSize = dlg.FontSize;
this.documentTextBox.FontWeight = dlg.FontWeight;
this.documentTextBox.FontStyle = dlg.FontStyle;
}
}
// Detect when document has been altered
void documentTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
this.needsToBeSaved = true;
}
void OpenDocument()
{
// Instantiate the dialog box
OpenFileDialog dlg = new OpenFileDialog();
// Configure open file dialog box
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".wpf"; // Default file extension
dlg.Filter = "Word Processor Files (.wpf)|*.wpf"; // Filter files by extension
// Open the dialog box modally
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
// Open document
string filename = dlg.FileName;
}
}
void SaveDocument()
{
// Configure save file dialog
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
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog results
if (result == true)
{
// Save document
string filename = dlg.FileName;
}
}
void PrintDocument()
{
// Configure printer dialog
PrintDialog dlg = new PrintDialog();
dlg.PageRangeSelection = PageRangeSelection.AllPages;
dlg.UserPageRangeEnabled = true;
// Show save file dialog
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog results
if (result == true)
{
// Print document
}
}
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();
}
}
}
Закрытие немодального диалогового окна
Так как DialogResult устанавливать не требуется, немодальное диалоговое окно можно закрыть с помощью предоставляемых системой механизмов, включая следующие:
Щелкнув кнопку Закрыть в заголовке окна.
Нажатием клавиш ALT+F4.
Выбором Закрыть в системном меню.
Кроме того, код может вызвать Close при нажатии кнопки Закрыть.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Text.RegularExpressions;
namespace SDKSample
{
public partial class FindDialogBox : Window
{
public event TextFoundEventHandler TextFound;
protected virtual void OnTextFound()
{
TextFoundEventHandler textFound = this.TextFound;
if (textFound != null) textFound(this, EventArgs.Empty);
}
public FindDialogBox(TextBox textBoxToSearch)
{
InitializeComponent();
this.textBoxToSearch = textBoxToSearch;
// If text box that's being searched is changed, reset search
this.textBoxToSearch.TextChanged += textBoxToSearch_TextChanged;
}
// Text to search
TextBox textBoxToSearch;
// Find results
MatchCollection matches;
int matchIndex = 0;
// Search results
int index = 0;
int length = 0;
public int Index
{
get { return this.index; }
set { this.index = value; }
}
public int Length
{
get { return this.length; }
set { this.length = value; }
}
void findNextButton_Click(object sender, RoutedEventArgs e)
{
// Find matches
if (this.matches == null)
{
string pattern = this.findWhatTextBox.Text;
// Match whole word?
if ((bool)this.matchWholeWordCheckBox.IsChecked) pattern = @"(?<=\W{0,1})" + pattern + @"(?=\W)";
// Case sensitive
if (!(bool)this.caseSensitiveCheckBox.IsChecked) pattern = "(?i)" + pattern;
// Find matches
this.matches = Regex.Matches(this.textBoxToSearch.Text, pattern);
this.matchIndex = 0;
// Word not found?
if (this.matches.Count == 0)
{
MessageBox.Show("'" + this.findWhatTextBox.Text + "' not found.", "Find");
this.matches = null;
return;
}
}
// Start at beginning of matches if the last find selected the last match
if (this.matchIndex == this.matches.Count)
{
MessageBoxResult result = MessageBox.Show("Nmore matches found. Start at beginning?", "Find", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No) return;
// Reset
this.matchIndex = 0;
}
// Return match details to client so it can select the text
Match match = this.matches[this.matchIndex];
if (TextFound != null)
{
// Text found
this.index = match.Index;
this.length = match.Length;
OnTextFound();
}
this.matchIndex++;
}
void textBoxToSearch_TextChanged(object sender, TextChangedEventArgs e)
{
ResetFind();
}
void findWhatTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
ResetFind();
}
void criteria_Click(object sender, RoutedEventArgs e)
{
ResetFind();
}
void ResetFind()
{
this.findNextButton.IsEnabled = true;
this.matches = null;
}
void closeButton_Click(object sender, RoutedEventArgs e)
{
// Close dialog box
this.Close();
}
}
}