Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
WPF-практика 5 События и команды.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.21 Mб
Скачать

События мыши

В классе любого элемента управления 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

  1. Window: Наблюдаю туннельное событие PreviewMouseDown

  2. Window: Наблюдаю пузырьковое событие MouseDown

Green

  1. Window: Наблюдаю туннельное событие PreviewMouseDown

  2. Grid: Наблюдаю туннельное событие PreviewMouseDown

  3. Grid: Наблюдаю пузырьковое событие MouseDown

  4. Window: Наблюдаю пузырьковое событие MouseDown

Blue

  1. Window: Наблюдаю туннельное событие PreviewMouseDown

  2. Grid: Наблюдаю туннельное событие PreviewMouseDown

  3. UniformGrid: Наблюдаю туннельное событие PreviewMouseDown

  4. UniformGrid: Наблюдаю пузырьковое событие MouseDown

  5. Grid: Наблюдаю пузырьковое событие MouseDown

  6. Window: Наблюдаю пузырьковое событие MouseDown

Yellow

  1. Window: Наблюдаю туннельное событие PreviewMouseDown

  2. Grid: Наблюдаю туннельное событие PreviewMouseDown

  3. UniformGrid: Наблюдаю туннельное событие PreviewMouseDown

  4. TextBlock: Наблюдаю туннельное событие PreviewMouseDown

  5. TextBlock: Наблюдаю пузырьковое событие MouseDown

  6. UniformGrid: Наблюдаю пузырьковое событие MouseDown

  7. Grid: Наблюдаю пузырьковое событие MouseDown

  8. Window: Наблюдаю пузырьковое событие MouseDown

Aqua

  1. TextBlock: Возбуждаю прямое событие MouseEnter

  2. TextBlock: Возбуждаю прямое событие MouseLeave

Orange

  1. Window: Наблюдаю туннельное событие PreviewMouseDown

  2. Grid: Наблюдаю туннельное событие PreviewMouseDown

  3. UniformGrid: Наблюдаю туннельное событие PreviewMouseDown

  4. Button: Наблюдаю туннельное событие PreviewMouseDown

  5. Button: Возбуждаю пузырьковое событие Click

  6. UniformGrid: Наблюдаю пузырьковое событие Click (как вложенное)

  7. Grid: Наблюдаю пузырьковое событие Click (как вложенное)

  8. Window: Наблюдаю пузырьковое событие Click (как вложенное)

Обратите внимание, что хотя мы и вложили в элементы 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#.