十三、C# 事件

1、多播委托

2、事件

3、自定义事件

在上一章中,所有委托都只支持单一回调。

然而,一个委托变量可以引用一系列委托,在这一系列委托中,每个委托都顺序指向一个后续的委托,

从而形成了一个委托链,或者称为多播委托*multicast delegate)。

使用多播委托,可以通过一个方法对象来调用一个方法链,创建变量来引用方法链,并将那些数据类型用

作参数传递给方法。

在C#中,多播委托的实现是一个通用的模式,目的是避免大量的手工编码。这个模式称为

observer(观察者)或者publish-subscribe模式,它要应对的是这样一种情形:你需要将单一事件的通知

(比如对象状态发生的一个变化)广播给多个订阅者(subscriber)。

一、使用多播委托来编码Observer模式

来考虑一个温度控制的例子。

假设:一个加热器和一个冷却器连接到同一个自动调温器。

为了控制加热器和冷却器的打开和关闭,要向它们通知温度的变化。

自动调温器将温度的变化发布给多个订阅者---也就是加热器和冷却器。

  1     class Program
  2     {
  3         static void Main(string[] args)
  4         {
  5             //连接发布者和订阅者
  6             Thermostat tm = new Thermostat();
  7             Cooler cl = new Cooler(40);
  8             Heater ht = new Heater(60);
  9             //设置委托变量关联的方法。+=可以存储多个方法,这些方法称为订阅者。
 10             tm.OnTemperatureChange += cl.OnTemperatureChanged;
 11             tm.OnTemperatureChange += ht.OnTemperatureChanged;
 12             string temperature = Console.ReadLine();
 13
 14             //将数据发布给订阅者(本质是依次运行那些方法)
 15             tm.OnTemperatureChange(float.Parse(temperature));
 16
 17             Console.ReadLine();
 18
 19
 20
 21         }
 22     }
 23     //两个订阅者类
 24     class Cooler
 25     {
 26         public Cooler(float temperature)
 27         {
 28             _Temperature = temperature;
 29         }
 30         private float _Temperature;
 31         public float Temperature
 32         {
 33             set
 34             {
 35                 _Temperature = value;
 36             }
 37             get
 38             {
 39                 return _Temperature;
 40             }
 41         }
 42
 43         //将来会用作委托变量使用,也称为订阅者方法
 44         public void OnTemperatureChanged(float newTemperature)
 45         {
 46             if (newTemperature > _Temperature)
 47             {
 48                 Console.WriteLine("Cooler:on ! ");
 49             }
 50             else
 51             {
 52                 Console.WriteLine("Cooler:off ! ");
 53             }
 54         }
 55     }
 56     class Heater
 57     {
 58         public Heater(float temperature)
 59         {
 60             _Temperature = temperature;
 61         }
 62         private float _Temperature;
 63         public float Temperature
 64         {
 65             set
 66             {
 67                 _Temperature = value;
 68             }
 69             get
 70             {
 71                 return _Temperature;
 72             }
 73         }
 74         public void OnTemperatureChanged(float newTemperature)
 75         {
 76             if (newTemperature < _Temperature)
 77             {
 78                 Console.WriteLine("Heater:on ! ");
 79             }
 80             else
 81             {
 82                 Console.WriteLine("Heater:off ! ");
 83             }
 84         }
 85     }
 86
 87
 88     //发布者
 89     class Thermostat
 90     {
 91
 92         //定义一个委托类型
 93         public delegate void TemperatureChangeHanlder(float newTemperature);
 94         //定义一个委托类型变量,用来存储订阅者列表。注:只需一个委托字段就可以存储所有订阅者。
 95         private TemperatureChangeHanlder _OnTemperatureChange;
 96         //现在的温度
 97         private float _CurrentTemperature;
 98
 99         public TemperatureChangeHanlder OnTemperatureChange
100         {
101             set { _OnTemperatureChange = value; }
102             get { return _OnTemperatureChange; }
103         }
104
105
106         public float CurrentTemperature
107         {
108             get { return _CurrentTemperature;}
109             set
110             {
111                 if (value != _CurrentTemperature)
112                 {
113                     _CurrentTemperature = value;
114                 }
115             }
116         }
117     }

