Разработка пользовательского интерфейса на основе технологии Windows Presentation Foundation
Шрифт:
Исправление. Определите для компонента StackPanel, содержащего поля ввода, обработчик события PreviewKeyDown:
Комментарий
При нажатии пробела возникают только события KeyDown и KeyUp (и связанные с ними события PreviewKeyDown и PreviewKeyUp), которые реагируют на нажатие любых клавиш, в том числе и не приводящих к генерации отображаемых символов. Мы перехватываем это событие на уровне родителя обоих полей ввода, поэтому оно не доходит до них и пробелы в полях ввода не отображаются.
Заметим, что перехватывать событие на более высоком уровне (на уровне вертикальной панели StackPanel или на уровне окна), не следует, так как в этом случае
Недочет 2. В нашей программе предполагается, что десятичным разделителем является запятая, тогда как при других региональных настройках в системе Windows может использоваться другой разделитель.
Исправление. Измените фрагмент последнего оператора в методе Window_PreviewTextInput:
Комментарий
Статическое свойство CurrentCulture класса CultureInfo, определенного в пространстве имен System.Globalization, позволяет получить информацию о региональных настройках, используемых операционной системой, в частности о числовых форматах. Необходимость в указании индекса [0] обусловлена тем, что свойство NumberDecimalSeparator имеет строковый тип, который не совместим по присваиванию с символьным типом. Заметим, что свойство NumberDecimalSeparator доступно только для чтения, однако имеется возможность изменить региональные настройки в целом для конкретного приложения (см. по этому поводу комментарий в проекте CLOCK, п. 4.1).
3.5. Контроль за изменением исходных данных
Добавьте в метод button1_Click следующий оператор:
Кроме того, определите для поля ввода textBox1 обработчик события TextChanged, а также свяжите этот обработчик с полем ввода textBox2:
Результат. При изменении операции или содержимого текстовых полей результат предыдущего вычисления стирается. Это важная возможность, позволяющая предотвратить рассогласование отображаемых данных. При ее отсутствии возможна ситуация, когда после выполнения, например, вычислений вида 3 + 2 (с результатом 5), пользователь изменит первый операнд на 2, получив на экране текст 2 + 2 = 5.
Комментарии
1. Здесь мы использовали более традиционный способ связывания одного обработчика события с несколькими компонентами – путем указания этого обработчика в xaml-файле в описании каждого компонента.
Впрочем, в данном случае тоже можно было воспользоваться механизмом маршрутизируемых событий и после создания обработчика textBox1_TextChanged для компонента textBox1 не копировать соответствующий атрибут в компонент textBox2, а переместить его в родительский компонент StackPanel, снабдив префиксом TextBox:
2. Указание обработчиков событий, подобных событию TextChanged, непосредственно в xaml-файле может приводить к неожиданным ошибкам. Например, если закомментировать условный оператор в методе textBox1_TextChanged
то при запуске программы возникнет исключение NullReferenceException («Ссылка на объект не указывает на экземпляр объекта»). Это связано с тем, что в WPF событие TextChanged возникает сразу после конструирования поля ввода (при присваивания свойству Text начального значения). Но в момент создания поля ввода textBox1 метка label2 еще не существует, поскольку компоненты создаются в порядке их указания в xaml-файле, что и приводит к возникновению исключения.
Мы избежали этой ошибки, добавив проверку в обработчик. Исправить подобную ошибку можно и другим способом: не добавляя проверку в метод textBox1_TextChanged, удалить оба атрибута TextChanged="textBox1_TextChanged" в xaml-файле и вместо этого добавить в конец конструктора MainWindow операторы
Благодаря
При анализе данного исправления возникает естественный вопрос: можно ли в программном коде связать требуемый обработчик с общим родителем обоих полей ввода – панелью StackPanel (подобно тому, как это делается в xaml-файле – см. комментарий 1)? Этому препятствуют два обстоятельства: во-первых, данная панель не имеет имени, с помощью которого к ней можно было бы обратиться в коде, и, во-вторых, в компоненте StackPanel отсутствует событие TextChanged. Первую проблему легко решить, добавив к описанию панели в xaml-файле атрибут x:Name. Вторая проблема решается благодаря наличию у любого компонента метода AddHandler, позволяющего связать с компонентом обработчик события даже в случае, если это событие для компонента не предусмотрено. Между прочим, первую из отмеченных проблем можно вообще не решать, если связать обработчик с родителем более высокого уровня – окном MainWindow. Таким образом, вместо указанных выше двух операторов достаточно добавить в конструктор окна следующий вызов метода AddHandler:
Обратите внимание на необходимость указания суффикса Event в первом параметре метода AddHandler и на более сложный способ определения второго параметра, требующий вызова конструктора класса, связанного с данным типом обработчиков событий.
4. Работа с датами и временем: CLOCK
Рис. 14. Окно приложения CLOCK
4.1. Отображение текущего времени
В список директив using в начале файла MainWindow.xaml.cs добавьте директиву:
В описание класса MainWindow добавьте поле
В конструктор класса добавьте следующие операторы:
< image l:href="#" alt="screen_image_63_490_75"/>Опишите в классе MainWindow обработчик события Tick для таймера (этот обработчик придется ввести полностью, вместе с его заголовком, так как заготовку для него нельзя создать с помощью окна Properties или xaml-файла):
Результат. При работе программы в ее окне отображается текущее время (рис. 15).
< image alt="screen_image_64_174_93" l:href="#"/>Рис. 15. Окно приложения CLOCK (первый вариант)
Комментарии
1. Для работы с датами и временем в библиотеке .NET предусмотрена структура DateTime. Ее статическое свойство Now, доступное только для чтения, возвращает текущую дату и время (по системным часам компьютера). Текущую дату без времени (время соответствует полуночи) можно получить с помощью статического свойства Today. Для преобразования даты/времени к их стандартным строковым представлениям можно использовать следующие методы структуры DateTime:
• ToShortDateString – дата в кратком формате «d», например «27.01.1756»;
• ToLongDateString – дата в полном формате «D», например «27 января 1756 г.»);
• ToShortTimeString – время в кратком формате «t», например «10:55»;
• ToLongTimeString – время в полном формате «T», например «10:55:15».
Метод ToString без параметров возвращает дату/время в формате «G» (дата в кратком формате, время в полном). Формат отображения даты/времени можно явно указать в методе ToString; например, в нашей программе можно было бы использовать такой вариант: DateTime.Now.ToString("T").
Упомянем еще некоторые форматы для даты/времени: «g» – дата и время в кратком формате, «F» – дата и время в полном формате, «f» – дата в полном формате, время в кратком, «M» или «m» – формат «месяц, день», «Y» или «y» – формат «месяц, год».
При форматировании дат используются текущие региональные настройки (в нашем случае – настройки для России), хотя имеется перегруженный вариант метода ToString, где можно явно указать требуемую региональную настройку. Можно также сменить региональную настройку для приложения в целом; для этого достаточно установить новое значение свойства CurrentCulture для объекта Thread.CurrentThread из пространства имен System.Threads. Например, для того чтобы установить для нашего приложения региональные настройки, соответствующие американскому варианту английского языка, достаточно добавить в конструктор следующий оператор: