深入浅出WPF,逐渐边缘化的大哥

事件是C#的功底之壹,学好事件对于通晓.NET框架大有补益。

  假使对事件一点都不打听仍然是笼统的话,提议先去看张子阳的嘱托与事件的稿子(相比较长,或者看完了,也忘记看那一篇了,没事,小编会原谅你的),废话不多说,开头进入正题。本记录不是记录守旧的事件(CLLacrosse事件),而是记录WPF中常用的风浪——路由事件,由于路由事件“传播”的时刻是沿着可视树传播的,所以在记录起始此前如故通晓一下逻辑树和可视树。

事件最常见的比方正是订阅,即,尽管你订阅了自身的博客,那么,当自家发表新博客的时候,你就会得到布告。

 1、逻辑树和可视树

  在WPF中有二种树:逻辑树(Logical Tree)和可视树(Visual
Tree),XAML是表述WPF的1棵树。逻辑树完全是由布局组件和控件构成。假如大家把逻辑树延伸至Template组件级别,大家就取得了可视树,所以可视树把树分的更周全。由于本记录重在记录事件,所以不做过多发表逻辑树和可视树的内容。关于逻辑树和可视树的区分能够参照。

而以此历程正是事件,也许说是事件运转的轨迹。

二、路由事件

事件是散落,以自家的博客为着力,向装有订阅者发送消息。大家把那种分散称之为[多播]。

二.1、小记事件

  若是看完了寄托与事件的小说,相信会对事件有更进一步的认识了,但照旧要把一部分基础的地方再记录一下。3个轩然大波要有上边多少个因素,才会变的有含义:

    • 事件的拥有者(sender)——即音讯的发送者。
    • 事件发送的音信(伊芙ntAgs)
    • 事件的响应者——消息的收信人(对象)。
    • 响应者的电脑——音讯的接受者要对音讯作出处理的法子(方法名)。
    • 响应者对发送者事件的订阅

最广泛的轩然大波用途是窗体编制程序,在Windows窗体应用程序和WPF应用程序中。

二.二 初试路由事件

  大家创设多少个winform项目,然后在窗体上添加三个按钮,双击添加二个总括机,会发觉private
void btn_Click(object sender, 伊夫ntArgs
e)处理器要处理消息是伊夫ntArgs类型的,那里对应的是观念的事件(大家叫它CL酷路泽事件)。同样大家创设二个WPF项目,然后添加三个按钮,双击添加一个处理器,会意识private
void Button_Click(object sender, Routed伊夫ntArgs
e)处理器要处理的音信是Routed伊芙ntArgs类型的,那里对应的是路由事件。两者有哪些差距吗。让我们先看看CL大切诺基事件的害处,就像是(this.btn.Click
+= new
System.伊夫ntHandler(this.btn_Click);)每三个音信都以从发送到响应的多个进度,当三个处理器要用数次,必须建立显式的点对点订阅关系(窗体对按钮事件的订阅,即使是再有三个按钮的话,就要再来1回订阅);还有二个弊病是:事件的宿主必须能够向来访问事件的响应者,不然不能建立订阅关系(如有八个零部件,点击组件一的按钮,想让组件2响应事件,那么就让组件2向组件一的按钮揭示三个得以访问的风云,那样一旦再多多少个嵌套,会现出事件链,有暴露假使揭示不当就存在着威迫)。路由事件除了能很好的消除地点的标题,还有贰个是路由事件在有路的气象下,能很好的根据规定的方法传播事件,因为XAML的树状结构,构成了一条条的征途,所以在WPF中,引进了路由事件。举个例子:如果窗体要以相同的点子处理五个按钮的风云,大家就足以用一句代码就化解了,this.AddHandler(Button.Click伊夫nt,
new
Routed伊夫ntHandler(this.ButtonClicked));那样就收缩代码量。下边通过一个例子初试一下路由事件。 给出XAML代码:

<Window x:Class="Chapter_06.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid x:Name="GridRoot" Background="Lime">
        <Grid x:Name="gridA" Margin="10" Background="Blue">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Canvas x:Name="canvasLeft" Grid.Column="0" Background="Red" Margin="10">
                <Button x:Name="buttonLeft" Content="left" Width="40" Height="100" Margin="10"/>
            </Canvas>
            <Canvas x:Name="canvasRight" Grid.Column="1" Background="Yellow" Margin="10">
                <Button x:Name="buttonRight" Content="right" Width="40" Height="100" Margin="10" />
            </Canvas>
        </Grid>
    </Grid>
