Диалоговое окно в Windows: модальные и немодальные окна, стандартные диалоги и пример на C#

Диалоговое окно в Windows: модальные и немодальные окна, стандартные диалоги и пример на C# Полезное

Диалоговое окно (dialog box) — это вспомогательное окно программы, через которое она сообщает что-то пользователю или запрашивает у него решение: подтвердить удаление, выбрать файл, ввести имя. Главное свойство диалога — модальность: блокирует ли он работу с основным окном, пока его не закроют.

Ниже — словарь терминов, виды диалогов с таблицей, стандартные окна Windows, смысл кнопок ОК, Отмена и Применить и рабочий пример на C# WinForms: окно сообщения, собственный модальный диалог и немодальное окно.

Окно, диалоговое окно и окно сообщения: в чем разница

Эти слова часто используют как синонимы, хотя это разные уровни:

  • Окно — любая прямоугольная область программы с рамкой: главное окно Проводника, окно браузера, окно редактора.
  • Диалоговое окно — вспомогательное окно для короткого обмена «вопрос — ответ» с пользователем. Обычно у него фиксированный размер, нет кнопок свернуть и развернуть, есть кнопки подтверждения.
  • Окно сообщения (message box) — самый простой диалог: текст, значок и одна-три кнопки. В Windows его показывает системная функция MessageBox, в .NET — класс MessageBox.
  • Окно-владелец (owner) — окно, к которому привязан диалог. Диалог держится поверх владельца и при модальном показе блокирует именно его.

Модальные и немодальные диалоговые окна

Модальное окно не дает работать с окном-владельцем, пока его не закрыть: нажать ОК, Отмена или крестик. Программа ждет ответа и только потом продолжает действие. Немодальное окно открыто параллельно с основным: можно переключаться между ними, как между окном «Найти и заменить» и текстом документа.

Вид Что блокирует Где встречается
Модальный к окну-владельцу Окно-владелец (в WinForms при ShowDialog — и остальные окна этого приложения) Подтверждение удаления, «Сохранить как», настройки
Модальный к документу (sheet) Только одно окно документа, остальные окна программы доступны Типично для macOS: диалог «выезжает» из заголовка окна
Системно-модальный Поверх всех окон; в современных Windows другие программы при этом не блокируются Редкие критические предупреждения
Немодальный Ничего «Найти и заменить», палитры и панели инструментов

Граница упрощения: точная область блокировки зависит от платформы и от того, как программа назначила владельца. Например, окно сообщения без владельца может не заблокировать главное окно, и пользователь продолжит работу с ним, пока вопрос висит на экране.

Стандартные диалоги Windows

Для типовых задач Windows дает готовые окна (common dialogs). Программе не нужно рисовать свой выбор файла: она вызывает системный диалог, и пользователь видит привычный интерфейс.

Задача Системный диалог Класс в WinForms
Открыть файл «Открытие» OpenFileDialog
Сохранить файл «Сохранить как» SaveFileDialog
Выбрать папку «Выбор папки» FolderBrowserDialog
Выбрать цвет / шрифт «Цвет» / «Шрифт» ColorDialog / FontDialog
Печать «Печать» PrintDialog
Сообщение или вопрос Окно сообщения MessageBox
Вопрос с пояснением и вариантами Диалог задачи (task dialog) TaskDialog

Все они по умолчанию модальные: код, открывший диалог, продолжится только после его закрытия.

Кнопки ОК, Отмена и Применить

Кнопка Что делает Закрывает окно
ОК Применяет изменения Да
Отмена Отбрасывает изменения, сделанные в окне Да
Применить Применяет изменения, чтобы сразу увидеть результат Нет
Закрыть Закрывает окно, где изменения уже применены сразу Да

Частая путаница: «Применить» — не аналог ОК. После «Применить» окно остается открытым, а «Отмена» уже не вернет примененное. По соглашению Windows клавиша Enter нажимает кнопку по умолчанию, а Esc — кнопку отмены.

Пример: окно сообщения на C# WinForms

Пример работает только в Windows: WinForms — библиотека Windows. Нужен .NET SDK 10. Создайте проект командой dotnet new winforms -n DialogsDemo, удалите файлы Form1.cs и Form1.Designer.cs, замените Program.cs кодом ниже и запустите dotnet run в папке проекта.

namespace DialogsDemo;

static class Program
{
    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
        Application.Run(new MainForm());
    }
}

