Опубликован: 05.08.2010 | Уровень: специалист | Доступ: платный
Самостоятельная работа 12:

События и команды в WPF

События мыши

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

Упражнение 3. Создание и прослушивание пользовательского события

Наподобие библиотечных маршрутизированных событий мы можем создавать свои собственные перенаправленные события, которые называют пользовательскими. Порядок добавления в класс событий таков:

  1. Вначале объявляется статическое поле класса только для чтения типа RoutedEvent, которое будет являться базовым при упаковке события. В соответствии с соглашением, имя поля правильно заканчивать постфиксом Event. Имя базового поля ассоциируется с идентификатором поля как ссылкой на объект RoutedEvent, а не с именем создаваемого события
  2. С помощью метода RegisterRoutedEvent() класса System.Windows. EventManager событие нужно зарегистрировать в среде исполнения CLR (Common Language Runtime) и сохранить ссылку на объект события в статическом поле. В дальнейшем этот объект можно передавать обработчикам события. Вызов метода регистрации можно разместить в статическом конструкторе класса или вызвать сразу при инициализации поля
  3. Объявить само событие с помощью делегата RoutedEventHandler, использовав расширенный способ объявления события, который используется для упаковки поля события. Делегат обеспечивает стандартную сигнатуру для обработчиков
    • public delegate void RoutedEventHandler(object sender, RoutedEventArgs e)
  4. Определить, если нужно, метод диспетчеризации события с префиксом On

Объявление класса регистрации событий выглядит так

public static RoutedEvent RegisterRoutedEvent(
    string name,
    RoutingStrategy routingStrategy,
    Type handlerType,
    Type ownerType
)
  • name - имя маршрутизируемого события. Имя должно быть уникальным для данного типа владельца и не может быть пустой строкой или иметь значение ссылки null
  • routingStrategy - стратегия маршрутизации события, заданная в качестве значения перечисления System.Windows.RoutingStrategy. Это перечисление содержит элементы Tunnel, Bubble и Direct
  • handlerType - тип обработчика событий, который должен быть типом делегата и не может иметь значение ссылки null
  • ownerType - тип класса владельца маршрутизируемого события, который не может иметь значение ссылки null

Метод регистрации возвращает объект вновь зарегистрированного маршрутизируемого события, который нужно сохранить в статическом поле и в далнейшем упаковать в само событие. Упаковка события нужно для присоединения обработчиков, когда обработчики назначаются в разметке. Для прикрепления обработчиков в процедурном коде нужно использовать метод UIElement.AddHandler() явно, как будет показано ниже.

В данном упражнении создадим в классе собственное перенаправленное событие, которое будет исполнять назначенную стратегию маршрутизации ( Bubble, Tunnel, Direct ).

  • Добавьте к решению командой File/Add/New Project новый проект с именем UserEvents и назначьте его стартовым
  • Заполните файл Window1.xaml следующей базовой разметкой пользовательского интерфейса
<Window x:Class="UserEvents.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Пользовательские Routed-события" Height="300" Width="300"
    Background="Red"
    ToolTip="Элемент Window - Red"
    MinWidth="300"
    MinHeight="300"
    ResizeMode="CanResizeWithGrip"
    >
    <DockPanel>
        <Grid 
            Width="220"
            Height="200"
            Background="Green"
            ToolTip="Элемент Grid - Green"
            >
            <UniformGrid 
                Height="140" Width="130"
                Background="Blue"
                ToolTip="Элемент UniformGrid - Blue"
                >
                <Button   
                    VerticalAlignment="Center"
                    ToolTip="Элемент Button - #FFD4D0C8"
                    Margin="5,0,5,0"
                    >
                    Возбудить событие
                </Button>
            </UniformGrid>
        </Grid>
    </DockPanel>
</Window>

Чтобы не мудрствовать лукаво, на данном этапе мы взяли пустой пользовательский интерфейс из предыдущего упражнения и немного его упростили, убрав меню и контекстное меню окна.

  • Запустите приложение - получим заготовку окна с полуфункциональным интерфейсом для продолжения выполнения упражнения


Следующим шагом мы создадим класс с событием, расширяющий класс Button, и упакуем его в отдельный файл с именем MyButton.cs. Модель расширения кнопки будет удобна для возбуждения нашего события по перекрытому виртуальному методу OnClick().

  • Добавьте к текущему проекту командой Project/Add New Item новый файл с именем MyButton.cs по шаблону Custom Control (WPF), как показано на рисунке

  • Удалите сопутствующую папку Themes вместе с ее содержимым, которую автоматически создала оболочка для выбранного шаблона