</Window>

  大家点击按钮时,无论是buttonLeft照旧buttonRight单击都能显得按钮的名称。多少个按钮到顶部的window有唯一条路,左边的按钮对应的路:buttonLeft->canvasLeft->gridA->GridRoot->Window,左侧按钮对应的路:buttonRight->canvasRight->gridA->GridRoot->Window。借使GridRoot订阅三个总结机,那么处理器应该是1致的。后台代码为:

using System;
using System.Collections.Generic;
using System.Linq;
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 Chapter_06
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.GridRoot.AddHandler(Button.ClickEvent,new RoutedEventHandler(this.ButtonClicked));
        }
        private void ButtonClicked(object sender, RoutedEventArgs e)
        {
            MessageBox.Show((e.OriginalSource as FrameworkElement).Name);
        }
    }
}

  下边先解释一下路由事件是怎么沿着可视树来传播的,当Button被点击,Button就起来发送新闻了,可视树上的要素假使订阅了Button的点击事件,那么才会基于音信来作出相应的反应,假设没有订阅的话,就无所谓它产生的音讯,当然大家仍是可以够决定它的消息的传播格局,是从树根到树叶传播,如故树叶向树根传播以及是直接到达指标传播,不仅如此,还是能够控制音讯传出有些成分时,截至扩散。具体的会在后边记录到。其次是this.GridRoot.AddHandler(Button.Click伊夫nt,new Routed伊芙ntHandler(this.ButtonClicked));订阅事件时,第二个参数是路由事件类型,在此地用的是Button的Click伊夫nt,就像正视属性1样,类名加上信赖属性,那里是类名加上路由事件。其它一个是e.OriginalSource与e.Source的界别。由于音讯每传1站,都要把音讯交个3个控件(此控件成为了音信的出殡和埋葬地方),e.Source为逻辑树上的源头,要想博得原始发消息的控件(可视树的源流)要用e.OriginalSource。最终验明正身一下,怎么在XAML中添加订阅事件。直接用<Grid
x:Name=”gridA” Margin=”十” Background=”Blue”
Button.Click=”ButtonClicked”>在.net平台上不可能智能提示Button,因为Click是几次三番与ButtonBase的风云,XAML只认得含有Click事件的元素,可是要身先士卒的写下去才能学有所成。运维方面代码,点击左边按钮,效果如图一:

 

图片 1

图1

私下认可的路由音讯里面属性有三个,如图二,能够活动转到定义看一下其性质代表的含义。

图片 2

图2

 

当在窗体中式点心击按钮,移动鼠标等事件时,相应的后台程序会吸收布告,再履行代码。

 2.三自定义路由事件

  通过地方的小规模试制路由事件,应该适用由事件有些理解了,上边就记下一下什么自定义3个路由事件。大致分成八个步骤:1.声称并注册路由事件,二.为路由事件添加CLCRUISER事件包装,3.创办能够激励路由事件的主意。纪念一下借助属性,前五个步骤应该和路由事件很相似吧。上边将多个步骤分开来评释:

首先步:表明并注册路由事件       

       //***Event为路由事件名,类型为路由事件类型
       //注册事件用的是EventManager.RegisterRoutedEvent
      //第一个参数为事件名(下面的***都为同一个单词)
       //第二个参数为事件传播的策略,有三种策略:Bubble(冒泡式),Tunnel(隧道式),Direct(直达式)分别对应上面的三种青色字体的三种方式
       //第三个参数用于指定事件处理器的类型,该类型必须为委托类型,并且不能为 null。
       //第四个参数为路由事件的宿主    
       public static readonly RoutedEvent ***Event = EventManager.RegisterRoutedEvent("***", RoutingStrategy.Bubble,
                                                             typeof(***RouteEventHandler), typeof(ClassName));

第一步:为路由事件添加CLRubicon事件包装

       /*包装事件
        *这里与传统的数据差别是把+=和-=换成了AddHandler和RemovedHandler
        */
        public event RoutedEventHandler ***
        {
            add { this.AddHandler(***Event, value); }
            remove { this.RemoveHandler(***Event, value); }
        }