上述代码使用+=运算符来直接赋值。向其OnTemperatureChange委托注册了两个订阅者。

目前还没有将发布Thermostat类的CurrentTemperature属性每次变化时的值,通过调用委托来

向订阅者通知温度的变化,为此需要修改属性的set语句。

这样以后,每次温度变化都会通知两个订阅者。

 public float CurrentTemperature
        {
            get { return _CurrentTemperature; }
            set
            {
                if (value != _CurrentTemperature)
                {
                    _CurrentTemperature = value;
                    OnTemperatureChange(value);
                }
            }
        }

这里,只需要执行一个调用,即可向多个订阅者发出通知----这天是将委托更明确地

称为“多播委托”的原因。

针对这种以上的写法有几个需要注意的点:

1、在发布事件代码时非常重要的一个步骤:假如当前没有订阅者注册接收通知。

则OnTemperatureChange为空,执行OnTemperatureChange(value)语句会引发一

个NullReferenceException。所以需要检查空值。

        public float CurrentTemperature
        {
            get { return _CurrentTemperature; }
            set
            {
                if (value != _CurrentTemperature)
                {

                    _CurrentTemperature = value;
                    TemperatureChangeHanlder localOnChange = OnTemperatureChange;
                    if (localOnChange != null)
                    {
                        //OnTemperatureChange = null;
                        localOnChange(value);
                    }

                }
            }
        }

在这里,我们并不是一开始就检查空值,而是首先将OnTemperatureChange赋值给另一个委托变量localOnChange .

这个简单的修改可以确保在检查空值和发送通知之间,假如所有OnTemperatureChange订阅者都被移除(由一个不同的线程),那么不会触发

NullReferenceException异常。

注:将-=运算符应用于委托会返回一个新实例。

对委托OnTemperatureChange-=订阅者,的任何调用都不会从OnTemperatureChange中删除一个委托而使它的委托比之前少一个,相反,

会将一个全新的多播委托指派给它,这不会对原始的多播委托产生任何影响(localOnChange也指向那个原始的多播委托),只会减少对它的一个引用。

委托是一个引用类型。

2、委托运算符

为了合并Thermostat例子中的两个订阅者,要使用"+="运算符。

这样会获取引一个委托,并将第二个委托添加到委托链中,使一个委托指向下一个委托。

第一个委托的方法被调用之后,它会调用第二个委托。从委托链中删除委托,则要使用"-="运算符。

1             Thermostat.TemperatureChangeHanlder delegate1;
2             Thermostat.TemperatureChangeHanlder delegate2;
3             Thermostat.TemperatureChangeHanlder delegate3;
4             delegate3 = tm.OnTemperatureChange;
5             delegate1 = cl.OnTemperatureChanged;
6             delegate2 = ht.OnTemperatureChanged;
7             delegate3 += delegate1;
8             delegate3 += delegate2;

同理可以使用+ 与  - 。

1             Thermostat.TemperatureChangeHanlder delegate1;
2             Thermostat.TemperatureChangeHanlder delegate2;
3             Thermostat.TemperatureChangeHanlder delegate3;
4             delegate1 = cl.OnTemperatureChanged;
5             delegate2 = ht.OnTemperatureChanged;
6             delegate3 = delegate1 + delegate2;
7             delegate3 = delegate3 - delegate2;
8             tm.OnTemperatureChange = delegate3;

使用赋值运算符,会清除之前的所有订阅者,并允许使用新的订阅者替换它们。

这是委托很容易让人犯错的一个设置。因为本来需要使用"+="运算的时候,很容易就会错误地写成"="

无论是 +、-、 +=、 -=,在内部都是使用静态方法System.Delegate.Combine()和System.Delegate.Remove()来实现的。

3、顺序调用

委托调用顺序图,需要下载。

虽然一个tm.OnTemperatureChange()调用造成每个订阅者都收到通知,但它们仍然是顺序调用的,而不是同时调用,因为