Шаблон Custom Control (WPF) применяется для разработки пользовательских компонентов 'с нуля', но мы его здесь использовали потому, что в нем наиболее полно представлены подключенные пространства имен WPF

  • Заполните файл MyButton.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 UserEvents
{
    // Класс определения пользовательского события Tap,
    // возбуждаемого по щелчку мыши на расширенной кнопке
    public class MyButton : Button
    {
        // Объявляем базовое поле события
        public static readonly RoutedEvent TapEvent;
    
        // Инициализируем в статическом конструкторе базовое поле события
        // (можно было инициализировать сразу при объявлении поля, без конструктора)
        static MyButton()
        {
            TapEvent = EventManager.RegisterRoutedEvent(
                "Tap",                          // Зарегистрированное имя
                RoutingStrategy.Bubble,         // Стратегия перенаправления
                typeof(RoutedEventHandler),     // Тип делегата обработчиков
                typeof(MyButton)                // Тип владельца события
                );
        }
    
        // Контейнер для создания обработчиков в XAML
        public event RoutedEventHandler Tap
        {
            add { base.AddHandler(TapEvent, value); }
            remove { base.RemoveHandler(TapEvent, value); }
        }
    }
}

Событие нужно чем-то возбудить, собственно для этого мы и выбрали расширение именно кнопки. Переопределяемые события возбуждаются методом RaiseEvent(), который наследуется от класса UIElement всеми элементами управления пользовательского интерфейса (кроме документных для ContentElement ).

  • Добавьте в класс-расширение MyButton кнопки перекрытие виртуального метода OnClick(), наследуемого от базового класса Button, со следующим кодом
public static int count;        // Счетчик перехвата события
    
        // Перекроем метод щелчка базовой кнопки для возбуждения события
        protected override void OnClick()
        {
            count = 0;          // Сбрасываем счетчик
            //Console.Clear();    // Очищаем консоль
            base.RaiseEvent(new RoutedEventArgs(MyButton.TapEvent));// Возбуждаем событие
        }

Необязательный поле-счетчик count мы ввели для нумерации узлов прохождения прослушиваемого события при выдачи результатов. Он должен накапливать значение, поэтому объявлен как статический, чтобы иметь область видимости уровня объекта-типа, а не объекта-экземпляра.

Далее мы вложим пользовательское событие в элементы логического дерева интерфейса и прикрепим к нему обработчики для прослушивания. Заметим, что владельцем пользовательского события Tap является сам класс-расширение кнопки, поэтому обработчик щелчка нужно прикрепить прямо к событию.

Для наглядности часть обработчиков пользовательского события мы прикрепим в разметке, а часть - в процедурном коде страницы. В последнем случае прослушивающим элементам дерева нужно присвоить имена. И еще, для видимости класса с событием в файле разметки в объект окна Window1 (или индивидуально в каждый прослушивающий элемент) нужно добавить пространство имен по синтаксису

xmlns:custom="clr-namespace:UserEvents"

с любым уникальным именем, например, custom.

  • Дополните разметку следующим выделенным кодом
<Window x:Class="UserEvents.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Пользовательские Routed-события" Height="300" Width="300"
    Background="Red"
    ToolTip="Элемент Window - Red"
    MinWidth="300"
    MinHeight="300"
    ResizeMode="CanResizeWithGrip"
        
    xmlns:custom="clr-namespace:UserEvents"
    Name="nWindow1"
    >
    <DockPanel
        Name="nDockPanel"
        >
        <Grid 
            Width="220"
            Height="200"
            Background="Green"
            ToolTip="Элемент Grid - Green"
            
            custom:MyButton.Tap="Grid_Tap"
            >
            <UniformGrid 
                Height="140" Width="130"
                Background="Blue"
                ToolTip="Элемент UniformGrid - Blue"
                             
                custom:MyButton.Tap="UniformGrid_Tap"
                >
                <custom:MyButton   
                    VerticalAlignment="Center"
                    ToolTip="Элемент Button - #FFD4D0C8"
                    Margin="5,0,5,0"
    
                    Tap="MyButton_Tap"
                    >
                    Возбудить событие
                </custom:MyButton>
            </UniformGrid>
        </Grid>
    </DockPanel>
</Window>

Элементам Window1 и DockPanel мы только присвоили имена. Автоматически создать для них заготовки обработчиков с нужной сигнатурой оболочка не сможет, поскольку она их не видит. А для элементов с вложенными событиями - создаст.

  • Щелкайте на записях присоединения обработчиков в элементах Grid, UniformGrid, MyButton кода разметки и командой контекстного меню Navigate to Event Handler создайте в процедурном коде соответствующие заготовки обработчиков
  • Создайте в процедурном коде по любой из полученных заготовок еще два обработчика с именами nDockPanel_Tap() и nWindow1_Tap() и той же сигнатурой

