《WPF程序设计指南》读书笔记——第9章 路由输入事件

1.使用路由事件

    路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。通俗地说,路由事件会在可视树(逻辑树是其子集)上,上下routed,如果哪个节点上订阅了事件,就会被触发。

    路由事件的规则有三种:

    (1)冒泡;由事件源向上沿视觉树传递一直到根元素。如 MouseDown

    (2)直接;只有事件源才有机会响应事件,某个元素引发事件后,不传递到其他元素

    (3)隧道;从元素树的根部调用事件处理程序并依次向下深入直到事件源。 一般情况下,WPF提供的输入事件都是以隧道/冒泡对实现的。隧道事件常常被称为Preview事件。如 PreviewMouseDown

    可以通过 e.handled = true 来停止路由。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Documents;

namespace LY.ExamineRoutedEvents
{
    public class ExamineRoutedEvents : Application
    {
        static readonly FontFamily fontfam = new FontFamily("宋体");
        const string strformat = "{0,-30},{1,-15},{2,-15},{3,-15}";
        StackPanel stackOutput;
        DateTime dtLast;
        [STAThread]
        public static void Main()
        {
            ExamineRoutedEvents app = new ExamineRoutedEvents();
            app.Run();
        }
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            Window win = new Window();
            win.Title = "Examine Routed Events";
            //建立Grid
            Grid grid = new Grid();
            win.Content = grid;
            //建立三行
            RowDefinition rowdef = new RowDefinition();
            rowdef.Height = GridLength.Auto;
            grid.RowDefinitions.Add(rowdef);

            rowdef = new RowDefinition();
            rowdef.Height = GridLength.Auto;
            grid.RowDefinitions.Add(rowdef);

            rowdef = new RowDefinition();
            rowdef.Height = new GridLength(100, GridUnitType.Star);
            grid.RowDefinitions.Add(rowdef);

            //建立button,加入到grid
            Button btn = new Button();
            btn.HorizontalAlignment = HorizontalAlignment.Center;
            btn.Padding = new Thickness(24);
            btn.Margin = new Thickness(24);
            grid.Children.Add(btn);
            //建立textblock,加入button
            TextBlock text = new TextBlock();
            text.FontSize = 24;
            text.Text = win.Title;
            btn.Content = text;
            //建立标题,显示在ScrollViewer
            TextBlock textHeadings = new TextBlock();
            textHeadings.FontFamily = fontfam;
            textHeadings.Inlines.Add(new Underline(new Run(string.Format(strformat,
                "Routed Events", "Sender", "Source", "OriginalSource"))));
            grid.Children.Add(textHeadings);
            Grid.SetRow(textHeadings, 1);
            //加入Scorllviewer
            ScrollViewer scroll = new ScrollViewer();
            grid.Children.Add(scroll);
            Grid.SetRow(scroll, 2);
            //建立Stackpanel,放入Scorllviewer
            stackOutput = new StackPanel();
            scroll.Content = stackOutput;
            //新增事件处理器
            UIElement[] els = { win, grid, btn, text };
            foreach (UIElement el in els)
            {
                // Keyboard
                el.PreviewKeyDown += AllPurposeEventHandler;
                el.PreviewKeyUp += AllPurposeEventHandler;
                el.PreviewTextInput += AllPurposeEventHandler;
                el.KeyDown += AllPurposeEventHandler;
                el.KeyUp += AllPurposeEventHandler;
                el.TextInput += AllPurposeEventHandler;

                // Mouse
                el.MouseDown += AllPurposeEventHandler;
                el.MouseUp += AllPurposeEventHandler;
                el.PreviewMouseDown += AllPurposeEventHandler;
                el.PreviewMouseUp += AllPurposeEventHandler;
                
                // Stylus
                el.StylusDown += AllPurposeEventHandler;
                el.StylusUp += AllPurposeEventHandler;
                el.PreviewStylusDown += AllPurposeEventHandler;
                el.PreviewStylusUp += AllPurposeEventHandler;
                //Window,Grid,Textblock类没有click事件,所以用在ButtonBase类中定义的AddHandler方法
                el.AddHandler(Button.ClickEvent,
                    new RoutedEventHandler(AllPurposeEventHandler));               
            }
            win.Show();
        }

        void AllPurposeEventHandler(object sender, RoutedEventArgs e)
        {
            //如果有时间空隙,加入空格
            DateTime dtNow = DateTime.Now;
            if (dtNow - dtLast > TimeSpan.FromMilliseconds(100))
                stackOutput.Children.Add(new TextBlock(new Run()));
            dtLast = dtNow;
            //显示事件信息
            TextBlock text = new TextBlock();
            text.FontFamily = fontfam;
            text.Text = string.Format(strformat, e.RoutedEvent.Name,
                TypeWithoutNamespace(sender), 
                TypeWithoutNamespace(e.Source),
                TypeWithoutNamespace(e.OriginalSource));
            stackOutput.Children.Add(text);
            (stackOutput.Parent as ScrollViewer).ScrollToBottom();
        }
        string TypeWithoutNamespace(object obj)
        {
            string[] str = obj.GetType().ToString().Split('.');
            return str[str.Length-1];
        }
    }
}

 2.自定义路由事件 

    (1)声明并注册路由事件

    (2)为路由事件添加CLR事件包装

    (3)创建可以激发路由事件的方法

    以ButtonBase类中定义的Click路由事件为例:

public abstract class ButtonBase : ContentControl, ICommandSource
{
    //字段 声明路由事件
    public static readonly RoutedEvent ClickEvent;
    //构造函数 在静态构造函数中注册路由事件,也可在直接申明字段同时注册路由事件
    static ButtonBase()
    {
	//第1个参数为事件名
	//第2个参数为路由策略,有三种策略:Bubble(冒泡式),Tunnel(隧道式),Direct(直达式)
	//第3个参数为用于指定事件处理器的类型,该类型必须为委托类型,并且不能为 null
	//第4个参数为路由事件所在的类名
        ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ButtonBase));
    }
    //事件 将路由事件包装成CLR事件
    public event RoutedEventHandler Click
    {
        add { base.AddHandler(ClickEvent, value); }
        remove { base.RemoveHandler(ClickEvent, value); }
    }
    //方法 创建激发事件的方法
    protected virtual void OnClick()
    {
        RoutedEventArgs e = new RoutedEventArgs(ClickEvent, this);
        base.RaiseEvent(e);
    }
}

  进一步可参考:http://www.cnblogs.com/wilderhorse/articles/3231454.html

     在进行上述自定义路由事件后,就可以像CLR事件一样,订阅事件和处理事件了。其中,订阅本身的事件同CLR事件一样,订阅子节点的事件用AddHandler方法,如上面“使用路由事件”中的例子。

 

posted @ 2013-09-07 13:09  小p  阅读(559)  评论(0编辑  收藏  举报