Страница 11 из 15
2.7. Запрос на подтверждение закрытия окна
В классе Window1 измените обработчик Window_Closing:
Результат. Перед закрытием подчиненного окна win1 отображается стандартное диалоговое окно «Подтверждение» с запросом на подтверждение закрытия (рис. 10). При выборе варианта «Нет» (который предлагается по умолчанию) закрытие подчиненного окна отменяется.
Рис. 10. Стандартное диалоговое окно
Комментарий
В обработчике использован один из наиболее полных вариантов метода Show класса MessageBox, позволяющий указать (1) текст запроса, (2) заголовок окна запроса, (3) набор кнопок для данного окна, (4) иконку в окне и (5) кнопку, предлагаемую по умолчанию. Любой параметр, кроме первого, может отсутствовать; при этом должны отсутствовать и все следующие за ним параметры. Если отсутствует второй параметр, то заголовок окна является пустым, если третий, то в окне отображается единственная кнопка «ОК», если четвертый – иконка в окне не отображается, если пятый – кнопкой по умолчанию является первая кнопка.
Недочет 1. При выборе в диалоговом окне варианта «Да» подчиненное окно закрывается, но главное окно не становится активным.
Данный недочет объясняется тем обстоятельством, что «владельцем» диалогового окна MessageBox является то окно, которое было активным в момент отображения на экране окна MessageBox (в нашем случае это подчиненное окно win1), и именно это окно должно активизироваться при закрытии окна MessageBox. Однако при выборе варианта «Да» окно win1 закрывается, и поэтому его активизация оказывается невозможной. В подобной ситуации ни одно окно на экране не будет активным, а главное окно нашей программы, скорее всего, будет скрыто окном среды Visual Studio. Одним из вариантов исправления подобного недочета является явное указание владельца окна MessageBox в дополнительном параметре, который должен располагаться первым в списке параметров. Например, в качестве этого параметра можно указать Owner. В этом случае при выборе варианта «Да» будет успешно активизировано главное окно. Однако это же окно будет активизироваться и при выборе варианта «Нет» (когда подчиненное окно останется на экране), что является неестественным.
Исправление. Замените оператор Hide() в методе Window_Closing класса Window1 на следующий составной оператор:
Недочет 2. Если в программе ни разу не отображалось подчиненное окно, то при закрытии главного окна выводится запрос на подтверждение закрытия подчиненного окна, хотя это окно на экране отсутствует.
Исправление. Добавьте в начало метода Window_Closing класса Window1 следующий фрагмент:
3. Совместное использование обработчиков событий и работа с клавиатурой: CALC
Рис. 11. Окно приложения CALC
3.1. Настройка коллективного обработчика событий
Рис. 12. Макет окна MainWindow
Для кнопки button1 создайте обработчик события Click (напомним, что для этого достаточно ввести в xaml-файле текст Click= и в появившемся выпадающем списке выбрать вариант «New Event Handler»:
Дополните созданный в cs-файле обработчик следующим образом:
После этого переместите текст Click="button1_Click" в открывающий тег родителя кнопки button1 (т. е. ближайшего к ней компонента StackPanel), дополнив имя Click префиксом Button:
Результат. Нажатие на любую кнопку приводит к отображению текста, указанного на этой кнопке, в метке label1 между полями ввода textBox1 и textBox2.
Комментарии
1. Поскольку при нажатии на любую из кнопок с обозначением арифметической операции следует выполнять однотипные действия, создавать для каждой кнопки особый обработчик события Click нецелесообразно. В приложениях Windows Forms в подобной ситуации создается один обработчик, который затем связывается с соответствующими событиями всех требуемых компонентов. Такой подход возможен и в WPF-приложениях. В нашем случае его можно реализовать, определив обработчик button1_Click для кнопки button1 и указав в xaml-файле атрибут Click="button1_Click" для всех четырех кнопок button1–button4. При этом оператор в обработчике button1_Click можно изменить, указав вместо e.Source параметр sender (оба варианта будут работать одинаково):
Однако в случае WPF-приложений можно использовать другой подход, который позволяет избежать явного связывания обработчика с событиями для нескольких компонентов. Подход основан на механизме маршрутизируемых событий (routed events), благодаря которому информация о возникших событиях может передаваться по цепочке компонентов. В WPF почти все стандартные события являются маршрутизируемыми. При этом все маршрутизируемые события делятся на три категории: прямые (direct events), которые ведут себя как обычные события .NET и не передаются по цепочке наследования (примером такого события является MouseEnter); туннелируемые (tu
На всем пути прохождения события оно может приводить к запуску связанных с ним обработчиков. Замечательной чертой механизма маршрутизируемых событий в WPF является то, что обработчик для маршрутизируемого события можно связать даже с тем родителем, для которого соответствующее событие не определено! Именно такая ситуации имеет место в нашем случае, поскольку для компонента StackPanel не предусмотрено событие Click. Тем не менее мы смогли связать с ним обработчик для события Click, которое может возникать в его дочерних компонентах (для этого нам потребовалось уточнить имя Click именем того компонента, для которого событие Click определено: Button.Click). Подобное поведение похоже на поведение присоединенных свойств (подробно рассмотренных в проекте EVENTS), поэтому в данной ситуации говорят о присоединенных событиях (attached events).
Теперь при возникновении события Click у любой из кнопок панели StackPanel оно «поднимется» к родителю-панели и приведет к вызову связанного с ним обработчика button1_Click. При этом в параметре sender обработчика будет указан компонент, в котором был вызван обработчик (в нашем случае панель StackPanel), а в свойстве Source второго параметра e будет указан компонент, в котором фактически произошло событие (в нашем случае одна из кнопок).
Описанный механизм имеет одну особенность, которую необходимо учитывать: при связывании события в родительском компоненте с некоторым обработчиком мы не можем указать ту часть набора дочерних компонентов, для которой надо использовать обработчик. Обработчик будет вызываться для всех дочерних компонентов, для которых предусмотрено соответствующее событие (а также и для самого родительского компонента, если для него тоже предусмотрено это событие). Не следует думать, что указание префикса Button при определении обработчика в компоненте StackPanel ограничит действие обработчика только компонентами Button. Обработчик будет вызван для дочернего компонента любого типа, если в нем произойдет событие Click.
В нашем случае указанная особенность приведет к недочету в программе (он описывается в конце данного пункта).
2. В макете нашего приложения нельзя естественным образом распределить компоненты по столбцам. В такой ситуации использование группирующего компонента Grid нецелесообразно; вместо него мы используем вложенный набор панелей StackPanel (внешняя панель с вертикальной ориентацией содержит две горизонтально ориентированные панели, причем для второй горизонтальной панели дополнительно устанавливается выравнивание по правой границе).