class MainForm : Form
{
    public MainForm()
    {
        Text = "Демо диалогов";
        var deleteButton = new Button { Text = "Удалить файл", AutoSize = true };
        deleteButton.Click += OnDeleteClick;
        Controls.Add(deleteButton);
    }

    private void OnDeleteClick(object? sender, EventArgs e)
    {
        DialogResult answer = MessageBox.Show(
            this,                                  // владелец: окно, которое блокируется
            "Удалить файл report.txt?",            // текст
            "Подтверждение",                       // заголовок
            MessageBoxButtons.YesNo,               // кнопки
            MessageBoxIcon.Warning,                // значок
            MessageBoxDefaultButton.Button2);      // по Enter сработает «Нет»

        if (answer == DialogResult.Yes)
        {
            MessageBox.Show(this, "Файл удален (в демо ничего не удаляется).", "Готово",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }
}

Что произойдет: откроется окно «Демо диалогов» с кнопкой «Удалить файл». По нажатию появится окно «Подтверждение» с желтым значком предупреждения и кнопками «Да» и «Нет». Пока оно открыто, главное окно не реагирует на клики. На «Да» появится второе окно «Готово», на «Нет» или Esc окно просто закроется.

Разбор по строкам:

  • MessageBox.Show(...) показывает модальное окно и возвращает, какую кнопку нажал пользователь, в виде значения DialogResult.
  • Первый аргумент this — окно-владелец. Без него диалог может уйти под другое окно или не заблокировать нужное.
  • MessageBoxDefaultButton.Button2 делает кнопкой по умолчанию вторую («Нет»). Для опасных действий это защищает от случайного Enter.

Типичная ошибка: сравнение ответа со строкой

Неверно — сравнивать результат с текстом кнопки:

if (MessageBox.Show("Удалить файл?", "Подтверждение", MessageBoxButtons.YesNo) == "Yes")
{
    MessageBox.Show("Удалено");
}

Результат — проект не собирается:

Program.cs(8,13): error CS0019: Operator '==' cannot be applied to operands of type 'DialogResult' and 'string'

Исправление: сравнивать с перечислением — == DialogResult.Yes. Так код не зависит от языка интерфейса: в русской Windows кнопка подписана «Да», а значение остается тем же.

Свое модальное окно: ShowDialog

Когда окна сообщения мало (нужно поле ввода), делают свою форму и показывают ее методом ShowDialog. Добавьте в проект файл RenameDialog.cs:

namespace DialogsDemo;

// Собственное модальное окно: поле ввода + кнопки ОК и Отмена
class RenameDialog : Form
{
    private readonly TextBox nameBox = new() { Width = 220 };

    public string NewName => nameBox.Text.Trim();

    public RenameDialog(string currentName)
    {
        Text = "Переименовать";
        FormBorderStyle = FormBorderStyle.FixedDialog; // рамка диалога, без растягивания
        MaximizeBox = false;
        MinimizeBox = false;
        ShowInTaskbar = false;
        StartPosition = FormStartPosition.CenterParent;
        AutoSize = true;
        AutoSizeMode = AutoSizeMode.GrowAndShrink;

        nameBox.Text = currentName;
        var ok = new Button { Text = "ОК", DialogResult = DialogResult.OK };
        var cancel = new Button { Text = "Отмена", DialogResult = DialogResult.Cancel };

        AcceptButton = ok;      // Enter
        CancelButton = cancel;  // Esc

        var layout = new FlowLayoutPanel { AutoSize = true, Padding = new Padding(10) };
        layout.Controls.AddRange([nameBox, ok, cancel]);
        Controls.Add(layout);
    }
}

Вызов из главного окна:

using var dialog = new RenameDialog("report.txt");
if (dialog.ShowDialog(this) == DialogResult.OK && dialog.NewName.Length > 0)
{
    Text = "Новое имя: " + dialog.NewName;
}
// сюда выполнение доходит только после закрытия окна

Ключевые детали. Свойство DialogResult у кнопки закрывает модальную форму и передает значение в результат ShowDialog, отдельный обработчик клика не нужен. AcceptButton и CancelButton привязывают Enter и Esc. using нужен потому, что форма после ShowDialog не уничтожается автоматически, в отличие от окна, открытого через Show.

Немодальное окно: Show

private Form? findWindow;

private void ShowFind()
{
    if (findWindow is { IsDisposed: false })
    {
        findWindow.Activate();      // уже открыто: просто выводим вперед
        return;
    }
    findWindow = new Form { Text = "Найти", Width = 300, Height = 120 };
    findWindow.Show(this);          // немодально: главное окно остается доступным
    // эта строка выполнится сразу, не дожидаясь закрытия окна «Найти»
}

Окно «Найти» держится поверх главного и сворачивается вместе с ним, потому что передан владелец this. Проверка IsDisposed не дает открыть десять копий одного окна.

ShowDialog Show
Режим Модальный Немодальный
Ждет закрытия Да, возвращает DialogResult Нет, код идет дальше
Работа с главным окном Заблокирована Доступна
После закрытия Форму нужно освободить (using или Dispose) Форма освобождается сама
Типичное применение Вопрос, ввод, настройки с ОК/Отмена Поиск, палитры, окна свойств

Когда какое окно выбирать

Модальный диалог оправдан, если без ответа нельзя продолжить: подтвердить необратимое действие, ввести имя для сохранения, выбрать файл. Изменения в таком окне обычно применяются по ОК (отложенная фиксация).

Немодальное окно подходит для частых повторяющихся задач, когда пользователь работает с документом и инструментом одновременно. Изменения в нем обычно применяются сразу, а вместо ОК и Отмена ставят «Закрыть».

Это ориентиры из руководств по интерфейсу, а не жесткое правило. Слишком частые подтверждения пользователи начинают закрывать не читая, поэтому для опасных операций надежнее возможность отмены (корзина, Ctrl+Z), чем еще одно окно «Вы уверены?».

Долгую операцию показывают окном хода выполнения с кнопкой «Отмена». Если программа в фоне и ей нужно внимание, не выводите окно поверх чужой работы: достаточно подсветить кнопку на панели задач.

Если не получилось

Симптом Причина Что сделать
Ошибка сборки NETSDK1100 на Linux или macOS Проект под net10.0-windows Собирать в Windows или добавить <EnableWindowsTargeting>true</EnableWindowsTargeting> (только сборка, запуск все равно в Windows)
Диалог спрятался за главным окном Не передан владелец Передать this в MessageBox.Show и ShowDialog
Код после показа окна выполнился сразу Вызван Show вместо ShowDialog Для ответа пользователя — ShowDialog
Enter или Esc не срабатывают Не заданы AcceptButton и CancelButton Назначить обе кнопки формы
Открывается несколько копий окна Новая форма на каждый клик Хранить ссылку и проверять IsDisposed

Выводы

  • Диалоговое окно — вспомогательное окно для обмена «вопрос — ответ»; окно сообщения — его простейший вид.
  • Модальное окно блокирует владельца до закрытия и возвращает ответ; немодальное работает параллельно с основным окном.
  • «Применить» применяет изменения без закрытия окна, ОК — с закрытием, Отмена — закрывает без изменений.
  • В C# WinForms модальный показ — MessageBox.Show и ShowDialog, немодальный — Show(owner); ответ сравнивают с DialogResult, а не со строкой.
  • Для опасных действий возможность отмены надежнее, чем лишнее окно подтверждения.

Где применяется / связь с практикой

Диалоги встречаются в любом настольном приложении: редакторы, утилиты администрирования, внутренние корпоративные программы. Разработчик на C# собирает их в WinForms или WPF, и от того, модальный это диалог или нет, зависит логика кода: ждать ответа или продолжать работу.

Освойте тему на практике

Если хотите системно освоить C# — от синтаксиса и ООП до событий и работы с формами, — посмотрите курс «C# Developer. Basic». Разобрать отдельные темы с преподавателями можно на бесплатных открытых уроках Otus.

FAQ

Можно ли закрыть модальное окно крестиком?
Да. В WinForms это дает результат DialogResult.Cancel, поэтому крестик обрабатывают как отмену, а не как согласие.

Чем диалог задачи (TaskDialog) лучше MessageBox?
В нем есть заголовок-инструкция, пояснение, собственные подписи кнопок, флажок «Больше не спрашивать» и индикатор выполнения. MessageBox проще и подходит для короткого сообщения.

Есть ли диалоговые окна в WPF и на веб-страницах?
Да. В WPF окно показывают модально через ShowDialog и немодально через Show, в HTML есть элемент dialog с методами showModal() и show(). Принцип модальности тот же.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)