事件模型探究
Upcoming SlideShare
Loading in...5
×
 

事件模型探究

on

  • 4,120 views

 

Statistics

Views

Total Views
4,120
Views on SlideShare
4,085
Embed Views
35

Actions

Likes
2
Downloads
68
Comments
0

2 Embeds 35

http://www.cnblogs.com 23
http://blog.csdn.net 12

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

事件模型探究 事件模型探究 Document Transcript

  • 事件模型探究 金庸 Ematrix http://blog.csdn.net/ematrix001
  • 目录 函数指针进化论 ........................................................................................................................................................... 1 1、 1.1 C/C++函数指针.................................................................................................................................................... 1 Java 反射、多线程和回调 .................................................................................................................................. 2 1.2 1.3 C#委托、多线程和回调 ...................................................................................................................................... 4 参考资源 ............................................................................................................................................................. 10 1.4 Java 事件模型 ........................................................................................................................................................... 11 2、 五种事件处理方式 ............................................................................................................................................. 11 2.1 自定义 Java 事件 ............................................................................................................................................... 16 2.2 Swing 事件调度线程(Event Dispatch Thread, EDT) ................................................................................. 18 2.3 参考资源 ............................................................................................................................................................. 18 2.4 3、 .NET(C#)事件模型 ............................................................................................................................................... 19 3.1 C#委托(Delegates in C#) ............................................................................................................................ 19 3.2 C#事件处理(Event Handlers in C#) .......................................................................................................... 19 发布符合.NET Framework 准则的事件 .......................................................................................................... 22 3.3 应用范例 ............................................................................................................................................................. 24 3.4 参考资源 ............................................................................................................................................................. 29 3.5 DOM 和 JavaScript 事件模型................................................................................................................................. 30 4、 异步回调 vs.多线程 ........................................................................................................................................... 30 4.1 DOM 事件流(DOM Event Flow) ................................................................................................................. 31 4.2 绑定事件监听器(Event handler registration) ............................................................................................ 34 4.3 4.4 Event Delegation VS. Event Handling ........................................................................................................... 38 参考资源 ............................................................................................................................................................. 40 4.5 Flex 和 ActionScript 3 事件模型 ........................................................................................................................... 41 5、 概念和术语 ......................................................................................................................................................... 41 5.1 事件对象(Event objects) ............................................................................................................................. 42 5.2 事件流(The event flow) ............................................................................................................................... 42 5.3 禁止默认行为 ..................................................................................................................................................... 43 5.5 创建自定义事件类型 ......................................................................................................................................... 44 5.6 参考资源 ............................................................................................................................................................. 48 5.7
  • 函数指针进化论 1、 函数指针进化论 函数指针 (function pointer) 是传统 C 语言中少数的动态机制,但是近来许多语言都不再支持函数指针 (包括 Java 和 C#),而改用多态 (polymorphism)、反射 (reflection)、委托 (delegate) 等机制来取代函数指针。 1.1 C/C++函数指针 函数指针(function pointer) 是一种「指向函数的指针」 ,和一般指向资料的指针不同。凡是研究过许多系统原 代码 (例如:Linux Kernel、Borland OWL) 的人,对于函数指针应该都不陌生,因为多数低阶系统都使用 C 语言 编写,而函数指针是传统 C 语言中少数的动态机制,有许多不可取代的地方,所以这些 C 原代码中到处可见函数 指针。 范例源码 #include <iostream> using std::cin; using std::cout; using std::endl; //声明Fnc1(),Fnc2(),Twice() float Fnc1(int); float Fnc2(int); double Twice(float (*)(int),int); //主程序 void main(){ int A=3; int B=5; cout<<quot;Twice(Fnc1,A)的值为:quot;<<Twice(Fnc1,A)<<endl; cout<<quot;Twice(Fnc2,B)的值为:quot;<<Twice(Fnc2,B)<<endl; } float Fnc1(int N){ return float(N*N); } float Fnc2(int N){ return float(N*N*N); } double Twice(float (*pF)(int),int N){ return 2.0*double(pF(N)); } 运行结果 Twice(Fnc1,A)的值为:18 Twice(Fnc2,B)的值为:250 一旦函数可以被传递、被纪录,这开启了许多可能性,产生许多有趣的应用,特别是下列三者: 1. 多态 (polymorphism) 2. 多线程 (multithreading):将函数指针传进负责建立多线程的 API 中 3. 回调 (call-back):所谓的回调机制就是: 「当发生某事件时,自动呼叫某段程序代码」 ,Charles Petzold 称 此为「Don’t Call Me, I'll Call You」 。事件驱动 (event-driven) 的系统经常透过函数指针来实现回调机制 函数指针致命缺点:无法对参数 (parameter) 和返回值 (return value) 的型态进行检查,因为函数已经退化成 1 指针,指针并不带有这些型态信息。少了型态检查,当参数或返回值不一致时,会造成严重的错误。编译器和虚拟 机 (VM) 并不会帮我们找出函数指针这样的致命错误。 所以,许多新的程序语言不支持函数指针, 而改用其它方式。
  • 函数指针进化论 Java 与 C# 都不支持函数指针。 这不会影响对于多态的支持, 但是这会不会影响对于多线程 (multithreading) 与回调 (call-back) 机制的支持呢?答案是:不会!因为 Java 可以利用多态或反射 (reflection) 来实现多线程与 回调,而 C#可以利用多态或反射或委托 (delegate) 来实现多线程与回调。 1.2 Java 反射、多线程和回调 A. 反射 范例源码 import java.lang.reflect.*; public class Invoke { public static void main(String[] args) { try { Class c=Class.forName(args[0]); Method m=c.getMethod(args[1], new Class[]{}); Object ret=m.invoke(null,null); System.out.println(args[0]+quot;.quot;+args[1]+quot;()=quot;+ret); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { System.out.println(quot;找不到此类别quot;); } catch (NoSuchMethodException e) { System.out.println(quot;此方法不存在quot;); } catch (IllegalAccessException e) { System.out.println(quot;没有权限调用此方法quot;); } catch (InvocationTargetException e) { System.out.println(quot;调用方法时发生下列例外:quot;); System.out.println(e.getTargetException()); }}} 代码执行 java Invoke java.lang.System currentTimeMillis 运行结果 java.lang.System.currentTimeMillis()=1231215230359 代码说明 我们的第一步就是用名称去寻找指定的 Class。我们用类别名称 (命令列的第一个参数) 去呼叫 forName() 方法, 然后用方法名称 (命令列的第二个参数) 去取得方法。getMethod() 方法有两个参数:第一个是方法名称 (命令列的第二 个参数),第二个是 Class 对象的数组,这个阵例指明了方法的 signature (任何方法都可能会被多载,所以必须指定 signature 来分辨。) 因为我们的简单程序只呼叫没有参数的方法,我们建立一个 Class 对象的匿名空数组。如果我们 想要呼叫有参数的方法,我们可以传递一个类别数组,数组的内容是各个类别的型态,依顺序排列。 一旦我们有了 Method 对象,就呼叫它的 invoke() 方法,这会造成我们的目标方法被调用,并且将结果以 Object 对象传回。如果要对此对象做其它额外的事,你必须将它转型为更精确的型态。 invoke() 方法的第一个参数就是我们想要呼叫目标方法的对象,如果该方法是静态的,就没有对象,所以我们把第 一个参数设为 null,这就是我们范例中的情形。第二个参数是要传给目标方法作为参数的对象数组,它们的型态要符合 呼叫 getMethod() 方法中所指定的型态。因为我们呼叫的方法没有参数,所以我们传递 null 作为 invoke() 的第二个参 数。 以上是 Java 的例子,事实上,.NET 的反射机制也相去不远,不再赘述。反射机制是最动态的机制,比多态的功 2 能更强大。然而,反射的速度比多态慢许多 (而且多态又比函数指针稍慢),所以若非必要,应该少用反射机制。事实上, 不管是 Java API 或 .NET Framework,都不使用反射机制来实现回调与多线程。
  • 函数指针进化论 多线程 B. Java 使用多态的机制来处理多线程,共有 2 种方式: 第一种方式: 范例源码 /* * 继承Thread类,唯一缺点是不能再继承其它功能类 */ public class MyThread extends Thread { //覆盖run()方法,实现自己的线程任务 public void run() { System.out.println(quot;++++++++++++quot;); } public static void main(String[] args) { Thread t=new Thread(new MyThread()); t.start(); } } 第二种方式: 范例源码 /* * 实现Runnable接口,可以再继承其它功能类 */ public class MyThread2 implements Runnable { //实现run()方法,实现自己的线程任务 public void run() { System.out.println(quot;++++++++++++quot;); } public static void main(String[] args) { Thread t=new Thread(new MyThread2()); t.start(); } } C. 回调 Java 使用多态的机制来处理回调,使用 publisher/subscriber (出版者/订阅者) 的方式进行事件处理,详 细内容请参见 3.Java 事件模型 3
  • 函数指针进化论 1.3 C#委托、多线程和回调 C# 也支持多态与反射,但是 C# 却是使用 delegate 来实现多线程和回调 (而不使用多态与反射)。delegate 是函数指针的改良品种。delegate 的效率应该比多态稍差,但是用起来更方便,且允许使用静态方法。 委托 A.  委托类似于 C++ 函数指针,但它们是类型安全的。  委托允许将方法作为参数进行传递。  委托可用于定义回调方法。  委托可以链接在一起;例如,可以对一个事件调用多个方法。 声明、实例化和使用委托 1) 范例源码 // A set of classes for handling a bookstore: namespace Bookstore{ using System.Collections; // Describes a book in the book list: public struct Book { public string Title; // Title of the book. public string Author; // Author of the book. public decimal Price; // Price of the book. public bool Paperback; // Is it paperback? public Book(string title, string author, decimal price, bool paperBack) { Title = title; Author = author; Price = price; Paperback = paperBack; } } // Declare a delegate type for processing a book: public delegate void ProcessBookDelegate(Book book); // Maintains a book database. public class BookDB { // List of all books in the database: ArrayList list = new ArrayList(); // Add a book to the database: public void AddBook(string title, string author, decimal price, bool paperBack) { list.Add(new Book(title, author, price, paperBack)); } // Call a passed-in delegate on each paperback book to process it: public void ProcessPaperbackBooks(ProcessBookDelegate processBook) { 4 foreach (Book b in list) {
  • 函数指针进化论 if (b.Paperback) // Calling the delegate: processBook(b); } } } } // Using the Bookstore classes: namespace BookTestClient { using Bookstore; // Class to total and average prices of books: class PriceTotaller { int countBooks = 0; decimal priceBooks = 0.0m; internal void AddBookToTotal(Book book) { countBooks += 1; priceBooks += book.Price; } internal decimal AveragePrice() { return priceBooks / countBooks; } } // Class to test the book database: class TestBookDB { // Print the title of the book. static void PrintTitle(Book b) { System.Console.WriteLine(quot; {0}quot;, b.Title); } // Execution starts here. static void Main() { BookDB bookDB = new BookDB(); // Initialize the database with some books: AddBooks(bookDB); // Print all the titles of paperbacks: System.Console.WriteLine(quot;Paperback Book Titles:quot;); // Create a new delegate object associated with the static // method Test.PrintTitle: bookDB.ProcessPaperbackBooks(PrintTitle); // Get the average price of a paperback by using 5 // a PriceTotaller object: PriceTotaller totaller = new PriceTotaller();
  • 函数指针进化论 // Create a new delegate object associated with the nonstatic // method AddBookToTotal on the object totaller: bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal); System.Console.WriteLine(quot;Average Paperback Book Price: ${0:#.##}quot;, totaller.AveragePrice()); Console.ReadLine(); } // Initialize the book database with some test books: static void AddBooks(BookDB bookDB) { bookDB.AddBook(quot;The C Programming Languagequot;, quot;Brian W. Kernighan and Dennis M. Ritchiequot;, 19.95m, true); bookDB.AddBook(quot;The Unicode Standard 2.0quot;, quot;The Unicode Consortiumquot;, 39.95m, true); bookDB.AddBook(quot;The MS-DOS Encyclopediaquot;, quot;Ray Duncanquot;, 129.95m, false); bookDB.AddBook(quot;Dogbert's Clues for the Cluelessquot;, quot;Scott Adamsquot;, 12.00m, true); } } } 运行结果 Paperback Book Titles: The C Programming Language The Unicode Standard 2.0 Dogbert's Clues for the Clueless Average Paperback Book Price: $23.97 代码说明 BookDB 类封装一个书店数据库,它维护一个书籍数据库。它公开 ProcessPaperbackBooks 方法,该方法在数据库中查找所有平装书,并对每本平装书调用一个委托。使用的 delegate 类型 名为 ProcessBookDelegate。Test 类使用该类打印平装书的书名和平均价格。 委托的使用促进了书店数据库和客户代码之间功能的良好分隔。客户代码不知道书籍的存储方 式和书店代码查找平装书的方式。书店代码也不知道找到平装书后将对平装书执行什么处理。 合并委托(多路广播委托) ,即多播委托 2) 范例源码 delegate void Del(string s); class TestClass { static void Hello(string s) { System.Console.WriteLine(quot; Hello, {0}!quot;, s); } static void Goodbye(string s) { System.Console.WriteLine(quot; Goodbye, {0}!quot;, s); } static void Main() { 6 Del a, b, c, d; // Create the delegate object a that references
  • 函数指针进化论 // the method Hello: a = Hello; // Create the delegate object b that references // the method Goodbye: b = Goodbye; // The two delegates, a and b, are composed to form c: c = a + b; // Remove a from the composed delegate, leaving d, // which calls only the method Goodbye: d = c - a; System.Console.WriteLine(quot;Invoking delegate a:quot;); a(quot;Aquot;); System.Console.WriteLine(quot;Invoking delegate b:quot;); b(quot;Bquot;); System.Console.WriteLine(quot;Invoking delegate c:quot;); c(quot;Cquot;); System.Console.WriteLine(quot;Invoking delegate d:quot;); d(quot;Dquot;); } } 运行结果 Invoking delegate a: Hello, A! Invoking delegate b: Goodbye, B! Invoking delegate c: Hello, C! Goodbye, C! Invoking delegate d: Goodbye, D! 多线程 B. 范例源码 using System; using System.Threading; namespace CSharp{ class SimpleThreadApp{ public static void WorkerThreadMethod() { Console.WriteLine(quot;+++++++++++++++++quot;); } static void Main(string[] args) { //ThreadStart 是一个 delegate,由 System.Threading 所提供 ThreadStart start = new ThreadStart(WorkerThreadMethod); Thread t = new Thread(start); 7 t.Start(); }}}
  • 函数指针进化论 C. 回调 .NET Framework 允许您异步调用任何方法。为此,应定义与您要调用的方法具有相同签名的委托;公共 语言运行库会自动使用适当的签名为该委托定义 BeginInvoke 和 EndInvoke 方法。 定义测试方法和异步委托 class AsyncDemo { // The method to be executed asynchronously. public string TestMethod(int callDuration, out int threadId) { Console.WriteLine(quot;Test method begins.quot;); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format(quot;My call time was {0}.quot;,callDuration.ToString()); } } // The delegate must have the same signature as the method // it will call asynchronously. public delegate string AsyncMethodCaller(int callDuration, out int threadId); 第一种方式:使用 EndInvoke 等待异步调用 范例源码 class AsyncMain { public static void Main() { // The asynchronous method puts the thread id here. int threadId; // Create an instance of the test class. AsyncDemo ad = new AsyncDemo(); // Create the delegate. AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod); // Initiate the asychronous call. IAsyncResult result = caller.BeginInvoke(3000,out threadId, null, null); Thread.Sleep(0); Console.WriteLine(quot;Main thread {0} does some work.quot;,Thread.CurrentThread.ManagedThreadId); // Call EndInvoke to wait for the asynchronous call to complete, // and to retrieve the results. string returnValue = caller.EndInvoke(out threadId, result); Console.WriteLine(quot;The call executed on thread {0}, with return value quot;{1}quot;.quot;, threadId, returnValue); // Keep the console window open Console.WriteLine(quot;Press Enter to close this window.quot;); Console.ReadLine(); } } 8
  • 函数指针进化论 第二种方式:使用 WaitHandle 等待异步调用 范例源码 class AsyncMain2{ public static void Main(){ // The asynchronous method puts the thread id here. int threadId; // Create an instance of the test class. AsyncDemo ad = new AsyncDemo(); // Create the delegate. AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod); // Initiate the asychronous call. IAsyncResult result = caller.BeginInvoke(3000,out threadId, null, null); Thread.Sleep(0); Console.WriteLine(quot;Main thread {0} does some work.quot;,Thread.CurrentThread.ManagedThreadId); // Wait for the WaitHandle to become signaled. result.AsyncWaitHandle.WaitOne(); // Perform additional processing here. Console.WriteLine(quot;You can do something when calling is overquot;); // Call EndInvoke to retrieve the results. string returnValue = caller.EndInvoke(out threadId, result); Console.WriteLine(quot;The call executed on thread {0}, with return value quot;{1}quot;.quot;, threadId, returnValue); // Keep the console window open Console.WriteLine(quot;Press Enter to close this window.quot;); Console.ReadLine(); } } 第三种方式:轮询异步调用完成 范例源码 class AsyncMain3 { public static void Main() { // The asynchronous method puts the thread id here. int threadId; // Create an instance of the test class. AsyncDemo ad = new AsyncDemo(); // Create the delegate. AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod); // Initiate the asychronous call. IAsyncResult result = caller.BeginInvoke(3000,out threadId, null, null); Thread.Sleep(0); Console.WriteLine(quot;Main thread {0} does some work.quot;, Thread.CurrentThread.ManagedThreadId); // Poll while simulating work. while (result.IsCompleted == false) 9 { Thread.Sleep(10);
  • 函数指针进化论 } // Call EndInvoke to retrieve the results. string returnValue = caller.EndInvoke(out threadId, result); Console.WriteLine(quot;The call executed on thread {0}, with return value quot;{1}quot;.quot;, threadId, returnValue); // Keep the console window open Console.WriteLine(quot;Press Enter to close this window.quot;); Console.ReadLine(); } } 第四种方式:异步调用完成时执行回调方法 范例源码 class AsyncMain4{ // Asynchronous method puts the thread id here. private static int threadId; static void Main() { // Create an instance of the test class. AsyncDemo ad = new AsyncDemo(); // Create the delegate. AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod); // Initiate the asychronous call. Include an AsyncCallback // delegate representing the callback method, and the data // needed to call EndInvoke. IAsyncResult result = caller.BeginInvoke(3000, out threadId, new AsyncCallback(CallbackMethod), caller ); Console.WriteLine(quot;Press Enter to close application.quot;); Console.ReadLine(); } // Callback method must have the same signature as the // AsyncCallback delegate. static void CallbackMethod(IAsyncResult ar) { // Retrieve the delegate. AsyncMethodCaller caller = (AsyncMethodCaller) ar.AsyncState; // Call EndInvoke to retrieve the results. string returnValue = caller.EndInvoke(out threadId, ar); Console.WriteLine(quot;The call executed on thread {0}, with return value quot;{1}quot;.quot;, threadId, returnValue); }} 1.4 参考资源 [1] 蔡學鏞 函数指针的进化论(1) http://www.upsdn.net/html/2004-11/32.html [2] 蔡學鏞 函数指针的进化论(2) http://www.upsdn.net/html/2004-11/35.html [3] 蔡學鏞 函数指针的进化论(3) http://www.upsdn.net/html/2004-11/36.html [4] 破宝(percyboy) .NET 事件模型教程 http://blog.joycode.com/percyboy/archive/2005/01/22/43438.aspx 10 [5] MSDN 委托(C#编程指南) [6] MSDN 使用异步方式调用同步方法
  • Java 事件模型 2、 Java 事件模型 2.1 五种事件处理方式 基础源码 import javax.swing.*; public class Demo extends JFrame { JButton btnTest=new JButton(quot;Clickquot;); public Demo() { this.setLayout(null); this.add(btnTest); this.setTitle(quot;Demoquot;); this.setSize(200,100); this.setLocation(400,200); this.setVisible(true); btnTest.setBounds(60, 25, 83, 25); } public static void main(String[] args) { Demo buttondemo = new Demo(); } } 效果图示 第一种方式:自己实现 xxxListener接口,自己承担事件处理对象角色 import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.Color; import javax.swing.*; public class Demo1 extends JFrame implements MouseListener{ JButton btnTest=new JButton(quot;Clickquot;); public Demo1() { this.setLayout(null); this.add(btnTest); this.setTitle(quot;Demo1quot;); this.setSize(200,100); this.setLocation(400,200); this.setVisible(true); btnTest.setBounds(60, 25, 83, 25); 11 btnTest.addMouseListener(this); }
  • Java 事件模型 public void mouseClicked(MouseEvent e) { this.getContentPane().setBackground(Color.RED); } public void mouseEntered(MouseEvent e) { this.getContentPane().setBackground(Color.GREEN); } public void mouseExited(MouseEvent e) { this.getContentPane().setBackground(Color.BLUE); } public void mousePressed(MouseEvent e) { this.getContentPane().setBackground(Color.WHITE); } public void mouseReleased(MouseEvent e) { this.getContentPane().setBackground(Color.BLACK); } public static void main(String[] args) { Demo1 buttondemo = new Demo1(); } } 代码说明 优点:事件处理方法可以直接访问事件源所有属性和方法 缺点:必须实现xxxListener接口中定义的全部方法 第二种方式:定义特定类实现 xxxListener 接口,特定类承担事件处理对象角色 import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.Color; import javax.swing.*; public class Demo2 extends JFrame{ JButton btnTest=new JButton(quot;Clickquot;); public Demo2() { this.setLayout(null); this.add(btnTest); this.setTitle(quot;Demo2quot;); this.setSize(200,100); this.setLocation(400,200); this.setVisible(true); btnTest.setBounds(60, 25, 83, 25); btnTest.addMouseListener(new MyMouseListener2(this)); } public static void main(String[] args) { Demo2 buttondemo = new Demo2(); } } 12 class MyMouseListener2 implements MouseListener{ private Demo2 demo;
  • Java 事件模型 public MyMouseListener2(Demo2 demo){ this.demo=demo; } public void mouseClicked(MouseEvent e) { demo.getContentPane().setBackground(Color.RED); } public void mouseEntered(MouseEvent e) { demo.getContentPane().setBackground(Color.GREEN); } public void mouseExited(MouseEvent e) { demo.getContentPane().setBackground(Color.BLUE); } public void mousePressed(MouseEvent e) { demo.getContentPane().setBackground(Color.WHITE); } public void mouseReleased(MouseEvent e) { demo.getContentPane().setBackground(Color.BLACK); } } 代码说明 优点:事件源与事件处理类独立,分工明确 缺点:必须实现xxxListener接口中定义的全部方法;必须将事件源传递到事件处理类内部,以便访问 第三种方式:继承 xxxAdapter 类 import java.awt.Color; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JButton; import javax.swing.JFrame; public class Demo3 extends JFrame{ JButton btnTest=new JButton(quot;Clickquot;); public Demo3() { this.setLayout(null); this.add(btnTest); this.setTitle(quot;Demo3quot;); this.setSize(200,100); this.setLocation(400,200); this.setVisible(true); btnTest.setBounds(60, 25, 83, 25); btnTest.addMouseListener(new MyMouseListener3(this)); } public static void main(String[] args) { Demo3 buttondemo = new Demo3(); } } 13 class MyMouseListener3 extends MouseAdapter{ private Demo3 demo;
  • Java 事件模型 public MyMouseListener3(Demo3 demo){ this.demo=demo; } public void mouseClicked(MouseEvent e) { demo.getContentPane().setBackground(Color.RED); } } 代码说明 优点:不需要实现xxxListener接口中定义的全部方法 缺点:xxxAdapter类层次代码量增加 第四种方式:内部类(Inner Classes) import java.awt.Color; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JButton; import javax.swing.JFrame; public class Demo4 extends JFrame{ JButton btnTest=new JButton(quot;Clickquot;); public Demo4() { this.setLayout(null); this.add(btnTest); this.setTitle(quot;Demo4quot;); this.setSize(200,100); this.setLocation(400,200); this.setVisible(true); btnTest.setBounds(60, 25, 83, 25); btnTest.addMouseListener(new MyMouseListener4()); } class MyMouseListener4 extends MouseAdapter{ public void mouseClicked(MouseEvent e) { getContentPane().setBackground(Color.RED); } } public static void main(String[] args) { Demo4 buttondemo = new Demo4(); } } 代码说明 优点:事件处理方法可以直接访问事件源所有属性和方法 缺点:事件处理类一般只能为所在事件源类负责 14
  • Java 事件模型 第五种方式:匿名内部类(Anonymous Inner Classes) import java.awt.Color; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JButton; import javax.swing.JFrame; public class Demo5 extends JFrame{ JButton btnTest=new JButton(quot;Clickquot;); public Demo5() { this.setLayout(null); this.add(btnTest); this.setTitle(quot;Demo5quot;); this.setSize(200,100); this.setLocation(400,200); this.setVisible(true); btnTest.setBounds(60, 25, 83, 25); btnTest.addMouseListener(new MouseAdapter(){ public void mouseClicked(MouseEvent e) { getContentPane().setBackground(Color.RED); } }); } public static void main(String[] args) { Demo5 buttondemo = new Demo5(); } } 代码说明 优点:减少类体定义,减少代码量 缺点:匿名内部类不能重复使用,只能使用一次 15
  • Java 事件模型 2.2 自定义 Java 事件 Java 事件机制采用发布者/订阅者模式 (publisher/subscriber) 借助事件源 , (event source) 事件监听器 、 (event listener)和事件消息(Event Object)三类对象实现事件模型。 1)事件消息类 public class TickEvent extends EventObject{ public Object source; //构造方法的参数传递产生事件的事件源 public TickEvent(Object source) { super(source); this.source=source; } public Object getSource() { return source; } public void setSource(Object source) { this.source = source; } } 2)事件监听器接口 public interface TickListener { public void tick(TickEvent event); } 3)事件监听器 public class PluginForClock implements TickListener { public void tick(TickEvent event) { System.out.print(quot;>quot;); } } 4)事件源 public class Clock { //默认时间间隔为1000ms private long interval=1000; //使用线程安全的Vector存储所有事件监听器对象 private Vector<TickListener> listeners=new Vector<TickListener>(); //添加事件订阅 public void addTickListener(TickListener listener){ listeners.add(listener); } //移除事件订阅 public void removeTickListener(TickListener listener){ listeners.remove(listener); 16 }
  • Java 事件模型 protected void doTickEvent(){ //锁定,避免在触发期间有事件被订阅或移除 synchronized (this) { //创建事件状态对象 TickEvent event=new TickEvent(this); //循环触发所有的事件订阅方法 for(int i=0;i<this.listeners.size();i++){ TickListener listener=(TickListener)this.listeners.get(i); listener.tick(event); } } } //模拟闹钟的tick public void tick(){ while(true){ //触发事件 this.doTickEvent(); //中断当前线程interval try { Thread.sleep(interval); } catch (InterruptedException e) { e.printStackTrace(); } } } public long getInterval() { return interval; } public void setInterval(long interval) { this.interval = interval; } } 5)测试类 public class Test { public static void main(String[] args) { Clock clock=new Clock(); clock.setInterval(5000); PluginForClock plugin=new PluginForClock(); clock.addTickListener(plugin); clock.tick(); } } 运行效果 17 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  • Java 事件模型 2.3 Swing 事件调度线程(Event Dispatch Thread, EDT) Swing 不是一个“线程安全”的 API,Swing 的线程安全不是靠自身组件的 API 来保障,而是靠 EventQueue 和 EDT 来保障。Swing 将所有 GUI 请求放入一个事件队列 EventQueue 中,EventQueue 的派发则由事件派发线 程(EDT)来管理,因此 Swing 是一个单线程图形工具包(如下图所示) 。 Swing 开发有两条基本原则:一是所有 GUI 请求都应该在 EDT 中调用,以保证 Swing 的线程安全;二是所有 与 GUI 无关的处理不要交由 EDT 处理,尤其像 I/O 这种耗时的工作。 强烈推荐阅读 使用 SwingWorker 改善 Swing 应用程序 。 2.4 参考资源 [1] Prentice.Hall.Filthy.Rich.Clients.Aug.2007 [2] 深入浅出 Swing 事件分发线程 http://www.blogjava.net/javagui/archive/2008/06/23/EDT.html [3] 使用 SwingWorker 改善 Swing 应用程序 http://blog.sina.com.cn/s/blog_4b6047bc010007so.html [4] Improve Application Performance With SwingWorker in Java SE 6 http://java.sun.com/developer/technicalArticles/javase/swingworker/#Application [5] AWT/SWT/Swing 大比较之一:模型设计与实现 http://blog.sina.com.cn/s/blog_4b6047bc010007ib.html 18
  • DOM 和 JavaScript 事件模型 3、 .NET(C#)事件模型 3.1 C#委托(Delegates in C#) 委托使用步骤 //Step 1. Declare a delegate with the signature of the encapsulated method public delegate void MyDelegate(string input); //Step 2. Define methods that match with the signature of delegate declaration class MyClass1{ public void delegateMethod1(string input){ Console.WriteLine(quot;This is delegateMethod1 and the input to the method is {0}quot;,input); } public void delegateMethod2(string input){ Console.WriteLine(quot;This is delegateMethod2 and the input to the method is {0}quot;,input); } } //Step 3. Create delegate object and plug in the methods class MyClass2{ public MyDelegate createDelegate(){ MyClass1 c2=new MyClass1(); MyDelegate d1 = new MyDelegate(c2.delegateMethod1); MyDelegate d2 = new MyDelegate(c2.delegateMethod2); MyDelegate d3 = d1 + d2; return d3; } } //Step 4. Call the encapsulated methods through the delegate class MyClass3{ public void callDelegate(MyDelegate d,string input){ d(input); } } class Driver{ static void Main(string[] args){ MyClass2 c2 = new MyClass2(); MyDelegate d = c2.createDelegate(); MyClass3 c3 = new MyClass3(); c3.callDelegate(d,quot;Calling the delegatequot;); } } 3.2 C#事件处理(Event Handlers in C#) 非GUI事件处理 //Step 1 Create delegate object public delegate void MyHandler1(object sender,MyEventArgs e); 19 public delegate void MyHandler2(object sender,MyEventArgs e); //Step 2 Create event handler methods
  • DOM 和 JavaScript 事件模型 class A { public const string m_id=quot;Class Aquot;; public void OnHandler1(object sender,MyEventArgs e){ Console.WriteLine(quot;I am in OnHandler1 and MyEventArgs is {0}quot;, e.m_id); } public void OnHandler2(object sender,MyEventArgs e){ Console.WriteLine(quot;I am in OnHandler2 and MyEventArgs is {0}quot;, e.m_id); } //Step 3 create delegates, plug in the handler and register with the object that will fire the events public A(B b) { MyHandler1 d1=new MyHandler1(OnHandler1); MyHandler2 d2=new MyHandler2(OnHandler2); b.Event1 +=d1; b.Event2 +=d2; } } //Step 4 Calls the encapsulated methods through the delegates (fires events) class B { public event MyHandler1 Event1; public event MyHandler2 Event2; public void FireEvent1(MyEventArgs e) { if(Event1 != null){ Event1(this,e); } } public void FireEvent2(MyEventArgs e){ if(Event2 != null){ Event2(this,e); } } } public class MyEventArgs : EventArgs{ public string m_id; } public class Driver2 { public static void Main(){ B b= new B(); A a= new A(b); MyEventArgs e1=new MyEventArgs(); MyEventArgs e2=new MyEventArgs(); e1.m_id =quot;Event args for event 1quot;; e2.m_id =quot;Event args for event 2quot;; b.FireEvent1(e1); b.FireEvent2(e2); } } 20
  • DOM 和 JavaScript 事件模型 GUI事件处理 //MyForm.Designer.cs namespace DotNetEvents { partial class MyForm { /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// 清理所有正在使用的资源。 /// </summary> /// <param name=quot;disposingquot;>如果应释放托管资源,为 true;否则为 false。</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要 /// 使用代码编辑器修改此方法的内容。 /// </summary> private void InitializeComponent() { this.btnClick = new System.Windows.Forms.Button(); this.SuspendLayout(); // // btnClick // this.btnClick.Location = new System.Drawing.Point(65, 20); this.btnClick.Name = quot;btnClickquot;; this.btnClick.Size = new System.Drawing.Size(75, 23); this.btnClick.TabIndex = 0; this.btnClick.Text = quot;Clickquot;; this.btnClick.UseVisualStyleBackColor = true; this.btnClick.Click += new System.EventHandler(this.btnClick_Click); // // MyForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 21 this.ClientSize = new System.Drawing.Size(205, 55); this.Controls.Add(this.btnClick);
  • DOM 和 JavaScript 事件模型 this.Name = quot;MyFormquot;; this.Text = quot;MyFormquot;; this.ResumeLayout(false); } #endregion private System.Windows.Forms.Button btnClick; } } //MyForm.cs namespace DotNetEvents { public partial class MyForm : Form { public MyForm() { InitializeComponent(); } private void btnClick_Click(object sender, EventArgs e) { MessageBox.Show(quot;You Click Me!quot;,quot;Tipquot;,MessageBoxButtons.OK,MessageBoxIcon.Information); } public static void Main() { Application.Run(new MyForm()); } } } 3.3 发布符合.NET Framework 准则的事件 .NET Framework 类库设计指南:事件命名指南 1) 事件委托名称应以 EventHandler 为结尾。 2) 事件委托的“规格”应该是两个参数: 第一个参数是 object 类型的 sender,代表发出事件通知的对象(代 码中一般是 this 关键字)。第二个参数 e,应该是 EventArgs 类型或者从 EventArgs 继承而来的类型; 事件参数类型,应从 EventArgs 继承,名称应以 EventArgs 结尾。应该将所有想通过事件、传达到外界 的信息,放在事件参数 e 中。 3) 一般的,只要类不是密封(C# 中的 sealed)的,或者说此类可被继承,应该为每个事件提供一个 pr otected 并且是可重写(C# 用 virtual)的 OnXxxx 方法:该方法名称,应该是 On 加上事件的名称;只 有一个事件参数 e;一般在该方法中进行 null 判断,并且把 this/Me 作为 sender 执行事件委托;在需 要发出事件通知的地方,应调用此 OnXxxx 方法。对于此类的子类,如果要改变发生此事件时的行为,应 重写 OnXxxx 方法;并且在重写时,一般情况下应调用基类的此方法(C# 里的 base.OnXxxx,VB.NET 用 MyBase.OnXxxx)。 // Define a class to hold custom event info public class CustomEvents:EventArgs{ private string message; 22 public CustomEvents(string s) {
  • DOM 和 JavaScript 事件模型 message = s; } public string Message { get { return message; } set { message = value; } } } // Class that publishes an event class Publisher{ // Declare the event using EventHandler<T> public event EventHandler<CustomEvents> RaiseCustomEvent; // Write some code that does something useful here // then raise the event. You can also raise an event // before you execute a block of code. public void DoSomething(){ OnRaiseCustomEvent(new CustomEvents(quot;Did somethingquot;)); } // Wrap event invocations inside a protected virtual method // to allow derived classes to override the event invocation behavior protected virtual void OnRaiseCustomEvent(CustomEvents e){ // Make a temporary copy of the event to avoid possibility of // a race condition if the last subscriber unsubscribes // immediately after the null check and before the event is raised. EventHandler<CustomEvents> handler = RaiseCustomEvent; // Event will be null if there are no subscribers if (handler != null){ // Format the string to send inside the CustomEventArgs parameter e.Message += String.Format(quot; at {0}quot;, DateTime.Now.ToString()); // Use the () operator to raise the event. handler(this, e); } } } //Class that subscribes to an event class Subscriber{ private string id; public Subscriber(string ID, Publisher pub){ id = ID; // Subscribe to the event using C# 2.0 syntax pub.RaiseCustomEvent += HandlerCustomEvent; } // Define what actions to take when the event is raised. 23 void HandlerCustomEvent(object sender, CustomEvents e){ Console.WriteLine(id+quot; received this message:{0}quot;,e.Message);
  • DOM 和 JavaScript 事件模型 } } class Program{ static void Main(string[] args){ Publisher pub = new Publisher(); Subscriber sub1 = new Subscriber(quot;sub1quot;, pub); Subscriber sub2 = new Subscriber(quot;sub2quot;, pub); // Call the method that raises the event. pub.DoSomething(); // Keep the console window open Console.WriteLine(quot;Press Enter to close this window.quot;); Console.ReadLine(); } } 3.4 应用范例 任务描述:较长时间任务进度指示 Worker 类中有一个可能需要较长时间才能完成的方法 DoLongTimeTask,单击以下窗体“Start”按钮后, 开始执行该方法。在没有进度指示的情况下,界面长时间的无响应,往往会被用户认为是程序故障或者“死 机”,而实际上,你的工作正在进行还没有结束。如何进行较长时间任务进度指示呢? 高耦合解决方案 窗体类: public partial class Demo2 : Form { //定义进度状态条 private StatusBar progressBar = new StatusBar(); public Demo2() { InitializeComponent(); //添加进度状态条 this.Controls.Add(progressBar); } private void button1_Click(object sender, EventArgs e) { 24 Worker worker = new Worker(this); worker.DoLongTimeTask();
  • DOM 和 JavaScript 事件模型 } public void setStatus(string info) { this.progressBar.Text = info; } } Worker类: class Worker { private const int MAX = 10; private Demo2 demo; public Worker() { } public Worker(Demo2 demo) { this.demo = demo; } public void DoLongTimeTask() { int i; bool t = false; for (i = 0; i <= MAX; i++) { Thread.Sleep(1000); t = !t; double rate = (double)i / (double)MAX; string info=string.Format(@quot;已完成{0:P2}quot;,rate); demo.setStatus(info); } } } 代码说明 Windows窗体 Demo2 中单击“Start”按钮后,初始化 Worker 类的一个新实例,并执行它的 DoLongTimeTask 方法。但 你应该同时看到,Demo2也赋值给 Worker 的一个属性,在 Worker 执行 DoLongTimeTask 方法时,通过这个属性刷新 Demo2和 Worker 之间相互粘在一起:Demo2依赖于 Worker 类(因为它单击按钮后要实例化 Worker), Demo2的状态栏。 Worker 类也依赖于 Demo2(因为它在工作时,需要访问 Demo2)。这二者之间形成了高度耦合。高度耦合同样不利于 代码重用,你仍然无法在另一个窗体里使用 Worker 类,代码灵活度大为降低。正确的设计原则应该是努力实现低耦合: 如果 Demo2必须依赖于 Worker 类,那么 Worker 类就不应该再反过来依赖于 Demo2。 使用事件模型降低耦合 窗体类: public partial class Demo4 : Form { //定义进度状态条 private StatusBar progressBar = new StatusBar(); 25 public Demo4() {
  • DOM 和 JavaScript 事件模型 InitializeComponent(); //添加进度状态条 this.Controls.Add(progressBar); } private void button1_Click(object sender, EventArgs e) { Worker worker = new Worker(); worker.RateReport += new Worker.RateReportEventHandler(this.RateReportEventHandler); worker.DoLongTimeTask(); } private void StartWorkEventHandler(int totalUnits){} private void EndWorkEventHandler() {} private void RateReportEventHandler(object sender,Worker.RateReportEventArgs e) { this.progressBar.Text = string.Format(quot;已完成{0:P0}......quot;, e.Rate); } } Worker及相关类: class Worker { private const int MAX = 10000; public class StartWorkEventArgs : EventArgs { private int totalUnits; public int TotalUnits { get { return totalUnits; } } public StartWorkEventArgs(int totalUnits) { this.totalUnits = totalUnits; } } public class RateReportEventArgs : EventArgs { private double rate; public double Rate { get { return rate; } } public RateReportEventArgs(double rate) { this.rate = rate; } } public delegate void StartWorkEventHandler(object sender,StartWorkEventArgs e); public delegate void RateReportEventHandler(object sender,RateReportEventArgs e); public event StartWorkEventHandler StartWork; public event EventHandler EndWork; public event RateReportEventHandler RateReport; protected virtual void OnStartWork(StartWorkEventArgs e) { 26 if (StartWork != null) { StartWork(this, e);
  • DOM 和 JavaScript 事件模型 } } protected virtual void OnEndWork(EventArgs e) { if (EndWork != null) { EndWork(this, e); } } protected virtual void OnRateReport(RateReportEventArgs e) { if (RateReport != null) { RateReport(this, e); } } public Worker() {} public void DoLongTimeTask() { int i; bool t = false; OnStartWork(new StartWorkEventArgs(MAX)); for (i = 0; i <= MAX; i++) { Thread.Sleep(1); t = !t; double rate = (double)i / (double)MAX; OnRateReport(new RateReportEventArgs(rate)); } OnEndWork(EventArgs.Empty); } } 代码说明 Worker 类的代码和这个 Windows Form Demo4 没有依赖关系。Worker 类可以单独存在,可以被重复应用到不同的地方。 事件模型委托回调 VS 面向对象接口回调 窗体类: public partial class Demo8 : Form { //定义进度状态条 private StatusBar progressBar = new StatusBar(); public Demo8() { InitializeComponent(); //添加进度状态条 this.Controls.Add(progressBar); } private void button1_Click(object sender, EventArgs e) 27 { Worker worker = new Worker();
  • DOM 和 JavaScript 事件模型 worker.Report = new MyWorkerReport(this); worker.DoLongTimeTask(); } private void StartWorkEventHandler(int totalUnits){} private void EndWorkEventHandler() { } private class MyWorkerReport :WorkerReportAdapter { private Demo8 parent; public MyWorkerReport(Demo8 parent) { this.parent = parent; } public override void OnRateReport(double rate) { parent.progressBar.Text = String.Format(quot;已经完成 {0:P0} ...quot;,rate); } } } Worker及相关类: public interface IWorkerReport { void OnStartWork(int totalUnits); void OnEndWork(); void OnRateReport(double rate); } // 这个 Demo 参考了 Java 里惯用的设计方法, // 此 Adapter 类实现了 IWorkerReport 接口, // 使用时只需要从此继承,重写需要的方法, // 其他不需要的方法,不必就去管它。 public class WorkerReportAdapter : IWorkerReport { public virtual void OnStartWork(int totalUnits) {} public virtual void OnEndWork(){} public virtual void OnRateReport(double rate) {} } class Worker { private const int MAX = 1000; private IWorkerReport report = null; public IWorkerReport Report { set { report = value; } } public Worker() { 28 }
  • DOM 和 JavaScript 事件模型 public Worker(IWorkerReport report) { this.report = report; } public void DoLongTimeTask() { int i; bool t = false; if (report != null) { report.OnStartWork(MAX); } for (i = 0; i <= MAX; i++) { Thread.Sleep(1); t = !t; double rate = (double)i / (double)MAX; if (report != null) { report.OnRateReport(rate); } } if (report != null) { report.OnEndWork(); } } } 代码说明 事件模型委托回调较于向对象接口回调(类似Java事件模型实现)更简洁直接有效! 3.5 参考资源 [1] 破宝(percyboy) .NET 事件模型教程 http://blog.joycode.com/percyboy/archive/2005/01/22/43438.aspx [2] COM 线程模型详解 http://www.builder.com.cn/2007/1020/568247.shtml [3] MSDN (C#编程指南)发布符合 .NET Framework 准则的事件 [4] MSDN (Windows 窗体编程)对 Windows 窗体控件进行线程安全调用 29
  • DOM 和 JavaScript 事件模型 4、 DOM 和 JavaScript 事件模型 4.1 异步回调 vs.多线程 使用线程不断检查更新的替代方案是使用异步回调,这正是 JavaScript 所使用的。JavaScript 里的事件系统是 相当独特的,它完全地异步工作而根本不使用线程。这意味着你程序中的所有代码将依赖于其它的动作——比如用 户的点击或者页面的加载——来触发。显然,异步回调比多线程更为优雅。 第一种方案 模拟线程的 JavaScript 伪码及其示意图 // 注意:这段代码不能生效! // 在页面加载之前,持续地检查 while ( ! window.loaded() ) { } //页面现在已经加载了,于是开始做些事情 document.getElementById(quot;bodyquot;).style.border = quot;1px solid #000quot;; 第二种方案 JavaScript 里的异步回调及其示意图 //注册一个页面加载完成时被调用的函数 window.onload = loaded; //页面加载完成时被调用的函数 function loaded() { //页面现己加载完成了,于是干点事情 document.getElementById(quot;bodyquot;).style.border = quot;1px solid #000quot;; } 30
  • DOM 和 JavaScript 事件模型 4.2 DOM 事件流(DOM Event Flow) 在 W3C DOM Level2 和 Level3 的事件模型规范中,事件在 DOM 树中的传播过程(从根节点到目标节点)被 分为了三个阶段:捕捉(Capture) 、冒泡(Bubbling) 、目标(Target) 。DOM 事件触发后从 Root 根节点自上而下 传播事件到 target 对象 (此过程称为 Capture Phase) 然后传播事件给 target 对象 , (此过程称为 Target Phase), 最后在自下而上的传播事件(此过程称为 Bubbling Phase) 。 目前对于捕捉和冒泡事件流,Mozila Firefox 都支持,而 IE 仅支持冒泡事件流。 验证案例(效果图及源码如下) : 31
  • DOM 和 JavaScript 事件模型 <div id=quot;panelquot; style=quot;margin:0 auto; width:500px; height:500px; text-align:centerquot;> <div id=quot;Aquot; style=quot;background-color:#b0b0b0;border:1px solid #000000;padding:5px;quot;> DIV A <div id=quot;Bquot; style=quot;background-color:#d0d0d0;border:1px solid #000000;padding:5px;quot;> DIV B <div id=quot;Cquot; style=quot;background-color:#f0f0f0;border:1px solid #000000;padding:5px;quot;> DIV C </div> </div> </div> <form action=quot;quot; method=quot;postquot;> <p> <textarea id=quot;traceOutquot; rows=quot;7quot; cols=quot;50quot;></textarea> <input class=quot;buttonquot; type=quot;resetquot; value=quot;Clearquot; /> </p> </form> </div> <script type=quot;text/javascriptquot;> //<![CDATA[ if (document.addEventListener) { document.getElementById(quot;Aquot;).addEventListener(quot;clickquot;, traceEvent, true); document.getElementById(quot;Bquot;).addEventListener(quot;clickquot;, traceEvent, true); document.getElementById(quot;Cquot;).addEventListener(quot;clickquot;, traceEvent, true); document.getElementById(quot;Aquot;).addEventListener(quot;clickquot;, traceEvent, false); document.getElementById(quot;Bquot;).addEventListener(quot;clickquot;, traceEvent, false); document.getElementById(quot;Cquot;).addEventListener(quot;clickquot;, traceEvent, false); } else { document.getElementById(quot;Aquot;).onclick = traceEvent; document.getElementById(quot;Bquot;).onclick = traceEvent; document.getElementById(quot;Cquot;).onclick = traceEvent; } function traceEvent(event) { var phase, source; if (window.event) { target = window.event.srcElement.id; current = this.id if (this == window.event.srcElement) phase = quot;AT TARGETquot;; else phase = quot;BUBBLEquot;; } else { if (event.target.tagName){ target = event.target.id; } else{ 32 target = quot;[Text]quot;; }
  • DOM 和 JavaScript 事件模型 current = event.currentTarget.id; if (event.eventPhase == Event.CAPTURING_PHASE) phase = quot;CAPTUREquot;; else if (event.eventPhase == Event.BUBBLING_PHASE) phase = quot;BUBBLEquot;; else if (event.eventPhase == Event.AT_TARGET) phase = quot;AT TARGETquot;; } document.getElementById(quot;traceOutquot;).value += quot;Target = quot; + target + quot;; Phase = quot; + phase + quot;; Current = quot; + current + quot;;nquot;; } //]]> </script> 33
  • DOM 和 JavaScript 事件模型 4.3 绑定事件监听器(Event handler registration) 第一种方式 内联事件绑定 <!--居中显示的一个DIV,当鼠标移上去时背景色为红色,鼠标移出时变为蓝色--> <div id=quot;div1quot; style=quot;margin:0 auto; border:#000000 solid 1px; width:100px; height:100px;quot; onmouseover=quot;this.style.backgroundColor='red';quot; onmouseout =quot;this.style.backgroundColor='blue';quot;> </div> 优点 1. 最大程度的跨浏览器兼容性 2. this 关键字指向当前的元素 缺点 破坏了非侵入脚本原则 1. 只作用于事件冒泡,而非捕获和冒泡 2. 只能为一个元素绑定一个事件处理函数,一个新的事件处理函数会覆盖原来的事件处理函数 3. event 对象参数只在非 IE 浏览器上有效 4. 第二种方式 传统事件绑定 <!--居中显示的一个层,当鼠标移上去时背景色为红色,鼠标移出时变为蓝色--> <div id=quot;div2quot; style=quot;margin:0 auto; border:#000000 solid 1px; width:100px; height:100px;quot;></div> <script type=quot;text/javascriptquot;> document.getElementById(quot;div2quot;).onmouseover=function(){ this.style.backgroundColor='red'; } document.getElementById(quot;div2quot;).onmouseout=function(){ this.style.backgroundColor='blue'; } </script> 优点 1. 遵循非侵入脚本原则 2. 最大程度的跨浏览器兼容性 3. this 关键字指向当前的元素 缺点 1. 只作用于事件冒泡,而非捕获和冒泡 2. 只能为一个元素绑定一个事件处理函数,一个新的事件处理函数会覆盖原来的事件处理函数 3. event 对象参数只在非 IE 浏览器上有效 34
  • DOM 和 JavaScript 事件模型 第三种方式 DOM 绑定:W3C <!--居中显示层,当鼠标移上时背景色为红色,鼠标移出时变为蓝色,鼠标移动时显示位置坐标--> <div id=quot;div3quot; style=quot;margin:0 auto; border:#000000 solid 1px; width:100px; height:100px;quot;></div> <script type=quot;text/javascriptquot;> document.getElementById(quot;div3quot;).addEventListener(quot;mouseoverquot;,function(){ this.style.backgroundColor='red'; },false); document.getElementById(quot;div3quot;).addEventListener(quot;mouseoutquot;,function(){ this.style.backgroundColor='blue'; },false); document.getElementById(quot;div3quot;).addEventListener(quot;mousemovequot;,function(e){ this.innerHTML=quot;(quot;+e.pageX+quot;,quot;+e.pageY+quot;)quot;; },false); </script> 优点 1. 遵循非侵入脚本原则 2. 支持事件处理的冒泡和捕获阶段。事件的阶段通过设置 addEventListener 的最后一个参数为 false(指示冒泡)或 true(指示捕获)来切换 3. 在事件处理函数内部,this 关键字引用当前元素 4. 事件对象总是作为事件处理函数的第一个参数被提供 5. 可以绑定任意多个函数到一个元素上,而不会覆盖先前所绑定的 缺点 W3C 的为 DOM 元素绑定事件处理函数的方法是这方面唯一真正的标准方式。除了 IE,所有其它的现 代浏览器都支持这一事件绑定的方式。 第四种方式 DOM 绑定:IE <!--居中显示层,当鼠标移上时背景色为红色,鼠标移出时变为蓝色,鼠标移动时显示位置坐标--> <div id=quot;div4quot; style=quot;margin:0 auto; border:#000000 solid 1px; width:100px; height:100px;quot;></div> <script type=quot;text/javascriptquot;> document.getElementById(quot;div4quot;).attachEvent(quot;onmouseoverquot;,function(){ var target=window.event.srcElement; target.style.backgroundColor='red'; }); document.getElementById(quot;div4quot;).attachEvent(quot;onmouseoutquot;,function(){ var target=window.event.srcElement; target.style.backgroundColor='blue'; }); document.getElementById(quot;div4quot;).attachEvent(quot;onmousemovequot;,function(e){ var e=window.event; var x=e.clientX+document.body.scrollLeft; var y=e.clientY+document.body.scrollTop; var target=window.event.srcElement; target.innerHTML=quot;(quot;+x+quot;,quot;+y+quot;)quot;; 35 }); </script>
  • DOM 和 JavaScript 事件模型 优点 1. 遵循非侵入脚本原则 2. 可以绑定任意多个函数到一个元素上,而不会覆盖先前所绑定的 缺点 IE 只支持事件的冒泡阶段 1. 事件监听函数内部的 this 关键字指向 window 对象,而非当前函数(这是 IE 的巨大败笔) 2. 事件对象只能从 widnow.event 得到 3. 事件名称必须形如 onxxxx——比如,要使用quot;onclickquot;而不能只是quot;clickquot; 4. 对于非 IE 平台,你必须使用 W3C 的 addEventListener 5. 第五种方式 原始通用 addEvent 和 removeEvent function addEvent(element,type,handler){ //为每一个事件处理函数分派一个唯一ID if(!handler.$$guid){ handler.$$guid=addEvent.guid++; } //为元素的事件类型创建一个哈希表 if(!element.events){ element.events={}; } //为每一个quot;元素/事件quot;对创建一个事件处理程序哈希表 var handlers=element.events[type]; if(!handlers){ handlers=element.events[type]={}; //存储存在的事件处理函数(如果有) if(element[quot;onquot;+type]){ handlers[0]=element[quot;onquot;+type]; } } //将事件处理函数存入哈希表 handlers[handler.$$guid]=handler; //指派一个全局事件处理函数来做所有工作 element[quot;onquot;+type]=handleEvent; } //用来创建唯一的ID计数器 addEvent.guid=1; function removeEvent(element,type,handler){ if(element.events && element.events[type]){ delete element.events[type][handler.$$guid]; } } function handleEvent(event){ var returnValue=true; //抓获事件对象(IE实用全局事件对象) event=event||fixEvent(window.event); 36
  • DOM 和 JavaScript 事件模型 //取得事件处理函数的哈希表引用 var handlers=this.events[event.type]; //执行每一个处理函数 for(var i in handlers){ this.$$handleEvent = handlers[i]; if(this.$$handleEvent(event)==false){ returnValue=false; } } return returnValue; } //为IE事件对象添加一些quot;缺失quot;的函数 function fixEvent(event){ //添加标准W3C方法 event.preventDefault=fixEvent.preventDefault; event.stopPropagation=fixEvent.stopPropagation; return event; } fixEvent.preventDefault=function(){ this.returnValue=false; } fixEvent.stopPropagation=function(){ this.cancelBubble=true; } 优点 遵循非侵入脚本原则 1. 它可以在所有浏览器上工作,甚至是很老的不被支持的浏览器 2. this 关键字对所有的绑定的函数可用,指向当前元素 3. 统一了浏览器特有的阻止浏览器默认动作和停止事件冒泡的函数 4. 不管是哪种浏览器,事件对象总是作为第一个参数传给处理函数 5. 缺点 只能工作于冒泡阶段(因为它在底层使用了传统事件绑定方法) 37
  • DOM 和 JavaScript 事件模型 4.4 Event Delegation VS. Event Handling <html> <head> <meta http-equiv=quot;Content-Typequot; content=quot;text/html; charset=gb2312quot; /> <title>Delegation</title> </head> <body onload=quot;init()quot;> <table width=quot;413quot; border=quot;0quot; align=quot;centerquot;> <tr><td> <ol id=quot;handlertestquot;> <li id=quot;handlertest1quot;>Handlers Test</li> <li id=quot;handlertest2quot;>Handlers Test</li> <li id=quot;handlertest3quot;>Handlers Test</li> <li id=quot;handlertest4quot;>Handlers Test</li> </ol> </td><td> <ol id=quot;delegationtestquot;> <li>Delegation Test</li> <li>Delegation Test</li> <li>Delegation Test</li> <li>Delegation Test</li> </ol> </td></tr><tr><td colspan=quot;2quot; align=quot;centerquot;> <input type=quot;buttonquot; name=quot;btnaddquot; value=quot;添加新项quot; onclick=quot;addNewItem()quot;/> </td></tr> </table> <script type=quot;text/javascriptquot;> function init(){ //Handlers Test for(var i=1;i<5;i++){ document.getElementById(quot;handlertestquot;+i).onmouseover=function(){ this.style.backgroundColor=quot;#FFCC33quot;; }; document.getElementById(quot;handlertestquot;+i).onmouseout=function(){ this.style.backgroundColor=quot;#FFFFFFquot;; }; } //Delegation Test document.getElementById(quot;delegationtestquot;).onmouseover=function(e){ var targetNode; if(e){ targetNode=e.target; //Firefox }else{ targetNode=window.event.srcElement; //IE } 38
  • DOM 和 JavaScript 事件模型 if(targetNode.tagName==quot;LIquot;){ targetNode.style.backgroundColor=quot;#FFCC33quot;; } }; document.getElementById(quot;delegationtestquot;).onmouseout=function(e){ var targetNode; if(e){ targetNode=e.target; //Firefox }else{ targetNode=window.event.srcElement; //IE } if(targetNode.tagName==quot;LIquot;){ targetNode.style.backgroundColor=quot;#FFFFFFquot;; } }; } var startIndex=4; function addNewItem(){ var handlerObj=document.getElementById(quot;handlertestquot;); var delegationObj=document.getElementById(quot;delegationtestquot;); startIndex++; //Handling handlerObj.innerHTML+=quot;<li id='handlertestquot;+(startIndex)+quot;'>New Item</li>quot;; //必须重新设置事件绑定,因为innerHTML不会返回之前设置的事件绑定,否则事件失效 for(var i=1;i<=startIndex;i++){ document.getElementById(quot;handlertestquot;+i).onmouseover=function(){ this.style.backgroundColor=quot;#FFCC33quot;; }; document.getElementById(quot;handlertestquot;+i).onmouseout=function(){ this.style.backgroundColor=quot;#FFFFFFquot;; }; } //Delegation //不需要重新事件绑定 delegationObj.innerHTML+=quot;<li id='delegationtestquot;+(startIndex)+quot;'>New Item</li>quot;; } </script> </body> Event Handling </html> 1. 容易理解,不需要过多解释 2. 为每个元素单独绑定事件,效率较低 3. 添加新项之后需要重新进行事件绑定,因为之前事件绑定失效 Event Delegation 1. 充分利用 DOM 事件的冒泡事件流特性 39 2. 事件绑定代码简洁高效,可以根据不同事件源采取不同事件处理 3. 大型应用中事件绑定有效解决之道,特别是对于动态加载 DOM 元素的事件绑定
  • DOM 和 JavaScript 事件模型 4.5 参考资源 [1] Peter-Paul Koch 《PPK on JavaScript》 [2] Peter-Paul Koch http://quirksmode.org [3] John Resig 《Pro JavaScript Techniques》 [4] Christian Heilmann《Beginning JavaScript with DOM Scripting and Ajax》 [5] Christian Heilmann http://icant.co.uk/sandbox/eventdelegation/ [6] Douglas Crockford《JavaScript: The Good Parts》 [7] The DOM Event Model http://www.brainjar.com/dhtml/events [8] 同时支持三种事件模型(中文)http://developer.apple.com.cn/internet/webcontent/eventmodels.html [9] 同时支持三种事件模型(英文)http://developer.apple.com/internet/webcontent/eventmodels.html [10] W3C DOM Level2 http://www.w3.org/TR/DOM-Level-2-Events/ [11] W3C DOM Level3 http://www.w3.org/TR/DOM-Level-3-Events/ 40
  • Flex 和 ActionScript 3 事件模型 5、 Flex 和 ActionScript 3 事件模型 AS3.0 的事件机制基于 DOM3 事件模型。AS1.0 和 2.0 有多种事件处理方式,但 AS 3.0 的事件机制采用单 一方式,全部采用 addEventListener 方法来注册监听器,且监听器必须是函数或方法,监听器的作用域和监听器 所在对象的作用域一致。 5.1 概念和术语 1. 调度:通知事件侦听器发生了事件。 2. 事件:对象可以通知其它对象它所发生的情况。 3. 事件流:如果显示列表中的对象(屏幕上显示的对象)发生事件,则会向包含该对象的所有对象通知此事件, 并依次通知其事件侦听器。此过程从舞台开始,并在显示列表中一直进行到发生事件的实际对象,然后再返 回到舞台。此过程称为事件流。 4. 事件对象:此对象包含发生的特定事件的相关信息,当调度事件时,此信息将被发送到所有侦听器。 5. 事件目标:实际调度事件的对象。例如,如果用户单击位于 Sprite (位于舞台内)内的按钮,则所有这些 对象将调度事件,但事件目标是指实际发生事件的对象,此处指单击的按钮。 6. 侦听器:对象或在对象中注册其自身的函数,用于指示发生特定事件时应通知它。 7. 显示对象:ActionScript 3.0 的 flash.display 包中包括可在 Flash Player 或 AIR 中显示的可视对象的类。 下图说明了这些核心显示对象类的子类关系。请注意,其中某些类,尤其是 StaticText、TextField 和 Video 类,不在 flash.display 包中,但它们仍然是从 DisplayObject 类继承的。 8. 显示列表:使用 ActionScript 3.0 构建的每个应用程序都有一个由显示对象构成的层次结构,这个结构称为 “ 显示列表” ,如下所示。显示列表包含应用程序中的所有可视元素。 41
  • Flex 和 ActionScript 3 事件模型 5.2 事件对象(Event objects) 在 ActionScript 3.0 中,每个事件都由一个事件对象表示。事件对象是 Event 类或其某个子类的实例。事件对 象不但存储有关特定事件的信息,还包含便于操作事件对象的方法。Event 类的属性和方法可以参见 Adobe® Flex ™ 3.2 语言参考 http://livedocs.adobe.com/flex/3_cn/langref/flash/events/Event.html ,此处不再敷述。 5.3 事件流(The event flow) 创建事件对象之后,Flash Player 或 AIR 即“ 调度” 该事件对象,这意味着将该事件对象传递给作为事件 目标的对象。作为被调度事件对象的目标的对象称为“事件目标” 。但是,如果事件目标在显示列表中,则在显示 列表层次结构中将事件对象向下传递,直到到达事件目标为止。在某些情况下,该事件对象随后会沿着相同路线在 显示列表层次结构中向上“ 冒泡” 回去。显示列表层次结构中的这种遍历行为称为“ 事件流” 。 只要发生事件,Flash Player 或 AIR 就会调度事件对象。如果事件目标不在显示列表中,则 Flash Player 或 AIR 将事件对象直接调度到事件目标。例如,Flash Player 将 progress 事件对象直接调度到 URLStream 对象。 但是,如果事件目标在显示列表中,则 Flash Player 将事件对象调度到显示列表,事件对象将在显示列表中穿行, 直到到达事件目标。 从概念上来说,事件流分为三部分。第一部分称为捕获阶段,该阶段包括从舞台到目标节点的父节点范围内的 所有节点。第二部分称为目标阶段,该阶段仅包括目标节点。第三部分称为冒泡阶段。冒泡阶段包括从目标节点的 父节点返回到舞台的行程中遇到的节点,如下图所示。 42
  • Flex 和 ActionScript 3 事件模型 不过,并非每个事件对象都参与事件流的所有三个阶段。某些类型的事件(例如 enterFrame 和 init 类型的事 件)会直接调度到目标节点,并不参与捕获阶段和冒泡阶段。其它事件可能以不在显示列表中的对象为目标,例如 调度到 Socket 类的实例的事件。这些事件对象也将直接流至目标对象,而不参与捕获和冒泡阶段。 5.4 事件监听器(Event listeners) 您可以使用事件侦听器“ 侦听”代码中的事件对象。 “事件侦听器”是您编写的用于响应特定事件的函数或方 法。若要确保您的程序响应事件,必须将事件侦听器添加到事件目标,或添加到作为事件对象事件流的一部分的任 何显示列表对象。 在 DOM 事件模型中,事件侦听器和侦听器函数之间有一个明显的不同:即事件侦听器是实现 EventListener 接口的类的实例,而侦听器是该类的名为 handleEvent() 的方法。在 DOM 事件模型中,您注册的是包含侦听器 函数的类实例,而不是实际的侦听器函数。在 ActionScript 3.0 事件模型中,事件侦听器和侦听器函数之间没有区 别。ActionScript 3.0 没有 EventListener 接口,侦听器函数可以在类外部定义,也可以定义为类的一部分。此外, 无需将侦听器函数命名为 handleEvent() — 可以将它们命名为任何有效的标识符。在 ActionScript 3.0 中,您注 册的是实际侦听器函数的名称。 5.5 禁止默认行为 开发人员通常负责编写响应事件的代码。但在某些情况下,行为通常与某一事件关联,使得 Flash Player 或 AIR 会自动执行该行为,除非开发人员添加了取消该行为的代码。由于 Flash Player 或 AIR 会自动表现该行为, 因此这类行为称为默认行为。 例如,当用户在 TextField 对象中输入文本时,普遍期待文本显示在该 TextField 对象中,因此该行为被内置 到 Flash Player 和 AIR 中。如果您不希望该默认行为发生,可以使用新的事件处理系统来取消它。当用户在 TextField 对象中输入文本时,Flash Player 或 AIR 会创建 TextEvent 类的实例以表示该用户输入。若要阻止 Flash Player 或 AIR 显 示 TextField 对 象 中 的 文 本 , 必 须 访 问 该 特 定 TextEvent 实 例 并 调 用 该 实 例 的 preventDefault() 方法。 并非所有默认行为都可以阻止。例如,当用户双击 TextField 对象中的单词时, Flash Player 和 AIR 会生成 一个 MouseEvent 对象。无法阻止的默认行为是:鼠标点击的单词突出显示。 许多类型的事件对象没有关联的默认行为。例如,当建立网络连接时, Flash Player 调度一个连接事件对象,但 没有与该对象关联的默认行为。Event 类及其子类的 API 文档列出了每一类型的事件,并说明所有关联的默认行 为,以及是否可以阻止该行为。 默认行为仅与由 Flash Player 或 AIR 所调度的事件对象关联, 但通过 ActionScript 以编程方式调度的事件对 象则不存在默认行为。了解这一点很重要。例如,可以使用 EventDispatcher 类的方法来调度类型为 textInput 的 事件对象,但该事件对象没有关联的默认行为。也就是说, Flash Player 和 AIR 不会因为您以编程方式调度了 textInput 事件而在 TextField 对象中显示字符。 43
  • Flex 和 ActionScript 3 事件模型 5.6 创建自定义事件类型 IEventDispatcher 接口定义用于添加或删除事件侦听器的方法,检查是否已注册特定类型的事件侦听器,并调 度事件。 EventDispatcher 类实现 IEventDispatcher 接口,并且是 DisplayObject 类的基类。 EventDispatcher 类允 许显示列表上的任何对象都是一个事件目标,同样允许使用 IEventDispatcher 接口的方法。 通常,使用户定义的类能够调度事件的最简单方法是扩展 EventDispatcher。如果无法扩展(即,如果该类已 经扩展了另一个类) ,则可以实现 IEventDispatcher 接口,创建 EventDispatcher 成员,并编写一些简单的挂钩, 将调用连接到聚合的 EventDispatcher 中。 1. 自定义事件类型 可以创建 Event 的子类来满足自己的实际开发需要,但注意,创建 Event 子类时,必须覆盖 clone() 和 toString() 方法以实现特定于该子类的功能。 自定义事件类型 TickEvent import flash.events.Event; /** * 自定义事件类型 */ public class TickEvent extends Event { /** * TickEvent类型的名称 */ public static const TICK:String=quot;tickquot;; /** * 可以通过此Event对象传递给Event handler的文本消息 */ public var message:String; /** * 构造方法 * @param message * @param type * @param bubbles * @param cancelable */ public function TickEvent(message:String,type:String=TICK, bubbles:Boolean=false, cancelable:Boolean=false){ super(type, bubbles, cancelable); this.message=message; } /** * 创建并返回一个当前类实例的拷贝 * @return 一个当前类实例的拷贝 */ public override function clone():Event{ return new TickEvent(message); } 44 /** * 返回一个字符串包含当前实例的所有属性
  • Flex 和 ActionScript 3 事件模型 * @return 一个字符串包含当前实例的所有属性 */ public override function toString():String{ return formatToString(quot;TickEventquot;, quot;typequot;, quot;bubblesquot;, quot;cancelablequot;, quot;eventPhasequot;, quot;messagequot;); } } 2. 使用户自定义的类能够调度事件 第一种方式:扩展 EventDispatcher import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.events.TimerEvent; import flash.utils.Timer; [Event(name=quot;tickquot;,type=quot;cn.qdqn.clock.TickEventquot;)] public class Clock1 extends EventDispatcher { /** * 使用Timer作为时间晶振 */ private var timer:Timer; /** * 初始化timer */ public function initTimer():void{ timer=new Timer(1000,1000); timer.addEventListener(TimerEvent.TIMER,timerHandler); timer.start(); } /** * 构造方法 * @param target */ public function Clock1(target:IEventDispatcher=null) { super(target); initTimer(); } /** * timerHandler */ public function timerHandler(event:TimerEvent):void{ var tickEvent:TickEvent=new TickEvent(quot;>quot;); this.dispatchEvent(tickEvent); 45 } }
  • Flex 和 ActionScript 3 事件模型 第二种方式:实现 IEventDispatcher 接口 import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.utils.Timer; import flash.events.TimerEvent; [Event(name=quot;tickquot;,type=quot;cn.qdqn.clock.TickEventquot;)] public class Clock2 implements IEventDispatcher{ /** * 使用Timer作为时间晶振 */ private var timer:Timer; /** * 初始化timer */ public function initTimer():void{ timer=new Timer(0,1000); timer.addEventListener(TimerEvent.TIMER,timerHandler); timer.start(); } public function timerHandler(event:TimerEvent):void{ var tickEvent:TickEvent=new TickEvent(quot;>quot;); this.dispatchEvent(tickEvent); } private var dispatcher:EventDispatcher; public function Clock2(){ dispatcher=new EventDispatcher(this); initTimer(); } public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void{ dispatcher.addEventListener(type,listener,useCapture,priority); } public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void{ dispatcher.addEventListener(type,listener,useCapture); } public function dispatchEvent(event:Event):Boolean{ return dispatcher.dispatchEvent(event); } public function hasEventListener(type:String):Boolean{ return dispatcher.hasEventListener(type); } public function willTrigger(type:String):Boolean{ return dispatcher.willTrigger(type); } 46 }
  • Flex 和 ActionScript 3 事件模型 运行测试类 import cn.qdqn.clock.*; import flash.display.Sprite; public class ClockTest extends Sprite { public function ClockTest():void { var clock:Clock1=new Clock1(); clock.addEventListener(TickEvent.TICK,tickHandler); } function tickHandler(event:TickEvent):void{ trace(event.message); } } 3. 使自定义 MXML 组件能够调度自定义事件 在自定义 MXML 组件使用<mx:Metadata>声明自定义事件 <?xml version=quot;1.0quot; encoding=quot;utf-8quot;?> <mx:Canvas xmlns:mx=quot;http://www.adobe.com/2006/mxmlquot; width=quot;342quot; height=quot;116quot; creationComplete=quot;initTimer()quot;> <mx:Metadata> [Event(name=quot;tickquot;,type=quot;cn.qdqn.clock.TickEventquot;)] </mx:Metadata> <mx:Script> <![CDATA[ /** * 使用Timer作为时间晶振 */ private var timer:Timer; /** * 初始化timer */ private function initTimer():void{ timer=new Timer(1000); timer.addEventListener(TimerEvent.TIMER,timerHandler); timer.start(); } private function timerHandler(event:TimerEvent):void{ var tickEvent:TickEvent=new TickEvent(quot;>quot;); this.dispatchEvent(tickEvent); } public function changeTime():void{ txtTime.text=new Date().toTimeString(); } ]]> </mx:Script> 47 <mx:Label id=quot;txtTimequot; x=quot;60quot; y=quot;48quot; text=quot;quot; width=quot;200quot;/> </mx:Canvas>
  • Flex 和 ActionScript 3 事件模型 运行测试 MXML Application <?xml version=quot;1.0quot; encoding=quot;utf-8quot;?> <mx:Application xmlns:mx=quot;http://www.adobe.com/2006/mxmlquot; layout=quot;absolutequot; xmlns:qdqn=quot;cn.qdqn.clock.*quot;> <mx:Script> <![CDATA[ import cn.qdqn.clock.TickEvent; private function tickHandler(event:TickEvent):void{ event.currentTarget.changeTime(); } ]]> </mx:Script> <qdqn:Clock3 x=quot;298.5quot; y=quot;201quot; tick=quot;tickHandler(event)quot;> </qdqn:Clock3> </mx:Application> 5.7 参考资源 [1] Adobe Flex 3 Help http://livedocs.adobe.com/flex/3/html/help.html [2] Adobe® Flex™ 3.3 Language Reference http://livedocs.adobe.com/flex/3/langref/index.html Adobe® Flex™ 3.2 语言参考 http://livedocs.adobe.com/flex/3_cn/langref/ [3] Programming Adobe ActionScript 3.0 for Adobe Flex 英文版 http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3_Flex/ 中文版 http://help.adobe.com/zh_CN/ActionScript/3.0_ProgrammingAS3/ [4] DOM-Level-3-Events http://www.w3.org/TR/DOM-Level-3-Events/events.html [5] Flex 事件机制 http://blog.csdn.net/ThinkInside/archive/2007/11/13/1882194.aspx [6] 黑羽 ActionScript 3 教程全集 http://www.kingda.org/archives/kingda/2007/04/actionscript_3.html [7] 邱彦林 《Flex 第一步:基于 ActionScript 3.0 的 Flex 2 应用开发》 [8] 《ActionScript 3.0 Quick Reference Guide》 48