Разработка пользовательского интерфейса на основе технологии Windows Presentation Foundation
Шрифт:
Недочет 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); туннелируемые (tunneling events), которые возникают в компоненте верхнего уровня и «спускаются» по цепочке его дочерних компонентов к компоненту, в котором фактически произошло
На всем пути прохождения события оно может приводить к запуску связанных с ним обработчиков. Замечательной чертой механизма маршрутизируемых событий в 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 (внешняя панель с вертикальной ориентацией содержит две горизонтально ориентированные панели, причем для второй горизонтальной панели дополнительно устанавливается выравнивание по правой границе).
Недочет. При нажатии на кнопку «=» между полями ввода выводится знак равенства, что не имеет смысла. Этот недочет будет исправлен в следующем пункте.
3.2. Организация вычислений
Определите обработчик события Click для кнопки button5:
Результат. При нажатии кнопки «=» указанное выражение вычисляется и отображается на экране (в метке label2). В качестве операнда при любой операции можно указывать число 0; при делении на 0 результатом является «–бесконечность» или «бесконечность» (в зависимости от знака первого операнда) или «NaN» («не число»), если первый операнд также равен 0. В случае если поля ввода содержат текст, который нельзя преобразовать в вещественное число, то выводится результат «ERROR».
Комментарии
1. При выполнении операций над числами типа double ошибок времени выполнения не возникает, однако результатом может быть одно из «особых» значений: double.NegativeInfinity (–), double.PositiveInfinity (+) и double.NaN («не число»).
2. Для преобразования строки в число можно использовать методы Parse и TryParse соответствующего числового типа. Метод TryParse следует применять, если возможна ситуация, когда требуемое преобразование окончится неудачей (при использовании метода Parse такая ситуация приведет к возбуждению исключения, для обработки которого потребуется писать дополнительный код; кроме того, обработка исключения требует существенно больше времени, чем обычная проверка с помощью условного оператора).
3. В методе button5_Click демонстрируются важные особенности, связанные с типом string. Во-первых, тип string можно использовать в качестве переключателя в операторе switch, во-вторых, для типа string определена операция +, в которой в качестве другого операнда (причем не обязательно второго) можно указывать выражение любого типа; при этом данное выражение автоматически преобразуется к типу string с помощью метода ToString, определенного для любого типа платформы .NET.