一个委托能指向另一个委托,后者又能指向其它委托。

注:多播委托的内部机制

delegate关键字是派生自System.MulticastDelegate的一个类型的别名。

System.MulticastDelegate则是从System.Delegate派生的,后者由一个对象引用和一个System.Reflection.MethodInfo类型的该批针构成。

创建一个委托时,编译器自动使用System.MulticastDelegate类型而不是System.Delegate类型。

MulticastDelegate类包含一个对象引用和一个方法指针,这和它的Delegate基类是一样的,但除此之外,

它还包含对另一个System.MulticastDelegate对象的引用 。

向一个多播委托添加一个方法时,MulticastDelegate类会创建委托类型的一个新实例,在新实例中为新增的方法存储对象引用和方法指针,

并在委托实例列表中添加新的委托实例作为下一项。

这样的结果就是,MulticastDelegate类维护关由多个Delegate对象构成的一个链表。

调用多播委托时,链表中的委托实例会被顺序调用。通常,委托是按照它们添加时的顺序调用的。

4、错误处理

错误处理凸显了顺序通知的重要性。假如一个订阅者引发一个异常,链中后续订阅不接收不到通知。

为了避免这个问题,使所有订阅者都能收到通知,必须手动遍历订阅者列表,并单独调用它们。

 1         public float CurrentTemperature
 2         {
 3             get { return _CurrentTemperature; }
 4             set
 5             {
 6                 if (value != _CurrentTemperature)
 7                 {
 8
 9                     _CurrentTemperature = value;
10                     TemperatureChangeHanlder localOnChange = OnTemperatureChange;
11                     if (localOnChange != null)
12                     {
13                         foreach (TemperatureChangeHanlder hanlder in localOnChange.GetInvocationList())
14                         {
15                             try
16                             {
17                                 hanlder(value);
18                             }
19                             catch (Exception e)
20                             {
21                                 Console.WriteLine(e.Message);
22
23                             }
24                         }
25                     }
26
27                 }
28             }
29         }

5、方法返回值和传引用

在这种情形下,也有必要遍历委托调用列表,而非直接激活一个通知。

因为不同的订阅者返回的值可能不一。所以需要单独获取。

二、事件

目前使用的委托存在两个关键的问题。C#使用关键字event(事件)一解决这些问题。

二、1 事件的作用:

1、封装订阅

如前所述,可以使用赋值运算符将一个委托赋给另一个。但这有可能造成bug。

在本应该使用 "+=" 的位置,使用了"="。为了防止这种错误,就是根本

不为包容类外部的对象提供对赋值运算符的运行。event关键字的目的就是提供额外

的封装,避免你不小心地取消其它订阅者。

2、封装发布

委托和事件的第二个重要区别在于,事件确保只有包容类才能触发一个事件通知。防止在包容

类外部调用发布者发布事件通知。

禁止如以下的代码:

tm.OnTemperatureChange(100);

即使tm的CurrentTemperature没有发生改变,也能调用tm.OnTemperatureChange委托。

所以和订阅者一样,委托的问题在于封装不充分。

二、2 事件的声明

C#用event关键字解决了上述两个问题,虽然看起来像是一个字段修饰符,但event定义的是一个新的成员类型。

 1     public class Thermostat
 2     {
 3         private float _CurrentTemperature;
 4         public float CurrentTemperature
 5         {
 6             set { _CurrentTemperature = value; }
 7             get { return _CurrentTemperature; }
 8         }
 9         //定义委托类型
10         public delegate void TemperatureChangeHandler(object sender, TemperatureArgs newTemperatrue);
11
12         //定义一个委托变量,并用event修饰,被修饰后有一个新的名字,事件发布者。
13         public event TemperatureChangeHandler OnTemperatureChange = delegate { };
14
15
16         public class TemperatureArgs : System.EventArgs
17         {
18             private float _newTemperature;
19             public float NewTemperature
20             {
21                 set { _newTemperature = value; }
22                 get { return _newTemperature; }
23             }
24             public TemperatureArgs(float newTemperature)
25             {
26                 _newTemperature = newTemperature;
27             }
28
29         }
30     }

