在C#是一种类似 Java 的完全面向对象的高级编程语言,其处理过程采用事件驱动方式,但在实际的使用过程中,有时候通过调用系统原有的消息处理起来会更简单,特别是在处理与DLL文件的交互时,实践证明的确是非常方便的。
在C#中使用自定义消息
在C#中使用自定义消息非常简单,只需要下面几个简单的步骤就可以了:
1、定义消息
定义消息的方法与在VC中定义消息有一点点不同,比如在VC中申明一个自定义消息是这样的:
1.
#define WM_TEST WM_USER + 101
而在C#中消息需要定义成 Windows 系统中的原始的16进制数字,比如自定义消息
1.
public
const
int
USER = 0x0400;
那么我们在VC中申明的自定义消息,在C#中就可以做对应的声明:
1.
public
const
int
WM_TEST = USER+101;
2、发送消息
消息发送是通过 Windows 提供的 API 函数 SendMessage 来实现的,它的原型定义:
1.
[DllImport(
"User32.dll"
,EntryPoint=
"SendMessage"
)]
2.
private
static
extern
int
SendMessage(
3.
IntPtr hWnd,
// handle to destination window
4.
uint Msg,
// message
5.
uint wParam,
// first message parameter
6.
uint lParam
// second message parameter
7.
);
3、消息接收
消息发出之后,在Form中如何接收呢?我们可以重载DefWinproc函数来接收消息。
01.
protected
override
void
DefWndProc ( ref System.Windows.Forms.Message m )
02.
{
03.
switch
(m.Msg)
04.
{
05.
case
Message.WM_TEST:
//处理消息
06.
break
;
07.
default
:
08.
base.DefWndProc(ref m);
//调用基类函数处理非自定义消息。
09.
break
;
10.
}
11.
}
在C#中使用系统消息
我们以WM_PAINT消息的处理为例,在C#中处理消息与MFC的消息处理是类似的,但更为简单。MFC中需要使用DECLARE_MESSAGE_MAP来定义消息映射,在C#就不需要了。比如WM_PAINT消息,我们只要重载父类中的OnPaint虚拟方法即可,方法如下:
在菜单View->Other Windows->Object Browser打开对象浏览窗口(或用CTRL+ALT+J打开),在我们的工程名下找到Form并选中,这时在右边的窗口列出所有Form类的成员函数。
我们选中OnPaint(System.WinForms.PaintEventArgs)此时在下面会显示完整的OnPaint函数protected void OnPaint ( System.WinForms.PaintEventArgs e )我们将这一行字符串Copy下来。打开Form1.cs进行代码编辑,我们把刚才拷贝下来的函数定义复制到Form1类里面,并加上override关键字,此时我们便可以在里面添加我们的消息处理代码了,请参考如下代码段:
01.
protected
override
void
OnPaint (System.Windows.Forms.PaintEventArgs e )
02.
{
03.
Font font =
new
Font(
"黑体"
,28);
///定义字体:黑体,大小:28
04.
SolidBrush bluepen =
new
SolidBrush(Color.Blue);
///创建蓝色画笔
05.
SolidBrush blackpen =
new
SolidBrush(Color.FromARGB(0xa0,0xa0,0xb0));
///创建黑色画笔
06.
e.Graphics.DrawString(
"VC知识库"
,font,blackpen,65,25);
///写字符串
07.
08.
///偏移4个象素用不同的颜色再写一次,达到立体效果
09.
e.Graphics.DrawString(
"VC知识库"
,font,bluepen,61,21);}
示例应用
1、定义消息
我们在工程中添加一个Message类用来定义消息。然后添加了三个成员变量,其中USER为自定义消息的初始值,相当与MFC中的WM_USER。WM_TEST为自定义的用来响应应用程序的消息,WM_MSG为自定义的用来响应DLL传递过来的消息。如何在DLL定义消息请参考文章:VC.Net从DLL传递消息到DLL。
1.
public
class
Message
2.
{
3.
public
const
int
USER = 0x0400;
4.
//as mfc Define WM_TEST WM_USER + 101
5.
public
const
int
WM_TEST = USER+101;
6.
public
const
int
WM_MSG = USER+102;
7.
}
2、声明引用函数
在使用消息的地方,申明引用的函数,我们这里在MsgForm.cs文件中申明:
01.
//申明发送消息函数
02.
[DllImport(
"User32.dll"
,EntryPoint=
"SendMessage"
)]
03.
private
static
extern
int
SendMessage(
04.
IntPtr hWnd,
// handle to destination window
05.
uint Msg,
// message
06.
uint wParam,
// first message parameter
07.
uint lParam
// second message parameter
08.
);
09.
10.
//申明DLL中启动消息函数
11.
[DllImport(
"MessageDLL.dll"
,EntryPoint=
"StartSendMessage"
)]
12.
private
extern
static
void
StartSendMessage(IntPtr hWnd);
3、处理系统消息
01.
protected
override
void
OnPaint ( System.Windows.Forms.PaintEventArgs e )
02.
{
03.
///定义字体:黑体,大小:28
04.
Font font =
new
Font(
"黑体"
,28);
05.
///创建蓝色画笔
06.
SolidBrush bluepen =
new
SolidBrush(Color.Blue);
07.
///创建黑色画笔
08.
SolidBrush blackpen =
new
SolidBrush(Color.FromArgb(0xa0,0xa0,0xb0));
09.
///写字符串
10.
e.Graphics.DrawString(
"VC知识库"
,font,blackpen,65,25);
11.
///偏移4个象素用不同的颜色再写一次,达到立体效果
12.
e.Graphics.DrawString(
"VC知识库"
,font,bluepen,61,21);
13.
}
4、 触发自定义消息
01.
//测试应用程序消息
02.
private
void
TestAppbutton_Click(object sender, System.EventArgs e)
03.
{
04.
SendMessage(
this
.Handle,Message.WM_TEST,100,200);
05.
}
06.
07.
//测试DLL消息
08.
private
void
TestDLLbutton_Click(object sender, System.EventArgs e)
09.
{
10.
StartSendMessage(
this
.Handle);
11.
}
5、响应和处理自定义消息
01.
protected
override
void
DefWndProc ( ref System.Windows.Forms.Message m )
02.
{
03.
string message;
04.
switch
(m.Msg)
05.
{
06.
case
Message.WM_TEST:
//处理消息
07.
message = string.Format(
"收到从应用程序发出的消息!参数为:{0},{1}"
,m.WParam,m.LParam);
08.
MessageBox.Show(message);
///显示一个消息框
09.
break
;
10.
case
Message.WM_MSG:
11.
message = string.Format(
"收到从DLL发出的消息!参数为:{0},{1}"
,m.WParam,m.LParam);
12.
MessageBox.Show(message);
///显示一个消息框
13.
break
;
14.
default
:
15.
base.DefWndProc(ref m);
//调用基类函数处理非自定义消息。
16.
17.
break
;
18.
}
19.
}
程序运行结果:
当我们点击测试DLL消息时,弹出消息框显示收到消息的参数,窗口也会调用WM_PAIN函数对窗口进行重新绘制。
说明:本文是根据VC知识库《在线杂志》中王骏的一篇文章:“C#开发WINDOWS应用程序时消息的处理”整理而成。
===========================================================================================================================
谈到Winform的消息处理,多数时候是通过事件处理程序进行的,但当没有对应的事件时通常的做法是声明DefWndProc或者WndProc或者IMessageFilter,经常在网上看见有文章将三者并列,那么它们有什么区别呢?
DefWndProc和WndProc都是继承自Control类中的虚方法,原型如下:
1: protected override void DefWndProc(ref Message m)
2: {
3: ....
4: base.DefWndProc(m);
5: }
6:
7: protected override void WndProc(ref Message m);
8: {
9: .....
10: base.WndProc(m);
11: }
所有的有用户界面的控件都继承自Control,这种方式需要创建对应控件的派生类,不能统一对各个窗口的消息进行拦截处理,因为从根本上说这两者都是Windows的窗口过程,只有收到针对本窗口自身的消息。
通过复习Windows的消息处理机制,对这三者的关系可以有更好的理解。应用程序的消息来自于系统消息队列,被应用程序的主程序中的消息循环所处理。这个消息循环从应用程序的消息队列中取出消息,进行预处理,然后派发到消息对应的窗口过程,窗口过程在被调用后根据消息的类型进行相应的处理,有些可以由Windows默认处理的消息就调用Windows的DefWindowProc。
这里的WndProc就是对应控件窗口的窗口过程,而DefWndProc会被WndProc调用,处理那些WndProc中未处理的消息(包括WndProc未吞掉的),因此DefWndProc收到的消息会比WndProc少。
IMessageFilter的调用发生在应用程序的消息循环中,是消息预处理的一部分,所以它收到的消息是更全的(除了直接发送到窗口过程不进入消息队列的那些消息)。使用方式如下:
1: public class MessageFilter : IMessageFilter
2: {
3: public bool PreFilterMessage(ref Message msg)
4: {
5: //识别消息并处理
6: //return true;//吞掉消息,不派发
7: return false;//进入下一步派发到对应窗口过程
8: }
9: }
10:
11: //在应用程序消息循环中加入消息过滤器
12: MessageFilter f = new MessageFilter(this.lbMsg);
13: Application.AddMessageFilter(f);
14:
三者都有一个共同的参数类型Message,它封装了Windows消息。同时还包括一个很方便的ToString方法,可以将Message对象转换成包括消息名称(WM_XXX)在内的字符串,通过Reflector可以看到实现是通过一个内部类MessageDecoder,使用一个很长的switch语句将消息ID转换成消息名称。
Message的定义如下:
1: [StructLayout(LayoutKind.Sequential), SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
2: public struct Message
3: {
4: private IntPtr hWnd;
5: private int msg;
6: private IntPtr wparam;
7: private IntPtr lparam;
8: private IntPtr result;
9: public IntPtr HWnd { get; set; }
10: public int Msg { get; set; }
11: public IntPtr WParam { get; set; }
12: public IntPtr LParam { get; set; }
13: public IntPtr Result { get; set; }
14: public object GetLParam(Type cls);
15: public static Message Create(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam);
16: public override bool Equals(object o);
17: public static bool operator !=(Message a, Message b);
18: public static bool operator ==(Message a, Message b);
19: public override int GetHashCode();
20: public override string ToString();
21: }
22:
其中hWnd是消息对应的窗口句柄,根据上面的分析可以知道在窗口过程(DefWndProc,WndProc)中收到的窗口句柄都是该窗口的句柄,而在PreFilterMessage中收到的消息的窗口句柄则根据触发消息的窗口不同而不同。
在PreFilterMessage中收到消息时,可以使用Control.FromHandle得到窗口对应的控件对象,原型如下:
//Declaring Type: System.Windows.Forms.Control //Assembly: System.Windows.Forms, Version=2.0.0.0 public static Control FromHandle(IntPtr handle); 通过这种方式可以监测各消息的信息来自哪个控件。
public bool PreFilterMessage(ref Message msg) { Control c = Control.FromHandle(msg.HWnd); if (c == null) System.Diagnostics.Debug.WriteLine("Filter:NULL" +"-" + msg.ToString()); else System.Diagnostics.Debug.WriteLine("Filter:" +c.Name+"-"+ msg.ToString()); return false; }
从Visual Studio的输出窗口监视到的调试输出:
P.S.脑子里一直想着好像还有种定义消息处理过程的方式,而且是可以直接指定处理哪个消息,好像使用的关键字是“message”。。。
在MSDN上搜索N久后反应过来,哦,好象是Delphi中的方法;-)