浅析Visual C#事件处理机制
事件简介: 任何进行过图形用户界面开发的编程人员都会知道事件的概念。当用户在使用程序的时候,用户必然要和程序进行一定的交互。比如当用户点击窗体上的一个按钮后,程序就会产生该按钮被点击的事件,并通过相应的事件处理函数来响应用户的操作。这样用户的直观感觉就是程序执行了我要求的任务了。当然,事件并不一定是在和用户交互的情况下才会产生的,系统的内部也会产生一些事件并请求处理的,比如时钟事件就是一个很好例子。不过要介绍C#中的事件处理机制(扩展到更广的范围便是整个.Net框架),我们首先得明白一个叫"委托"的概念。 C#中的委托: 委托,顾名思义,就是中间代理人的意思。C#中的委托允许你将一个对象中的方法传递给另一个能调用该方法的类的某个对象。你可以将类A中的一个方法m(被包含在某个委托中了)传递给一个类B,这样类B就能调用类A中的方法m了。同时,你还可以以静态(static)的方式或是实例(instance)的方式来传递该方法。所以这个概念和C++中的以函数指针为参数形式调用其他类中的方法的概念是十分类似的。 委托的概念首先是在Visual J++中被提出来的,现在C#也应用了委托的概念,这也可谓是"拿来主义"吧。C#中的委托是通过继承System.Delegate中的一个类来实现的,下面是具体的步骤: 1. 声明一个委托对象,其参数形式一定要和你想要包含的方法的参数形式一致。 2. 定义所有你要定义的方法,其参数形式和第一步中声明的委托对象的参数形式必须相同。 3. 创建委托对象并将所希望的方法包含在该委托对象中。 4. 通过委托对象调用包含在其中的各个方法。 以下的C#代码显示了如何运用以上的四个步骤来实现委托机制的: using System; file://步骤1: 声明一个委托对象 public delegate void MyDelegate(string input); file://步骤2::定义各个方法,其参数形式和步骤1中声明的委托对象的必须相同 class MyClass1{ public void delegateMethod1(string input){ Console.WriteLine( "This is delegateMethod1 and the input to the method is {0}", input); } public void delegateMethod2(string input){ Console.WriteLine( "This is delegateMethod2 and the input to the method is {0}", input); } } file://步骤3:创建一个委托对象并将上面的方法包含其中 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; } } file://步骤4:通过委托对象调用包含在其中的方法 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,"Calling the delegate"); } } C#中的事件处理函数: C#中的事件处理函数是一个具有特定参数形式的委托对象,其形式如下: public delegate void MyEventHandler(object sender, MyEventArgs e); 其中第一个参数(sender)指明了触发该事件的对象,第二个参数(e)包含了在事件处理函数中可以被运用的一些数据。上面的MyEventArgs类是从EventArgs类继承过来的,后者是一些更广泛运用的类,如MouseEventArgs类、ListChangedEventArgs类等的基类。对于基于GUI的事件,你可以运用这些更广泛的、已经被定义好了的类的对象来完成处理;而对于那些基于非GUI的事件,你必须要从EventArgs类派生出自己的类,并将所要包含的数据传递给委托对象。下面是一个简单的例子: public class MyEventArgs EventArgs{ public string m_myEventArgumentdata; } 在事件处理函数中,你可以通过关键字event来引用委托对象,方法如下: public event MyEventHandler MyEvent; 现在,我们来创建两个类,通过这两个类我们可以知道C#完成事件处理的机制是如何工作的。在我们的实例中,A类将提供事件的处理函数,并在步骤3中创建委托对象同时将事件处理函数包含在其中,同上所述,事件处理函数的参数形式必须和委托对象的参数形式相一致。然后,A类将委托对象传递给B类。当B类中的事件被触发后,A类中的事件处理函数就相应的被调用了。下面是示例代码: using System; file://步骤1:声明委托对象 public delegate void MyHandler1(object sender,MyEventArgs e); public delegate void MyHandler2(object sender,MyEventArgs e); file://步骤2:创建事件处理函数的方法 class A{ public const string m_id="Class A"; public void OnHandler1(object sender,MyEventArgs e){ Console.WriteLine("I am in OnHandler1 and MyEventArgs is {0}", e.m_id); } public void OnHandler2(object sender,MyEventArgs e){ Console.WriteLine("I am in OnHandler2 and MyEventArgs is {0}", e.m_id); } file://步骤3:创建委托对象,并事件处理函数包含在其中同时设置好将要触发事件的对象 public A(B b){ MyHandler1 d1=new MyHandler1(OnHandler1); MyHandler2 d2=new MyHandler2(OnHandler2); b.Event1 +=d1; b.Event2 +=d2; } } file://步骤4:通过委托对象(也就是触发事件)来调用被包含的方法 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 Driver{ public static void Main(){ B b= new B(); A a= new A(b); MyEventArgs e1=new MyEventArgs(); MyEventArgs e2=new MyEventArgs(); e1.m_id ="Event args for event 1"; e2.m_id ="Event args for event 2"; b.FireEvent1(e1); b.FireEvent2(e2); } } 在进行文件操作时,可以使用CFile类中的Remove()函数来删除一个文件,但是这样的操作将永久性的删除该文件,不能在必要的时候再恢复该文件,解决这个问题的唯一方法就是把文件送到Windows系统中的回收站(Recycle Bin)里面,而不是简单的永久性删除它,这样用户就可以在必要的时候恢复这个文件。这个例子就来说明如何实现编程来实现Windows回收站的文件存取操作。 一、实现方法 在Windows的shellapi文件中定义了一个名为SHFileOperation()的外壳函数,用它可以实现各种文件操作,如文件的拷贝、删除、移动等,该函数使用起来非常简单,它只有一个指向SHFILEOPSTRUCT结构的参数。使用SHFileOperation()函数时只要填写该专用结构--SHFILEOPSTRUCT,告诉Windows执行什么样的操作,以及其它重要信息就行了。SHFileOperation()的特别之处在于它是一个高级外壳函数,不同于低级文件处理。当调用SHFileOperation操作文件时,相应的外壳拷贝处理器(如果有的话)被调用。如在删除某个文件时,SHFileOperation会将删除的文件放到Recycle Bin中。SHFileOperation()函数的原形为: WINSHELLAPI int WINAPI SHFileOperation (LPSHFILEOPSTRUCT lpFIleOp); 函数中参数类型为一个LPSHFILEOPSTRUCT结构,它包含有进行文件操作的各种信息,其具体的结构如下: Typedef struct _ShFILEOPSTRUCT { HWND hWnd; //消息发送的窗口句柄; UINT wFunc; //操作类型 LPCSTR pFrom; //源文件及路径 LPCSTR pTo; //目标文件及路径 FILEOP_FLAGS fFlags; //操作与确认标志 BOOL fAnyOperationsAborted; //操作选择位 LPVOID hNameMappings; //文件映射 LPCSTR lpszProgressTitle; //文件操作进度窗口标题 }SHFILEOPSTRUCT, FAR * LPSHFILEOPSTRUCT; 在这个结构中,hWnd是指向发送消息的窗口句柄,pFrom与pTo是进行文件操作的源文件名和目标文件名,它包含文件的路径,对应单个文件的路径字符串,或对于多个文件,必须以NULL作为字符串的结尾或文件路径名之间的间隔,否则在程序运行的时候会发生错误。另外,pFrom和pTo都支持通配符*和?,这大大方便了开发人员的使用。例如,源文件或目录有两个,则应是:char pFrom[]="d:\\Test1\0d:\\Text.txt\0",它表示对要D:盘Test目录下的所有文件和D:盘上的Text.txt文件进行操作。字符串中的"\\"是C语言中的'\'的转义符,'\0'则是NULL。wFunc 是结构中的一个非常重要的成员,它代表着函数将要进行的操作类型,它的取值为如下: FO_COPY: 拷贝文件pFrom到pTo 的指定位置。 FO_RENAME: 将pFrom的文件名更名为pTo的文件名。 FO_MOVE: 将pFrom的文件移动到pTo的地方。 FO_delete: 删除pFrom指定的文件。 使用该函数进行文件拷贝、移动或删除时,如果需要的时间很长,则程序会自动在进行的过程中出现一个无模式的对话框(Windows操作系统提供的文件操作对话框),用来显示执行的进度和执行的时间,以及正在拷贝、移动或删除的文件名,此时结构中的成员lpszProgressTitle显示此对话框的标题。fFlags是在进行文件操作时的过程和状态控制标识。它主要有如下一些标识,也可以是其组合: FOF_FILESONLY:执行通配符,只执行文件; FOF_ALLOWUNDO:保存UNDO信息,以便在回收站中恢复文件; FOF_NOCONFIRMATION:在出现目标文件已存在的时候,如果不设置此项,则它会出现确认是否覆盖的对话框,设置此项则自动确认,进行覆盖,不出现对话框。 FOF_NOERRORUI:设置此项后,当文件处理过程中出现错误时,不出现错误提示,否则会进行错误提示。 FOF_RENAMEONCOLLISION:当已存在文件名时,对其进行更换文提示。 FOF_SILENT:不显示进度对话框。 FOF_WANTMAPPINGHANDLE:要求SHFileOperation()函数返回正处于操作状态的实际文件列表,文件列表名柄保存在hNameMappings成员中。 SHFILEOPSTRUCT结构还包含一个SHNAMEMAPPING结构的数组,此数组保存由SHELL计算的每个处于操作状态的文件的新旧路径。 在使用该函数删除文件时必须设置SHFILEOPSTRUCT结构中的神秘FOF_ALLOWUNDO标志,这样才能将待删除的文件拷到Recycle Bin,从而使用户可以撤销删除操作。需要注意的是,如果pFrom设置为某个文件名,用FO_delete标志删除这个文件并不会将它移到Recycle Bin,甚至设置FOF_ALLOWUNDO标志也不行,在这里你必须使用全路径名,这样SHFileOperation才会将删除的文件移到Recycle Bin。 二、编程步骤 1、 启动Visual C++6.0,生成一个单文档视图的项目Filedelete; 2、为项目添加一个菜单ID_Filedelete,然后用Visual C++的Class Wizard为其在视图类新增消息处理函数OnFiledelete(); 3、添加代码,编译运行程序; 三、程序代码 /////////////////////////////////////////////////////////////////////// void CFileOperationView::OnFiledelete() { int nOk; char strSrc[]="d:\\Vb\0";//源文件路径; char strDst[]="d:\\Vb1\0";//目标文件路径; char strTitle[]="文件拷贝"; //文件删除进度对话框标题 SHFILEOPSTRUCT FileOp;//定义SHFILEOPSTRUCT结构对象; FileOp.hwnd=this->m_hWnd; FileOp.wFunc=FO_delete; //执行文件删除操作; FileOp.pFrom=strSrc; FileOp.pTo=strDst; FileOp.fFlags=FOF_ALLOWUNDO;//此标志使删除文件备份到Windows回收站 FileOp.hNameMappings=NULL; FileOp.lpszProgressTitle=strTitle; //开始删除文件 nOk=SHFileOperation(&FileOp); if(nOk) TRACE("There is an error: %d\n",nOk); else TRACE("SHFileOperation finished successfully\n"); } 四、小结 在Visual C++编程中实现文件的操作是很多应用程序要涉及到的内容,解决这个问题的一般方法是直接利用CFile 类的操作成员函数,这种方法对于广大Visual C++编程爱好者来说已经耳熟能详了,其实使用我们上文介绍的方法,利用Win32外壳来实现拷贝、更名、移动、删除等各种文件操作将更加高效、快捷,值得一提的是该方法不仅具有上述功能,它还直接支持对一个目录或目录树进行操作。同时该方法直接调用Windows操作系统中的外壳,它的处理过程与Windows的自身文件处理过程是一致的,这大大地有利于我们开发的应用程序与操作系统保持高度的一致性。 |