这个新的Thermostat类进行了几处修改:

a、OnTemperatureChange属性被移除了,且被声明为一个public字段

b、在OnTemperatureChange声明为字段的同时,使用了event关键字,这会禁止为一个public委托字段使用赋值运算符。

只有包容类才能调用向所有订阅者发布通知的委托。

以上两点解决了委托普通存在 的两个问题

c、普通委托的另一个不利之处在于,易忘记在调用委托之前检查null值,

通过event关键字提供的封装,可以在声明(或者在构造器中)采用一个替代方案,以上代码赋值了空委托。

当然,如果委托存在被重新赋值为null的任何可能,仍需要进行null值检查。

d、委托类型发生了改变,将原来的单个temperature参数替换成两个新参数。

二、3 编码规范

在以上的代码中,委托声明还发生另一处修改。

为了遵循标准的C#编码规范,修改了TemperatureChangeHandler,将原来的单个temperature参数替换成两新参数,

即sender和temperatureArgs。这一处修改并不是C#编译器强制的。

但是,声明一个打算作为事件来使用的委托时,规范要求你传递这些类型的两个参数。

第一个参数sender就包含"调用委托的那个类"的一个实例。假如一个订阅者方法注册了多个事件,这个参数就尤其有用。

如两个不同的Thermostata实例都订阅了heater.OnTemperatureChanged事件,在这种情况下,任何一个Thermostat实例都

可能触发对heater.OnTemperatureChanged的一个调用,为了判断具体是哪一个Thermostat实例触发了事件,要在Heater.OnTemperatureChanged()

内部利用sender参数进行判断。

第二个参数temperatureArgs属性Thermostat.TemperatureArgs类型。在这里使用嵌套类是恰当的,因为它遵循和OntermperatureChangeHandler委托本身

相同的作用域。

Thermostat.TemperatureArgs,一个重点在于它是从System.EventArgs派生的。System.EventArgs唯一重要的属性是

Empty,它指出不存在事件数据。然而,从System.EventArgs派生出TemperatureArgs时,你添加了一个额外的属性,名为NewTemperature。这样一来

就可以将温度从自动调温器传递到订阅者那里。

编码规范小结:

1、第一个参数sender是object类型的,它包含对调用委托的那个对象的一个引用。

2、第二个参数是System.EventArgs类型的(或者是从System.EventArgs派生,但包含了事件数据的其它类型。)