其三步:创建能够激起路由事件的秘籍

        /*对于控件的事件,一般是重写宿主事件对应的方法(如Button的click事件和OnClick()方法相对应):新建消息,并把消息与路由事件相关联,
         *通过调用元素的RaiseEvent方法把时间传送出去(这里与包装器的CRL事件毫不相干),在CLR事件是用Invoke方法,下面以按钮为例
         */

        protected override void OnClick()
        {
            base.OnClick();
            ***EventArgs args = new ***EventArgs(***Event, this);
            this.RaiseEvent(args);
        }

 下边大家就来促成2个简便的自定义路由作用,当路由飘过二个控件的小时,突显通过该控件的时间。 上边介绍的大都了,所以就直接上代码,有亟待表达的话,再3个个表明。

下面是XAML代码:

<Window x:Class="DefineEvent.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DefineEvent" 
        Title="Routed Event" x:Name="window_1" Height="350" Width="525" local:TimeButton.ReportTime="ReportTimeHandler" >
    <Grid x:Name="grid_1" local:TimeButton.ReportTime="ReportTimeHandler" >
        <Grid x:Name="grid_2" local:TimeButton.ReportTime="ReportTimeHandler"  >
            <Grid x:Name="grid_3" local:TimeButton.ReportTime="ReportTimeHandler"  >
                <StackPanel x:Name="stackPanel_1" local:TimeButton.ReportTime="ReportTimeHandler" >
                    <ListBox x:Name="listBox" />
                    <local:TimeButton x:Name="timeButton" Width="200" Height="80" Content="显示到达某个位置的时间" ReportTime="ReportTimeHandler"/>
                </StackPanel>
            </Grid>
        </Grid>
    </Grid>
</Window>

下面是CS代码

using System;
using System.Collections.Generic;
using System.Linq;
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;
using System.ComponentModel;

namespace DefineEvent
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    delegate void ReportTimeRouteEventHandler(object sender, ReportTimeEventArgs e);
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void ReportTimeHandler(object sender, ReportTimeEventArgs e)
        {
            FrameworkElement element = sender as FrameworkElement;
            string timeStr = e.ClickTime.ToString("yyyyMMddHHmmss");
            string content = string.Format("{0}到达{1}", timeStr, element.Name);
            this.listBox.Items.Add(content);
        }
    }
    //创建消息类型,在此可以附加自己想要的信息
    public class ReportTimeEventArgs : RoutedEventArgs
    {
        public ReportTimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { }
        public DateTime ClickTime { get; set; }
    }
   public class TimeButton : Button
    {
        //1、为元素声明并注册事件
        public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble,
                                                             typeof(ReportTimeRouteEventHandler), typeof(TimeButton));

       //2、包装事件
        public event RoutedEventHandler ReportTime
        {
            add { this.AddHandler(ReportTimeEvent,value); }
            remove { this.RemoveHandler(ReportTimeEvent,value); }
        }
        //3、创建激发事件的方法
        protected override void OnClick()
        {
            base.OnClick();
            ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent,this);
            args.ClickTime = DateTime.Now;
            this.RaiseEvent(args);
        }
    }
}

运行单击按钮的功能为图3:

图片 3

图3

  注意下边的一行代码,在宣称和定义路由事件时,第伍个参数,委托的档次和处理器方法的的参数一定要1律,不然会报错,能够把下部一句中的ReportTimeRoute伊芙ntHandler换到Routed伊芙ntHandler试试,会油可是生:不可能从文本“ReportTimeHandler”成立“ReportTime”。”,行号为“5”,行任务为“30”,的标题,那里重要的来头正是寄托的参数和Routed伊夫ntHandler的参数不雷同,纵然都是(sender, 
e);然而e的品种已经发生了扭转,成为了ReportTime伊夫ntArgs类型的。所以在选用在此以前,声喜宝个寄托就能够了。还有个方法是使用伊夫ntHandler<ReportTime伊夫ntArgs>替换ReportTimeRoute伊夫ntHandler,其实两边的用法差不多,只是分化的写法,但是是笔者觉得第3种写法会越来越好精晓。具体有关伊芙ntHandler<ReportTime伊夫ntArgs>的意义请参考。大家一样能够选用让第一个参数改变成其余两体系型的探视测试结果。