В результате должны быть созданы обработчики с именами:

  1. MyButton_Tap(object sender, RoutedEventArgs e)
  2. UniformGrid_Tap(object sender, RoutedEventArgs e)
  3. Grid_Tap(object sender, RoutedEventArgs e)
  4. nDockPanel_Tap(object sender, RoutedEventArgs e)
  5. nWindow1_Tap(object sender, RoutedEventArgs e)
  • Добавьте в экземплярный конструктор класса MyButton процедурный код динамического прикрепления обработчиков к пользовательскому событию для именованных элементов nDockPanel и nWindow1 следующим образом
// Конструктор экземпляра
        public Window1()
        {
            InitializeComponent();
    
            // Динамический способ присоединения обработчиков
            nWindow1.AddHandler(MyButton.TapEvent,
                new RoutedEventHandler(this.nWindow1_Tap));
            nDockPanel.AddHandler(MyButton.TapEvent,
                new RoutedEventHandler(this.nDockPanel_Tap));
        }
  • Добавьте в конец класса Window1 функцию ShowTap() вывода результатов прослушивания события Tap
void ShowTap(object obj, RoutedEventArgs args)
        {
            if (MyButton.count == 0)
            {
                System.Diagnostics.Debug.WriteLine(
                    String.Format("\n\t Стратегия маршрутизации: {0}",
                    args.RoutedEvent.RoutingStrategy));
            }
    
            String typeName = obj.GetType().Name;
            System.Diagnostics.Debug.WriteLine(
                String.Format("{0}) {1}: Наблюдаю событие Tap", 
                ++MyButton.count, typeName));
        }

Данная функция будет выводить информацию о прохождении события по элементам дерева в панель оболочки Output. Применение метода String. Format() объясняется тем, что ни одна из 4-х перегрузок функции Debug. WriteLine() не способна принимать строку форматирования со спецификаторами формата типа {0}, {1}, и т.д. (в отличие от метода Console. WriteLine() ). При каждом новом возбуждении события в кнопке-источнике мы будем добавлять к выводу результатов заголовок с применяемой стратегией маршрутизации.

  • Если у вас панель Output еще не включена, то включите ее командой меню оболочки View/Output

Результаты можно выводить и на консольное окно. Тогда нужно выполнить следующее:

  1. В функции ShowTap() заменить метод System.Diagnostics.Debug. WriteLine() на Console. WriteLine()
  2. Изменить тип приложения с Windows Application на Console Application командой Project/Properties (в списке Output type )

  • Вставьте в каждый из созданных обработчиков пользовательского события Tap вызов функции ShowTap() по следующему образцу
private void nWindow1_Tap(object sender, RoutedEventArgs e)
        {
            this.ShowTap(sender, e);
        }

Здесь мы стремились сделать код как можно унифицированнее, чтобы в случае смены целевого объекта вывода результатов, например, с панели Output на консоль, не пришлось бы много исправлять.

Наконец-то наступил момент истины - время испытать наш код и подтвердить правильность механизма работы пользовательских событий, который должен быть точно таким же, как и библиотечных.

  • Запустите последовательно приложение с разными значениям стратегии маршрутизации (перечисление RoutingStrategy ) при регистрации события в классе MyButton. В окне Output должен появится следующий результат
Результат работы пользовательского события
Стратегия Результат
RoutingStrategy.Bubble     Стратегия маршрутизации: Bubble
  1. MyButton: Наблюдаю событие Tap
  2. UniformGrid: Наблюдаю событие Tap
  3. Grid: Наблюдаю событие Tap
  4. DockPanel: Наблюдаю событие Tap
  5. Window1: Наблюдаю событие Tap
RoutingStrategy.Tunnel     Стратегия маршрутизации: Tunnel
  1. Window1: Наблюдаю событие Tap
  2. DockPanel: Наблюдаю событие Tap
  3. Grid: Наблюдаю событие Tap
  4. UniformGrid: Наблюдаю событие Tap
  5. MyButton: Наблюдаю событие Tap
RoutingStrategy.Direct     Стратегия маршрутизации: Direct
  1. MyButton: Наблюдаю событие Tap

В таблице наглядно показано, как осуществляется стратегия маршрутизации события по дереву элементов: событие всплывает, нисходит или обрабатывается в месте возбуждения и немедленно останавливается ( Bubble, Tunnel или Direct ).

Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?

Dmitriy Ivanchenko
Dmitriy Ivanchenko
Украина, Кировоград, Виктория-П, 2011
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974