调用委托的方式和以前几乎完全一样,只是要提供附加的参数。

  1     class Program
  2     {
  3         static void Main(string[] args)
  4         {
  5             Thermostat tm = new Thermostat();
  6
  7             Cooler cl = new Cooler(40);
  8             Heater ht = new Heater(60);
  9
 10             //设置订阅者(方法)
 11             tm.OnTemperatureChange += cl.OnTemperatureChanged;
 12             tm.OnTemperatureChange += ht.OnTemperatureChanged;
 13
 14             tm.CurrentTemperature = 100;
 15         }
 16     }
 17     //发布者类
 18     public class Thermostat
 19     {
 20         private float _CurrentTemperature;
 21         public float CurrentTemperature
 22         {
 23             set
 24             {
 25                 if (value != _CurrentTemperature)
 26                 {
 27                     _CurrentTemperature = value;
 28                     if (OnTemperatureChange != null)
 29                     {
 30                         OnTemperatureChange(this, new TemperatureArgs(value));
 31                     }
 32
 33                 }
 34             }
 35             get { return _CurrentTemperature; }
 36         }
 37         //定义委托类型
 38         public delegate void TemperatureChangeHandler(object sender, TemperatureArgs newTemperatrue);
 39
 40         //定义一个委托变量,并用event修饰,被修饰后有一个新的名字,事件发布者。
 41         public event TemperatureChangeHandler OnTemperatureChange = delegate { };
 42
 43         //用来给事件传递的数据类型
 44         public class TemperatureArgs : System.EventArgs
 45         {
 46             private float _newTemperature;
 47             public float NewTemperature
 48             {
 49                 set { _newTemperature = value; }
 50                 get { return _newTemperature; }
 51             }
 52             public TemperatureArgs(float newTemperature)
 53             {
 54                 _newTemperature = newTemperature;
 55             }
 56
 57         }
 58     }
 59
 60     //两个订阅者类
 61     class Cooler
 62     {
 63         public Cooler(float temperature)
 64         {
 65             _Temperature = temperature;
 66         }
 67         private float _Temperature;
 68         public float Temperature
 69         {
 70             set
 71             {
 72                 _Temperature = value;
 73             }
 74             get
 75             {
 76                 return _Temperature;
 77             }
 78         }
 79
 80         //将来会用作委托变量使用,也称为订阅者方法
 81         public void OnTemperatureChanged(object sender, Thermostat.TemperatureArgs newTemperature)
 82         {
 83             if (newTemperature.NewTemperature > _Temperature)
 84             {
 85                 Console.WriteLine("Cooler:on ! ");
 86             }
 87             else
 88             {
 89                 Console.WriteLine("Cooler:off ! ");
 90             }
 91         }
 92     }
 93     class Heater
 94     {
 95         public Heater(float temperature)
 96         {
 97             _Temperature = temperature;
 98         }
 99         private float _Temperature;
100         public float Temperature
101         {
102             set
103             {
104                 _Temperature = value;
105             }
106             get
107             {
108                 return _Temperature;
109             }
110         }
111         public void OnTemperatureChanged(object sender, Thermostat.TemperatureArgs newTemperature)
112         {
113             if (newTemperature.NewTemperature < _Temperature)
114             {
115                 Console.WriteLine("Heater:on ! ");
116             }
117             else
118             {
119                 Console.WriteLine("Heater:off ! ");
120             }
121         }
122     }

通过将sender指定为容器类(this),因为它是能为事件调用委托的唯一一个类。

在这个例子中,订阅者可以将sender参数强制转型为Thermostat,并以那种方式来访问当前温度,

或通过TemperatureArgs实例来访问在。

然而,Thermostat实例上的当前温度可能由一个不同的线程改变。

在由于状态改变而发生事件的时候,连同新值传递前一个值是一个常见的编程模式,它可以决定哪些状态变化是

允许的。

二、4  泛型和委托

使用泛型,可以在多个位置使用相同的委托数据类型,并在支持多个不同的参数类型的同时保持强类型。

在C#2.0和更高版本需要使用事件的大多数场合中,都无需要声明一个自定义的委托数据类型

System.EventHandler<T> 已经包含在Framework Class Library

注:System.EventHandler<T> 用一个约束来限制T从EventArgs派生。注意是为了向上兼容。

//定义委托类型

public delegate void TemperatureChangeHandler(object sender, TemperatureArgs newTemperatrue);

//定义一个委托变量,并用event修饰,被修饰后有一个新的名字,事件发布者。

public event TemperatureChangeHandler OnTemperatureChange = delegate { };

使用以下泛型代替:

public event EventHandler<TemperatureArgs> OnTemperatureChange = delegate { };

事件的内部机制:

事件是限制外部类只能通过 "+="运算符向发布添加订阅方法,并用"-="运算符取消订阅,除此之外的任何事件都不允许做。

此外,它们还阻止除包容类之外的其他任何类调用事件。

为了达到上述目的,C#编译器会获取带有event修饰符的public委托变量,并将委托声明为private。

除此之外,它还添加了两个方法和两个特殊的事件块。从本质上说,event关键字是编译器用于生成恰当封装逻辑的

一个C#快捷方式。

C#实在现一个属性时,会创建get set,

此处的事件属性使用了 add remove分别使用了Sytem.Delegate.Combine

与 System.Delegate.Remove

 1         //定义委托类型
 2         public delegate void TemperatureChangeHandler(object sender, TemperatureArgs newTemperatrue);
 3
 4         //定义一个委托变量,并用event修饰,被修饰后有一个新的名字,事件发布者。
 5         public event TemperatureChangeHandler OnTemperatureChange = delegate { };
 6
 7 在编译器的作用下,会自动扩展成:
 8         private TemperatureChangeHandler _OnTemperatureChange = delegate { };
 9
