路由事件

什么是路由事件?先看一个Button的Click事件,该事件是一个路由事件。在Button控件的基类ButtonBase中关于路由事件定义的代码如下:
public class ButtonBase:ContentControl,ICommandSource
        {
            public static readonly RoutedEvent ClickEvent;  //路由事件的定义
            public event RoutedEventHandler Click   //传统的事件包装器
            {
                add
                {
                    base.AddHandler(ClickEvent, value);
                }
                remove
                {
                    base.RemoveHandler(ClickEvent, value);
                }
            }
            static ButtonBase()      //事件的注册
            {
                ClickEvent = EventManager.RegisterRoutedEvent("Click".
                    RoutingStrategy.Bubble,
                    typeof(RoutedEventHandler),
                    typeof(ButtonBase));
            }
        }

同依赖属性一样,路由事件也需要注册,不同的是使用EventManager.RegisterRoutedEvent方法。传统的CLR事件关联和解除关联处理函数均通过委托来实现,而路由事件则通过AddHanlder和RemoveHandler方法实现。传统事件的触发往往是调用其委托(因为事件的本质是委托),而路由事件则是通过一个RaiseEvent方法触发,调用该方法之后,所有关联该事件的对象都会得到通知。在ButtonBase中用如下方法关联事件:

RoutedEventArgs e = new RoutedEventArgs(ClickEvent,this);
base.RaiseEvent(e);

路由事件通过EventManager.RegisterRoutedEvent方法来注册事件;通过AddHandler和RemoveHandler来关联和解除关联的事件处理函数;通过RaiseEvent方法来触发事件;通过传统的CLR事件封装后供用户调用,使得用户如同使用传统的CLR事件一样使用路由事件。

在路由事件的旅行中,一般只出现两种角色:一是事件源,由其触发事件,是路由事件的起点;二是事件监听者,通常针对监听的事件有一个相应的事件处理函数。当路由事件经过事件监听者,就好比经过一个客栈,要做短暂的停留,由事件处理函数来处理该事件。路由事件的策略有如下三种:

1.Bubbling:事件从事件源出发一路上溯直到根节点,很多路由事件使用该策略;

2.Direct:事件事件源出发,围绕事件源转一圈结束;

3.Tunneling:事件源触发事件后,事件从根节点出发下沉直到事件源。

上面的事件策略相当于是一个旅行计划,它并不是一成不变的,而是可能会受到外力(监听者)的干预,那么这个计划可能会有所改变。

那么改变这个策略,也就是旅行计划的有哪些因素呢?

1.事件处理函数

一个最基本的路由事件处理函数原型如下:

public delegate void RoutedEventHandler(object sender, RoutedEventArgs)
鼠标事件处理函数的原型代码如下:
public delegate void MouseEventHandler(object sender, MouseEventArgs)
这种事件处理函数有如下两个特点:一是返回原型为void;二是有两个参数,第1个是一个object类型的对象,表示拥有该事件处理函数的对象,第2个是RoutedEventArgs或者是RoutedEventArgs的派生类,带有其路由事件的信息。

RoutedEventArgs的结构包括以下几部分:

Source:表明触发事件的源,如当键盘事件发生时,触发事件的源当前获得焦点的对象,当鼠标事件发生时,触发事件的源是鼠标所在的最上层对象。

OriginalSource:表明触发事件的源,一般来说OriginalSoource和Source相同,区别在于Source表示逻辑树上的元素,OriginalSource是可视树中的元素。如单击窗口的边框,Source为Window,OriginalSource为Border。

RoutedEvent:路由事件的对象。

Handled:布尔值,为true,表示该事件已处理,这样就可以停止路由事件。

由上可知:Hanlded属性就是改变路由事件旅行的元凶,一旦在某个事件处理函数中将Handled的值设置为true,路由事件就阻止传递。一个事件被标记为已处理,事件处理函数则不可处理该事件。但也有一种例外,WPF还提供了一种机制,即使事件被标记为已处理,事件处理函数仍然可以处理,但是关联事件及其处理函数需要稍做处理。AddHandler重载了两个方法,其中之一如下所示,需要将第3个参数设置为true:

public void AddHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo)
换句话说,因事件标识为处理而终止只是一种假象路由事件的旅行仍然在继续,只不过普通的事件处理函数无法处理它。

2.类和实例事件处理函数

