5. 函数指针进化论
多线程
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
6. 函数指针进化论
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)
{
7. 函数指针进化论
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();
8. 函数指针进化论
// 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
9. 函数指针进化论
// 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();
}}}
10. 函数指针进化论
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
11. 函数指针进化论
第二种方式:使用 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);
12. 函数指针进化论
}
// 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 使用异步方式调用同步方法
21. 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
22. 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
24. 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)
{
25. 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);
26. 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();
27. 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()
{
28. 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);
29. 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();
30. 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 }
31. 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
41. 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 元素的事件绑定
42. 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
47. 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
}
}
48. 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
}
49. 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>