10         public void add_OnTemperatureChange(TemperatureChangeHandler handler)
11         {
12             Delegate.Combine(_OnTemperatureChange, handler);
13         }
14         public void remove_OnTemperatureChange(TemperatureChangeHandler handler)
15         {
16             Delegate.Remove(_OnTemperatureChange, handler);
17         }
18         public event TemperatureChangeHandler OnTemperatureChange
19         {
20             add
21             {
22                 add_OnTemperatureChange(value);
23             }
24
25             remove
26             {
27                 remove_OnTemperatureChange(value);
28             }
29
30         }

这两个方法add_OnTemperatureChange与remove_OnTemperatureChange 分别负责实现

"+="和"-="赋值运算符。

在最终的CIL代码中,仍然保留了event关键字。

换言之,事件是CIL代码能够显式识别的一样东西,它并非只是一个C#构造。

二、5 自定义事件实现

编译器为"+="和"-="生成的代码是可以自定义的。

例如,将OnTemperatureChange委托的作用域改成protected而不是private。这样一来,从Thermostat派生的类就被允许直接访问委托,

而无需受到和外部类一样的限制。为此,可以允许添加定制的add 和 remove块。

 1         protected TemperatureChangeHandler _OnTemperatureChange = delegate { };
 2
 3         public event TemperatureChangeHandler OnTemperatureChange
 4         {
 5             add
 6             {
 7                 //此处代码可以自定义
 8                 Delegate.Combine(_OnTemperatureChange, value);
 9
10             }
11
12             remove
13             {
14                 //此处代码可以自定义
15                 Delegate.Remove(_OnTemperatureChange, value);
16             }
17
18         }

以后继承这个类的子类,就可以重写这个属性了。

实现自定义事件。

小结:通常,方法指针是唯一需要在事件上下文的外部乃至委托变量情况。

换句话说:由于事件提供了额外的封装特性,而且允许你在必要时对实现进行自定义,所以最佳

做法就是始终为Observer模式使用事件。

时间: 2024-10-08 02:06:18

十三、C# 事件的相关文章

javascript高级程序设计 第十三章--事件

javascript高级程序设计 第十三章--事件js与HTML的交互就是通过事件实现的,事件就是文档或浏览器窗口中发生的一些特定的交互瞬间. 事件流:事件流描述的是从页面中接收事件的顺序,IE的是事件冒泡流,Netscape的是事件捕获流,这个两个是完全相反的事件流概念. 事件冒泡:由最具体的元素接收,然后逐级向上传播到更高级的节点,即事件沿DOM树向上传播,直到document对象. 事件捕获:不大具体的节点应该更早接收到事件,相当于沿DOM节点树向下级传播直到事件的实际目标,在浏览器中,是

JavaScript高级程序设计:第十三章 事件

JavaScript与HTML之间的交互是通过事件实现的. 一.事件流 首先我们要明白事件流的概念.当我们点击一个按钮时,也点击了按钮的容器元素,甚至也点击了整个事件.事件流描述就是从页面中接收事件的顺序.在主流浏览器中有两种事件接收方式.一种是IE提出的事件冒泡流,另一种是Netscape提出的事件捕获流.顾名思义,事件冒泡流是从被点击的最小元素逐渐向上索引DOM树,而事件捕获的思想是不太具体的节点先捕捉到事件,然后事件沿DOM树逐渐向下,一直传播到事件的实际目标. 由于老版本浏览器不支持事件

第十三章 事件

事件概念 JS与HTML之间的交互通过事件实现.事件就是文档或浏览器窗口中发生的一些的特定交互瞬间.可以使用侦听器(处理程序)来预定事件.以便事件发生时执行相应的代码. 事件流 事件流 描述的是从页面中接收事件的顺序. I 事件冒泡 IE的事件流叫做事件冒泡,事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,逐级向上传播到较不具体的节点. IE9.FF.chrome.safari冒泡到window对象. <!DOCTYPE html> <html lang="en&