事件处理函数有两种类型:一是前面所说的普通事件处理函数,称为实例事件处理函数(Instance Handlers);二是通过EventManager.RegisterClassHandler方法将一个事件处理函数和一个类关联起来,这种事件处理函数,称为类事件处理函数(Class Handlers),其优先级高于前者,也就是说事件在旅行时,会先光临类事件处理函数,然后再光临实例事件处理函数。

下面是一个路由事件的示例:

<Window x:Class="MyCustomTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:custom="clr-namespace:MyCustomTest"
        Title="MainWindow" Height="300" Width="300"
        custom:MySimpleButton.CustomClick="InsertList" Loaded="Window_Loaded">
    <Grid Margin="3" custom:MySimpleButton.CustomClick="InsertList" Name="grid1">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <custom:MySimpleButton x:Name="simpleBtn" CustomClick="InsertList">
            MySimpleButton
        </custom:MySimpleButton>
        <ListBox Margin="5" Name="lstMessage" Grid.Row="1"></ListBox>
        <CheckBox Margin="5" Grid.Row="2" Name="chkHandle">Handle first event</CheckBox>
        <Button Grid.Row="3" HorizontalAlignment="Right" Margin="5" Padding="3" 
                Click="cmdClear_Click">Clear List</Button>
    </Grid>
</Window>

自定义的MySimpleButton类代码如下:

class MySimpleButton : Button
    {
        static MySimpleButton() 
        {
            EventManager.RegisterClassHandler(typeof(MySimpleButton),
                CustomClickEvent, new RoutedEventHandler(CustomClickClassHandler), false);
        }
        //创建和注册该事件,该事件路由为Bubble
        public static readonly RoutedEvent CustomClickEvent =
            EventManager.RegisterRoutedEvent("CustomClick", RoutingStrategy.Bubble,
            typeof(RoutedEventHandler), typeof(MySimpleButton));
        //CLR事件的包装器
        public event RoutedEventHandler CustomClick
        {
            add { AddHandler(CustomClickEvent, value); }
            remove { RemoveHandler(CustomClickEvent, value); }
        }
        protected override void OnClick()
        {
            RaiseCustomClickEvent();
        }
        void RaiseCustomClickEvent()
        {
            RoutedEventArgs newEventArgs = new RoutedEventArgs(MySimpleButton.CustomClickEvent);
            RaiseEvent(newEventArgs);
        }

        public event EventHandler ClassHandlerProcessed;
        public static void CustomClickClassHandler(object sender, RoutedEventArgs e)
        {
            MySimpleButton simpleBtn = sender as MySimpleButton;
            EventArgs args = new EventArgs();
            simpleBtn.ClassHandlerProcessed(simpleBtn, args);
        }
    }
C#代码如下:
public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.simpleBtn.ClassHandlerProcessed += new EventHandler(simpleBtn_RaisedClass);
            
        }

        protected int eventCounter = 0;
        private void InsertList(object sender, RoutedEventArgs e)
        {
            eventCounter++;
            string message = "#" + eventCounter.ToString() + ":\r\n" +
                "InsertList\r\n" +
                "Sender:" + sender.ToString() + "\r\n" +
                "Source:" + e.Source + "\r\n" +
                "Original Source:" + e.OriginalSource;
            lstMessage.Items.Add(message);
            e.Handled = (bool)chkHandle.IsChecked;
        }

        private void cmdClear_Click(object sender, RoutedEventArgs e)
        {
            eventCounter = 0;
            lstMessage.Items.Clear();
        }

        private void simpleBtn_RaisedClass(object sender, EventArgs e)
        {
            eventCounter++;
            string message = "#" + eventCounter.ToString() + "\r\n" +
                "Windows Class Handler\r\n" + "Sender:" + sender.ToString();
            lstMessage.Items.Add(message);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            grid1.AddHandler(MySimpleButton.CustomClickEvent, new RoutedEventHandler(ProcessHandlersToo), true);
        }
        private void ProcessHandlersToo(object sender,RoutedEventArgs e)
        {
            eventCounter++;
            string message = "#" + eventCounter.ToString() + ":\r\n" +
                "ProcessHandlerToo\r\n" +
                "Sender:" + sender.ToString() + "\r\n" +
                "Source:" + e.Source + "\r\n" +
                "Original Source:" + e.OriginalSource;
            lstMessage.Items.Add(message);
            
        }
    }
在上面代码中,我们分别为Window、Grid和Button装配不同的事件处理函数,然后再单击按钮,观察路由和事件。代码效果图如下:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值