- •Лабораторная работа: События и команды в wpf
- •Обзор библиотечных событий
- •Упражнение 1. Обработка событий клавиатуры
- •События мыши
- •Упражнение 2. Прослушивание событий мыши
- •Упражнение 3. Создание и прослушивание пользовательского события
- •Обработчики уровня класса
- •Добавление информации в объект аргумента события
- •Задание для Упражнения 3
- •Модель команд
- •Объекты команд
- •Библиотечные классы команд
- •Присоединение команды к источнику
- •Привязка команды к прослушивающему элементу
- •Упражнение 4. Привязка команд в разметке
- •Перекрытие функций диспетчеризации событий
- •Прямой вызов команд
- •Упражнение 5. Привязка команд в процедурном коде
- •Жесты как источники команд
- •Добавление жестов в команду
- •Способ 1
- •Способ 2
- •Способ 3
- •Добавление жестов в прослушивающий элемент
- •Упражнение 6. Разработка простого блокнота без механизма команд
- •Создание главного меню
- •Добавление иконок
- •Создание логических ресурсов
- •Создание панели инструментов, строки состояния и рабочей области
- •Замена встроенного контекстного меню
- •Назначение ресурсов неразделяемыми
- •Замена иконок на прозрачные
- •Отключение встроенных команд
- •Подключение иконки приложения
- •Распределение класса по нескольким файлам и создание вспомогательных функций
- •Размещение вариантов заголовков окна в ресурсах приложения
- •Создание заготовок обработчиков
- •Регистрация обработчиков в разметке
- •Реализация обработчиков раздела меню File
- •Обработка системной кнопки
- •Реализация части обработчиков раздела меню Edit
- •Разработка и кодирование диалогового окна Find and Replace
- •Кодирование функциональности Find and Replace в основном окне
- •Подключение функциональности Find and Replace к источникам задач
- •Разработка диалогового окна Go To
- •Применение вложенных ресурсов
- •Подключение функциональности Go To к основному окну
- •Прочие задачи
- •Принудительная перерисовка окна
- •Добавление жестов
- •Логика отключения источников задач
- •Реализация логики отключения источников задачи Save
- •Упражнение 7. Разработка простого блокнота с использованием механизма команд
- •Создание нового проекта из копии существующего
- •Краткий анализ задачи
- •Создание и привязка команд
- •Реализация логики доступности источников команд
- •Отображение позиции курсора в строке состояния
События мыши
В классе любого элемента управления WPF предусмотрено достаточно событий для программного реагирования на действия мышью со стороны пользователя. Все такие события имеют информативные названия со вставкой Mouse, например:
PreviewMouseDown, MouseDown, PreviewMouseUp, MouseUp, PreviewMouseMove, MouseMove, PreviewMouseWheel, MouseWheel, PreviewDragEnter, DragEnter, PreviewDrop, Drop и т.д.
Большинство событий унаследовано интерфейсными элементами WPF от класса UIElement, но часть событий добавлена самостоятельно или другими классами. Так, более поздний в цепочке наследования класс Control добавляет события PreviewMouseDoubleClick и MouseDoubleClick. Всеразличная информация о состоянии мыши передается вместе с событием в обработчик через объект аргумента и может быть из него извлечена. Но также, как и в случае с клавиатурным классом Keyboard, статический класс Mouse следит за состоянием мыши в реальном масштабе времени.
Все события мыши, связанные со щелчками или перемещением, передают объект аргументов MouseButtonEventArgs, наследующий класс MouseEventArgs. В этом объекте содержится информации о текущих координатах курсора, кнопке мыши, которая произвела щелчок (левая/правая/средняя), состоянии кнопки (нажата/отпущена), какой щелчок (одинарный/двойной) и многое другое. Даже если в элементе нет события MouseClick или MouseDoubleClick, его можно легко распознать в обработчике события MouseDown, проанализировав свойство аргумента ( MouseButtonEventArgs e ) как e.ClickCount == 1 (одинарный щелчок) или e.ClickCount == 2 (двойной щелчок).
Событий мыши очень много и каждое из них имеет свою специфику, поэтому ограничимся только таким вступительным словом и перейдем к их практическому использованию.
Упражнение 2. Прослушивание событий мыши
Добавьте к решению командой File/Add/New Project новый проект с именем ListenerEvents и назначьте его стартовым
увеличить изображение
Выполните команду Project/ListenerEvents Properties... и настройте выпадающий список Output type на значение Console Application, чтобы параллельно запускались графическое и консольное окна приложения
увеличить изображение
Мы будем воздействовать мышью на графическое окно, а вывод обработчиков событий наблюдать в консольном окне. Порядок выполнения обработчиков позволит проследить маршрут движение событий по дереву элементов.
Заполните файл разметки Window1.xaml следующим кодом
<Window x:Class="ListenerEvents.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1: Маршрутизация событий" Height="300" Width="300"
Background="Red"
ToolTip="Элемент Window - Red"
PreviewMouseDown="Window_PreviewMouseDown"
MouseDown="Window_MouseDown"
ButtonBase.Click="Window_Click"
>
<Window.ContextMenu>
<ContextMenu>
<MenuItem Header="Item1" />
</ContextMenu>
</Window.ContextMenu>
<DockPanel>
<Menu
DockPanel.Dock="Top"
ToolTip="Элемент Menu - #FFD4D0C8"
Background="#FFD4D0C8"
>
<MenuItem Header="File">
<MenuItem Header="_Open" />
<MenuItem Header="_Save" />
<MenuItem Header="Save_As" />
<MenuItem Header="E_xit" />
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Header="Cu_t" />
<MenuItem Header="_Copy" />
<MenuItem Header="_Paste" />
</MenuItem>
</Menu>
<Grid
Width="220"
Height="200"
Background="Green"
ToolTip="Элемент Grid - Green"
PreviewMouseDown="Grid_PreviewMouseDown"
MouseDown="Grid_MouseDown"
ButtonBase.Click="Grid_Click"
>
<UniformGrid
Rows="3"
Height="140" Width="130"
Background="Blue"
ToolTip="Элемент UniformGrid - Blue"
PreviewMouseDown="UniformGrid_PreviewMouseDown"
MouseDown="UniformGrid_MouseDown"
ButtonBase.Click="UniformGrid_Click"
>
<TextBlock
Background="Yellow"
VerticalAlignment="Center"
TextAlignment="Center"
ToolTip="Элемент TextBlock - Yellow"
PreviewMouseDown="TextBlock_PreviewMouseDown"
MouseDown="TextBlock_MouseDown"
ButtonBase.Click="TextBlock_Click"
>
Туннельное
<LineBreak />
Пузырьковое
</TextBlock>
<TextBlock
Background="Aqua"
VerticalAlignment="Center"
TextAlignment="Center"
ToolTip="Элемент TextBlock - Aqua"
MouseEnter="TextBlock_MouseEnter"
MouseLeave="TextBlock_MouseLeave"
PreviewMouseDown="TextBlock_PreviewMouseDown"
MouseDown="TextBlock_MouseDown"
ButtonBase.Click="TextBlock_Click"
>
Прямое MouseEnter
<LineBreak />
Прямое MouseLeave
</TextBlock>
<Button
Background="Orange"
VerticalAlignment="Center"
ToolTip="Элемент Button - Orange"
PreviewMouseDown="Button_PreviewMouseDown"
MouseDown="Button_MouseDown"
Click="Button_Click"
>
Генератор Click
</Button>
</UniformGrid>
</Grid>
</DockPanel>
</Window>
Обратите внимание, что все элементы, пока, мы сделали неименованными, но среда выполнения в точности определит, какой элемент возбудил или обработал событие. В названиях пунктов меню мы применили знаки подчеркивания для использования горячих клавиш, которые проявятся после нажатия клавиши Alt в работающем приложении. В Windows Forms для этой цели используется символ амперсанда &, но в XAML он конфликтовал бы с подобным управляющим символом &, поэтому был заменен на подчеркивание.
Заполните файл поддержки разметки Window1.xaml.cs следующим кодом
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ListenerEvents
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
int count;
private void Window_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
count = 0;
Console.Clear();
if (e.ChangedButton == MouseButton.Left)
Console.WriteLine("{0}) Window: Наблюдаю туннельное событие PreviewMouseDown", ++count);
else
e.Handled = true;
}
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("{0}) Window: Наблюдаю пузырьковое событие MouseDown", ++count);
}
private void Window_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("{0}) Window: Наблюдаю пузырьковое событие Click (как вложенное)", ++count);
}
private void Grid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("{0}) Grid: Наблюдаю туннельное событие PreviewMouseDown", ++count);
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("{0}) Grid: Наблюдаю пузырьковое событие MouseDown", ++count);
}
private void Grid_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("{0}) Grid: Наблюдаю пузырьковое событие Click (как вложенное)", ++count);
}
private void UniformGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("{0}) UniformGrid: Наблюдаю туннельное событие PreviewMouseDown", ++count);
}
private void UniformGrid_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("{0}) UniformGrid: Наблюдаю пузырьковое событие MouseDown", ++count);
}
private void UniformGrid_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("{0}) UniformGrid: Наблюдаю пузырьковое событие Click (как вложенное)", ++count);
}
private void TextBlock_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("{0}) TextBlock: Наблюдаю туннельное событие PreviewMouseDown", ++count);
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("{0}) TextBlock: Наблюдаю пузырьковое событие MouseDown", ++count);
}
private void TextBlock_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("{0}) TextBlock: Наблюдаю пузырьковое событие Click (как вложенное)", ++count);
}
private void TextBlock_MouseEnter(object sender, MouseEventArgs e)
{
Console.WriteLine("{0}) TextBlock: Возбуждаю прямое событие MouseEnter", ++count);
}
private void TextBlock_MouseLeave(object sender, MouseEventArgs e)
{
Console.WriteLine("{0}) TextBlock: Возбуждаю прямое событие MouseLeave", ++count);
}
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("{0}) Button: Наблюдаю туннельное событие PreviewMouseDown", ++count);
}
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("{0}) Button: Наблюдаю пузырьковое событие MouseDown", ++count);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("{0}) Button: Возбуждаю пузырьковое событие Click", ++count);
}
}
}
Обратите внимание, что в первом обработчике мы перехватываем туннельное событие щелчка мыши сразу же в корневом элементе и подавлем его для правой кнопки. В то же время это не влияет на работу контекстного меню, поскольку в этом случае среда выполнения создает свое всплывающее окно и передает ему фокус ввода.
Запустите приложение - получим следующее графическое окно
Для элементов логического дерева, входящих в разметку, мы определили разный цвет фона, чтобы можно было их визуально различать. Цвет фона меню Background="#FFD4D0C8" мы назначили в стиле HTML для напоминания, хотя по умолчанию оно и так имеет такой фоновый системный цвет, типичный для большинства пользовательских элементов управления (первый байт FF определяет коэффициент непрозрачности Opacity, FF - полная непрозрачность, 0 - полная прозрачность).
Теперь осталось последовательно щелкать на отдельные элементы графического окна и в консольном окне наблюдать за маршрутизацией событий по последовательности срабатывания обработчиков. Для удобства можно пользоваться всплывающей подсказкой. Ниже приведены результаты, для группировки которых использован цвет фона элемента:
Маршрутизация событий при щелчках на элементах |
|
Цвет (элемент) |
Вывод обработчиков |
Red |
|
Green |
|
Blue |
|
Yellow |
|
Aqua |
|
Orange |
|
Обратите внимание, что хотя мы и вложили в элементы TextBlock событие Click, но оно не проходит через них при щелчке на кнопке, поскольку сразу всплывает к родительскому элементу UniformGrid. И любой из элементов с вложенным событием Click, как и вообще с любым вложенным событием, способен его только слушать и обрабатывать, но никоим образом не может его возбуждать.
Может возникнуть вопрос, а как на самом деле в коде происходит регистрация обработчиков событий, если мы их прикрепили в разметке, ...и с помощью каких делегатов? Все это делает за нас оболочка благодаря тому, что класс поддержки разметки объявлен как partial (частичный). Чтобы увидеть это, надо зайти в конструктор кодовой части файла Window1.axml.cs для класса Window1, щелкнуть правой кнопкой мыши на вызове метода InitializeComponent() и выбрать команду Go To Definition (Перейти к определению) из контекстного меню. В редакторе отобразится созданный файл кода Window1.g.i.cs, где и будут полные определения прикрепленных к элементам обработчиков событий.
В нашем примере мы применили разные типы элементов для перехвата события. Когда применяются одинаковые типы элементов, то для правильной адресации следует присоединять к одному и тому же событию обработчики с уникальными именами. Но можно ко всем элементам присоединить и общий обработчик, тогда нужно присваивать уникальные имена уже элементам и анализировать в обработчике источник, возбудивший событие с присоединенным обработчиком. Сказанное можно проиллюстрировать примером из MSDN для трех именованных кнопок:
private void CommonClickHandler(object sender, RoutedEventArgs e)
{
FrameworkElement feSource = e.Source as FrameworkElement;
switch (feSource.Name)
{
case "YesButton":
// do something here ...
break;
case "NoButton":
// do something ...
break;
case "CancelButton":
// do something ...
break;
}
e.Handled=true;
}
Это распространенный прием для распознавания и обыкновенных событий C#.