第十三章——事件(事件处理程序)

事件就是用户或浏览器执行的某种动作.响应某个事件的函数就是事件处理程序. DOM0级事件处理程序. 使用DOM0级方法指定的事件处理程序被认为是元素的方法.因此这时候的事件处理程序是在元素的作用域中运行的:换句话说,程序中的this引用当前元素. 可以通过this访问元素的任何属性和方法.以这种方式添加的事件处理程序会在事件的冒泡流阶段被处理. DOM2级事件处理程序 和DOM0级方法一样,这里的事件处理程序也是依附在元素的作用域中运行的.使用DOM2级事件处理程序的好处就是可以添加多个事件处理

第十三章——事件对象

在触发DOM的某个事件上,会产生一个事件对象event,这个对象包含着所有与事件相关的信息. DOM中的事件对象 兼容DOM的浏览器会将一个event对象传入到事件处理程序中,无论指定事件处理程序使用什么方法(DOM0级,DOM2级),都会传入event对象. event对象包含着与创建它的特定事件有关的属性和方法.触发的事件类型不一样,可用的属性和方法也不一样.如下,是所有事件都有的成员: 在事件处理函数内部,this始终等于currentTarget的值,而target则只包含事件的实际目标

(十三)事件分发器——event()函数,事件过滤

事件分发器——event()函数 事件过滤 事件进入窗口之前被拦截 eventFilter #include "mywidget.h" #include "ui_mywidget.h" #include <QDebug> MyWidget::MyWidget(QWidget *parent) : QWidget(parent), ui(new Ui::MyWidget) { ui->setupUi(this); // 给MyLabel 安装事件过滤

《JavaScript高级程序设计》第12-13章

第十二章 DOM2和DOM3 1.DOM2和DOM3模块 DOM2级核心:在1级核心的基础上构建,为节点添加了更多的方法和属性 DOM2级视图:为文档定义了基于样式信息的不同视图 DOM2级事件:说明了如何使用事件和DOM文档交互 DOM2级样式:定义了如何以编程方式来访问和改变CSS样式信息 DOM2级遍历和范围:引入了遍历DOM文档和选择其特定部分的新接口 DOM2级HTML:在1级HTML基础上创建,添加了更多属性.方法和新接口 DOM3级XPath模块: DOM3级加载与保存模块 2.D

再看《JavaScript高级程序设计》第13、14、17、20-25章

第十三章 事件 1.事件流:描述的是从页面中接收事件的顺序 1)事件冒泡:事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点 2)事件捕获:事件开始时由不太具体的节点接收,然后逐级向下传播到最具体的元素 3)DOM事件流:经历三个阶段——事件捕获阶段.处于目标阶段和事件冒泡阶段 2.事件处理程序:事件是用户或浏览器自身执行的某种动作,响应某个事件的函数就是事件处理程序 1)HTML事件处理程序:将事件处理程序设置为 null 就可以删除该事件处理程序. 2)DOM0级事件处理程序:

《JavaScript高级程序设计》 阅读计划

第一周       第1章 JavaScript简介   1 第2章 在Html中使用JavaScript 1 第3章 基本概念   3         第二周       第4章 变量.作用域和内存问题 1.5 第5章 引用类型   2 第6章 面向对象的程序设计 1 第7章 函数表达式   1         第三周       第八章 BOM   1 第九章 客户端检测   1 第十章 DOM   2 第十一章 DOM扩展   1         第四周       第十二章 DOM2和D

《Javascript高级程序设计第3版》精华总结

一.JavaScript简介 1.1 javascript简史 1.2 javascript实现 + javascript是一种专为网页交互而设计的一种脚本语言,javascript由三大部分组成: ECMAScript,提供核心语言功能: DOM,提供访问和操作网页页面的方法和接口: BOM,提供与浏览器交互的方法和接口. 1.3 javascript的版本 二.在HTML中使用JavaScript 2.1 <script>元素 + 使用<script>元素向html页面中插入j