public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Tunnel,
                                                             typeof(ReportTimeRouteEventHandler), typeof(TimeButton));

 假使大家意在当事件传递到grid_二地点就停下了,大家得以那样做:在ReportTimeHandler函数中添加代码。

            if (element.Name == "grid_2")
                e.Handled = true;

事件的概念

 三、总结

  本篇记录的情节固然不多,不过感觉记录的大运专程讨厌,重要是因为对事件的多少个组成部分还不是不行熟识,而且在明白路由事件时,还要先知道逻辑树与可视树。最终依然把那1章看完了,但以此只是初阶。

  文章首要记录了路由事件的在可视树上的传播以及自定义路由事件的兑现。假诺在篇章有区别的见识或建议,欢迎调换! 下1篇:《深刻浅出WPF》笔记——命令篇

 

法定对事件的注明是这么的:类或对象足以经过事件向别的类或对象通告发出的相干作业。

换到经常语言正是,事件能够定义成静态的或1般的,所以事件就足以由注脚的对象调用,也能够直接通过类调用静态事件。

事件是C#中的一类别型,除了框架为大家定义好的轩然大波外,大家还足以自定义事件,用event关键字来声称。

上面我们来看最基础的事件定义。

public delegate void TestDelegate(string message);                                                  
public event TestDelegate testEvent;

咱俩首先定义了三个委托,然后使用event关键字,定义二个事变。

总体上看,好像正是在概念3个信托,只是在信托的概念从前,加了个event关键字。

科学,事件的定义就是这么,因为要声美素佳儿个事变,要求四个要素:

一,标识提供对事件的响应的办法的嘱托。

2,2个类,用存款和储蓄事件的数码。即,事件要定义在类中。

上面大家来为这一个事件赋值。

public void Init()
{   
    testEvent += new TestDelegate(EventSyntax_testEvent); 
    testEvent += EventSyntax_testEvent; 
}
private void EventSyntax_testEvent(string message)
{
    Console.WriteLine(message);
}

如代码所示,大家运用了+=这些符号来为事件赋值,赋值的始末是二个寄托和三个函数。

内部+=大家将她清楚为【添加】。

代码中,我们选择三种赋值情势,但实在都以为事件testEvent添加1个委。

其次种将函数直接【添加】到事件中,编写翻译时也会把函数转换来委托【添加】到事件中。

系统提供事件

C#的框架都很经典,而种种经典框架都为我们提供了壹些经文事件。

出于事件必须[标识响应措施的委托],所以这一个事件所利用的寄托都有3个合伙的性状,命名中包蕴伊夫nt。

比如EventHandler,CancelEventHandler,RoutedEventHandler,ContextMenuEventHandler等。

中间最经典的便是伊夫ntHandler和Routed伊芙ntHandler。

EventHandler:

伊芙ntHandler定义如下

[SerializableAttribute]
[ComVisibleAttribute(true)]
public delegate void EventHandler(
 object sender,
 EventArgs e
)

他带有了三个参数,即当大家为事件添加伊芙ntHandler委托后,再去触发该事件;被触发的信托将获取object
sender和伊夫ntArgs e两个参数。

sender:代表源,即触发该事件的控件。

e:代表事件参数,即触发该事件后,事件为被触发的信托,传递了某个参数,以方便委托在处理数据时,更便捷。

基于那个原理,我们得以分析出众多事物。

诸如,当控件DataGrid的事件被触发时,只要查看一下sender的实在类型,就足以理解,到底是DataGrid触发的轩然大波,依旧DataGridRow或DataGridCell触发的了。

RoutedEventHandler:

Routed伊夫ntHandler即路由事件,他的概念如下

public delegate void RoutedEventHandler(
 Object sender,
 RoutedEventArgs e
)

Routed伊芙ntHandler也为大家提供了sender和e多少个参数。

但Routed伊夫ntHandler特别之处是,他的sender并不一定是忠实的源,因为他是1个冒泡路由事件,即上升事件。

那里假设我们有好奇心去看官方文书档案,那么会在相关的介绍中来看四个单词sender和source。

通过那五个单词,大家会清楚的垂询路由事件。简单描述一下sender和source,它们3个是发送者,一个是源。

发表评论

电子邮件地址不会被公开。 必填项已用*标注