Разработка пользовательского интерфейса на основе технологии Windows Presentation Foundation
Шрифт:
Результат. Текст подчиненного окна win1 содержит информацию о том, сколько раз оно было открыто. При изменении размеров подчиненного окна положение находящегося на нем текста изменяется так, чтобы он всегда оставался отцентрированным как по горизонтали, так и по вертикали относительно границ окна.
Комментарии
1. Добавленное в описание класса Window1 поле count при создании окна автоматически инициализируется нулем; в дальнейшем это поле можно вызывать из любого метода класса. Новые поля позволяют хранить дополнительную информацию о состоянии окна.
2. Напомним, что при использовании операции инкремента вида ++i вначале происходит увеличение значения переменной i на 1, а затем данная переменная используется в выражении. Для операции i++ действия выполняются в обратном порядке: вначале прежнее значение i используется в выражении, а затем это значение увеличивается на 1.
3. В данном случае содержимым окна является не группирующий, а «обычный» компонент. Особенностью использованного компонента TextBlock является то, что его содержимым может быть только строка (этот компонент не имеет свойства Content, зато имеет свойство Text типа string). Для обеспечения центрирования текста по обоим измерениям достаточно установить соответствующие значения свойств HorizontalAlignment и VerticalAlignment компонента TextBlock.
2.5. Модальные и обычные кнопки диалогового окна
Рис. 9. Макет
В описание класса Window2 добавьте новое свойство, доступное только для чтения, и связанное с ним поле:
Определите три обработчика, которые уже указаны в xaml-файле:
Обратите внимание на то, что обработчик button2_Click должен иметь модификатор public (он выделен в тексте полужирным шрифтом).
В классе MainWindow дополните обработчик button2_Click:
Результат. Диалоговое окно win2 позволяет изменить заголовки главного и подчиненного окна. Заголовки окон изменяются либо при нажатии обычной кнопки «Применить», либо при нажатии модальной кнопки «OK» (в последнем случае диалоговое окно закрывается). Окно также закрывается при нажатии модальной кнопки «Отмена»; в этом случае заголовки окон не изменяются. Вместо кнопки «OK» можно нажать клавишу Enter, вместо кнопки «Отмена» – клавишу Esc.
Комментарии
1. Для того чтобы нажатие на кнопку «Отмена» приводило к закрытию диалогового окна (а также чтобы нажатие клавиши Esc интерпретировалось как нажатие на эту кнопку), для данной кнопки надо установить равным true свойство IsCancel. Для того чтобы кнопка «ОК» считалась кнопкой по умолчанию (и нажатие клавиши Enter интерпретировалось как нажатие на эту кнопку), для данной кнопки надо установить равным true свойство IsDefault. Заметим, что хотя кнопка «ОК» сделана кнопкой по умолчанию, для нее все равно необходимо определить обработчик события Click (для кнопки «Отмена» обработчик определять не требуется).
2. Доступ из окна win2 к свойству Title окна win1 возможен благодаря тому, что эти окна имеют общего владельца (Owner), который хранит список своих подчиненных окон (в порядке их добавления) в свойстве-коллекции OwnedWindows.
3. Включенное в класс Window2 свойство DialogRes позволяет определить способ закрытия диалогового окна: если окно было закрыто по нажатию кнопки «ОК», то свойство равно true, если окно было закрыто по нажатию кнопки «Отмена» (или каким-либо другим способом, например, по нажатию кнопки закрытия на заголовке окна), то свойство равно false. Это свойство проверяется в главном окне после возврата из метода ShowDialog.
Следует сказать, что метод ShowDialog из библиотеки WPF тоже возвращает значение, позволяющее определить, каким образом было закрыто диалоговое окно (это значение имеет тип bool?, т. е. может быть равно true, false и null, и определяется по значению стандартного свойства окна DialogResult того же типа), однако данный механизм корректно работает только в ситуации, когда диалоговое окно действительно закрывается, а не просто удаляется с экрана, как в нашем случае.
4. Явный вызов метода button2_Click класса Window2 в обработчике button2_Click класса MainWindow обеспечивает выполнение действий, связанных с нажатием на кнопку «Применить» (таким образом, данный вызов имитирует нажатие на кнопку). При вызове этого метода в качестве параметров указаны константы null, так как значения параметров в методе button2_Click класса Window2 не используются.
Для возможности вызова метода button2_Click класса Window2 из класса MainWindow модификатор доступа для данного метода необходимо изменить с private на public или internal (модификатор internal обеспечивает доступ к данному методу в пределах создаваемого проекта).
Заметим, что в данном случае можно было бы обойтись без модификации метода button2_Click класса MainWindow: достаточно просто вызывать метод button2_Click класса Window2 в уже имеющемся обработчике button1_Click этого же класса Window2:
При этом отпадает необходимость в изменении модификатора метода button2_Click с private на public, и, кроме того, можно вообще обойтись без свойства DialogRes.
5. Макет окна Window2 демонстрирует те же особенности компоновки, что и ранее обсуждавшийся макет главного окна, только в более сложном варианте. В нем, как и в главном окне, все компоненты размещаются с учетом их «истинных» размеров, причем размер окна подстраивается под размер компонентов. В данном случае вместо панели StackPanel используется более сложный группирующий компонент Grid, позволяющий размещать данные по строкам и столбцам. Следует обратить внимание на способ задания количества строк и столбцов (мы использовали простейший способ; в более сложных ситуациях можно явно указывать размеры некоторых строк и столбцов или настраивать их размеры с соблюдением требуемых пропорций – см. далее проект IMGVIEW).
Номер ячейки, которую должен занимать компонент, определяется присоединенными свойствами Grid.Row и Grid.Column (которые «делегируются» дочерним компонентам таким же образом, как и рассмотренные в проекте EVENTS свойства Left и Top компонента Canvas). Если для компонента не указывать свойства Grid.Row или Grid.Column, то их значение считается равным 0, т. е. соответствует первой строке или первому столбцу компонента Grid. Настраивая присоединенное свойство Grid.ColumnSpan, можно обеспечить «захват» компонентом нескольких столбцов (имеется также парное свойство Grid.RowSpan). Мы использовали свойство ColumnSpan для размещения набора кнопок по всей ширине нижней строки компонента Grid, сгруппировав их с помощью вспомогательной горизонтальной панели StackPanel и выровняв эту панель по правой границе родительского компонента Grid.
Обратить внимание на интересную особенность полученного макета. Поскольку для одного из полей ввода мы задали свойство MinWidth, ширина полей ввода не может стать меньше значения этого свойства, но может увеличиваться, если ее минимального размера недостаточно для отображения введенного текста. При этом будет пропорционально увеличиваться и ширина компонента Grid, и ширина всего окна, причем кнопки будут по-прежнему выровнены по правой границе.
Недочет. При первом отображении диалогового
2.6. Установка активного компонента окна. Особенности работы с фокусом в библиотеке WPF
В классе Window2 добавьте в метод Window_IsVisibleChanged следующий оператор:
Результат. При первом открытии диалогового окна фокус ввода принимает компонент textBox1. Этот же компонент оказывается активным и при последующих открытиях диалогового окна, независимо от того, какой компонент окна был активным в момент его закрытия. Таким образом, диалоговое окно всегда отображается в одном и том же начальном состоянии. Подобное поведение желательно обеспечивать для любых диалоговых окон.
Комментарии
1. Отметим, что указанное действие по установке фокуса происходит при скрытии окна. В этом можно убедиться, если добавить перед оператором установки фокуса условие:
В то же время, если использовать вариант
то фокус на первом поле ввода при последующих открытиях окна устанавливаться не будет.
Подобное странное поведение объясняется двумя такими же странными особенностями библиотеки WPF. Во-первых, метод Focus обеспечивает установку фокуса для указанного компонента только в случае, если в момент вызова метода окно отображается на экране (хотя более естественным было бы реализовать метод таким образом, чтобы он в любом случае сохранял информацию об установке фокуса и учитывал ее при отображении окна). Заметим, что метод Focus возвращает логическое значение, которое равно true, если вызов метода действительно обеспечил успешную установку фокуса на данном элементе.
Во-вторых, при установке свойства IsVisible равным true событие IsVisibleChanged наступает до того момента, как окно появится на экране, и наоборот, при установке свойства IsVisible равным false событие IsVisibleChanged выполняется, когда окно еще отображается на экране. Подобное поведение тоже представляется нелогичным, поскольку не позволяет связать некоторые действия (например, установку фокуса) с тем моментом, когда окно в очередной раз отображается на экране.
Тем не менее при первом отображении диалогового окна поле ввода textBox1 все же получает фокус, хотя это, казалось бы, противоречит сказанному выше. Это связано с особенностями реализации механизма настройки фокуса в WPF: если в окне еще не установлен активный компонент, то первый вызов метода Focus обеспечит установку фокуса на требуемый компонент даже при невидимом окне, несмотря на то, что этот вызов вернет значение false. Если же активный компонент уже был установлен ранее, когда окно еще отображалось на экране, то последующие вызовы метода Focus при скрытом окне не позволят изменить фокус.
Описанные особенности демонстрируют сложность и некоторую непоследовательность реализации механизма работы с фокусом в WPF. Отметим, что в WPF имеются два вида фокуса: клавиатурный и логический. Для обработки клавиатурного фокуса можно использовать методы класса Keyboard. В частности, свойство Keyboard.FocusedElement, доступное только для чтения, позволяет определить элемент приложения, имеющий в данный момент фокус, а метод Keyboard.Focus(comp) позволяет установить фокус на компонент comp, но только в случае, если этот компонент отображается на экране и находится в активном в данный момент окне. Для работы с логическим фокусом предназначен класс FocusManager. В частности, он позволяет устанавливать различные области фокусировки, в каждой из которых может быть определен свой логический фокус, а также получать и изменять логический фокус для каждой области фокусировки fscope методами GetFocusedElement(fscope) и SetFocusedElement(fscope, comp). Если какая-либо область фокусировки теряет клавиатурный фокус, то, тем не менее, в ней сохраняется информация о том ее компоненте, который имеет логический фокус. Поэтому в дальнейшем данный компонент автоматически получит клавиатурный фокус, если фокус примет сама область. К сожалению, вся эта красивая схема работает только в случае, когда окно отображается на экране. Изменить активный компонент для скрытого окна, если в нем уже имеется активный компонент, описанными выше средствами невозможно. В частности, даже если для скрытого окна попытаться установить логический фокус на другой компонент, при отображении этого окна фокус получит тот компонент, который имел фокус в момент скрытия окна, а не тот, для которого (при скрытом окне) вызывался метод SetFocusedElement. Единственная ситуация, при которой возможна установка логического фокуса для скрытого окна, – это ситуация, при которой в окне ранее еще не было компонента, имеющего фокус. Мы уже отмечали, что в этой ситуации установить фокус можно проще: обычным вызовом метода Focus.
2.7. Запрос на подтверждение закрытия окна
В классе Window1 измените обработчик Window_Closing:
Результат. Перед закрытием подчиненного окна win1 отображается стандартное диалоговое окно «Подтверждение» с запросом на подтверждение закрытия (рис. 10). При выборе варианта «Нет» (который предлагается по умолчанию) закрытие подчиненного окна отменяется.
Рис. 10. Стандартное диалоговое окно
Комментарий
В обработчике использован один из наиболее полных вариантов метода Show класса MessageBox, позволяющий указать (1) текст запроса, (2) заголовок окна запроса, (3) набор кнопок для данного окна, (4) иконку в окне и (5) кнопку, предлагаемую по умолчанию. Любой параметр, кроме первого, может отсутствовать; при этом должны отсутствовать и все следующие за ним параметры. Если отсутствует второй параметр, то заголовок окна является пустым, если третий, то в окне отображается единственная кнопка «ОК», если четвертый – иконка в окне не отображается, если пятый – кнопкой по умолчанию является первая кнопка.