一、消息概述

Windows下应用程序的执行是通过消息驱动的。消息是整个应用程序的工作引擎,我们需要理解掌握我们使用的编程语言是如何封装消息的原理。C#自定义消息通信往往采用事件驱动的方式实现,但有时候我们不得不采用操作系统的消息通信机制,例如在和底层语言开发的DLL交互时,是比较方便的。下面列举了一些实现方式,供大家参考.

1 什么是消息(Message)

消息就是通知和命令。在.NET框架类库中的System.Windows.Forms命名空间中微软采用面对对象的方式重新定义了Message。新的消息(Message)结构的公共部分属性基本与早期的一样,不过它是面对对象的。

公共属性:

HWnd     获取或设定消息的处理函数

Msg      获取或设定消息的ID号

Lparam   指定消息的LParam字段

Wparam   指定消息的WParam字段

Result   指定为响应消息处理函数而向OS系统返回的值

2 消息驱动的过程

所有的外部事件,如键盘输入、鼠标移动、按动鼠标都由OS系统转换成相应的消息发送到应用程序的消息队列。每个应用程序都有一段相应的程序代码来检索、分发这些消息到对应的窗体,然后由窗体的处理函数来处理。

二、C#中的消息的封装

C#对消息重新进行了面对对象的封装,在C#中消息被封装成了事件。

System.Windows.Forms.Application类具有用于启动和停止应用程序和线程以及处理Windows消息的方法。

调用Run以启动当前线程上的应用程序消息循环,并可以选择使其窗体可见。

调用Exit或ExitThread来停止消息循环。

C#中用Application类来处理消息的接收和发送的。消息的循环是由它负责的。

从本质上来讲,每个窗体一般都对应一个窗体过程处理函数。那么,C#的一个Form实例(相当于一个窗体)收到消息后是如何处理消息的?其实,这个问题的分析也就是展示了C#的消息封装原理。

实现鼠标左键按下的消息的响应(WM_LBUTTONDOWN)

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown1);

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown2);

private void Form1_MouseDown1(object sender, System.Windows.Forms.MouseEventArgs e)

{

if(e.Button==System.Windows.Forms.MouseButtons.Left)

System.Windows.Forms.MessageBox.Show("消息被Form1_MouseDown1函数响应");

}

private void Form1_MouseDown2(object sender, System.Windows.Forms.MouseEventArgs e)

{

if(e.Button==System.Windows.Forms.MouseButtons.Left)

System.Windows.Forms.MessageBox.Show("消息被Form1_MouseDown2函数响应");

}

上面this.MouseDown是C#中的一个事件。它的定义如下:

public event MouseEventHandler MouseDown;

而MouseEventHandler的定义为:

public delegate void MouseEventHandler( object sender,MouseEventArgs e);

实际上,上面定义了一个委托类型MouseEventHandler。委托了启用了其它编程语言中的函数指针的解决方案。与C++的函数指针不同,委托是完全面向对象的,同时封装了对象实例和方法。本质上,委托把一个实例和该实例上的方法函数封装成一个可调用的实体,它是面对对象的、安全的。

我们可以把

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown1);

这条语句看成向this.MouseDown添加一个函数指针。

事件是对象发送的消息,以发送信号通知操作的发生。引发(触发)事件的对象叫做事件发送方。捕获事件并对事件作出响应的对象叫做事件接收方。在事件通讯中,事件发送方类并不知道哪个对象或方法将接收到(处理)它引发的事件。所需要的是在发送方和接收方之间存在一个媒介(类似指针的机制)。.NET框架定义了一个特殊的类型(Delegate委托),该类型提供函数指针的功能。这样,委托就等效于一个类型安全的函数指针或一个回调函数。

前面我们向this.MouseDown事件添加了两个委托。

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown1);

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown2);

结果,我们的两个函数Form1_MouseDown1、Form1_MouseDown2在我们单击鼠标左键的时候都会被调用,而且调用的顺序和我们添加委托的顺序一致。

WM_LBUTTONDOWN消息首先被Application类从应用程序消息队列中取出,然后分发到相应的窗体。窗体使用MouseDown事件中的函数指针调用已经添加的响应函数。所以C#中的事件字段实质上是一个函数指针列表,用来维护一些消息到达时的响应函数的地址。

三、结论

C#中消息的工作流程:

C#中的消息被Application类从应用程序消息队列中取出,然后分发到消息对应的窗体,窗体对象的第一个响应函数是对象中的protected override void WndProc(ref System.Windows.Forms.Message e)方法。

它再根据消息的类型调用默认的消息响应函数(如OnMouseDown),默认的响应函数然后根据对象的事件字段(如this.MouseDown )中的函数指针列表,调用用户所加入的响应函数(如Form1_MouseDown1和Form1_MouseDown2),而且调用顺序和用户添加顺序一致。

四、再回首Application类

Application类有一个AddMessageFilter的静态方法,通过它我们可以添加消息筛选器,以便在向目标传递Windows消息时,检视这些消息。

使用消息筛选器来防止引发特定事件,或在将某事件传递给事件处理程序之前使用消息筛选器对其执行特殊操作。我们必须提供IMessageFilter接口的一个实现,然后才可以使用消息筛选器。以下的示范代码将演示在消息发往窗体前我们如何拦截它。我们拦截的同样是WM_LBUTTONDOWN消息。

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

namespace MessageMech3

{

//实现消息过滤器接口

public class CLButtonDownFilter : IMessageFilter

{

public bool PreFilterMessage(ref Message m)

{

if (m.Msg==0x0201)// WM_LBUTTONDOWN

{

System.Windows.Forms.MessageBox.Show("App中鼠标左键按下");

//返回值为true, 表示消息已被处理,不要再往后传递,因此消息被截获

//返回值为false,表示消息未被处理,需要再往后传递,因此消息未被截获

return true;

}

return false;

}

}

/// <summary>

/// Summary description for WinForm.

/// </summary>

public class WinForm : System.Windows.Forms.Form

{

/// <summary>

/// Required designer variable.

/// </summary>

private System.Windows.Forms.Label label1;

private System.ComponentModel.Container components = null;

public WinForm()

{

//

// Required for Windows Form Designer support

//

InitializeComponent();

//

// TODO: Add any constructor code after InitializeComponent call

//

//安装自己的过滤器

CLButtonDownFilter MyFilter=new CLButtonDownFilter();

System.Windows.Forms.Application.AddMessageFilter(MyFilter);

}

/// <summary>

/// Clean up any resources being used.

/// </summary>

protected override void Dispose (bool disposing)

{

if (disposing)

{

if (components != null)

{

components.Dispose();

}

}

base.Dispose(disposing);

}

#region Windows Form Designer generated code

/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

private void InitializeComponent()

{

this.label1 = new System.Windows.Forms.Label();

this.SuspendLayout();

//

// label1

//

this.label1.BackColor = System.Drawing.Color.Transparent;

this.label1.Dock = System.Windows.Forms.DockStyle.Top;

this.label1.ForeColor = System.Drawing.Color.DarkViolet;

this.label1.Name = "label1";

this.label1.Size = new System.Drawing.Size(440, 32);

this.label1.TabIndex = 0;

this.label1.Text = "演示如何在App对象中处理消息,请点鼠标左键";

this.label1.TextAlign = System.Drawing.ContentAlignment.BottomCenter;

//

// Form1

//

this.AutoScaleBaseSize = new System.Drawing.Size(7, 22);

this.BackColor = System.Drawing.Color.WhiteSmoke;

this.ClientSize = new System.Drawing.Size(440, 273);

this.Controls.AddRange(new System.Windows.Forms.Control[] {this.label1});

this.Font = new System.Drawing.Font("华文行楷", 15F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(134)));

this.Name = "WinForm";

this.Text = "WinForm";

//消息响应函数的调用顺序和添加委托的顺序一致

//即:以下命令将先调用Form1_MouseDown1再调用Form1_MouseDown2

//通过委托添加自己的鼠标按键消息响应函数1

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown1);

//通过委托添加自己的鼠标按键消息响应函数2

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown2);

this.ResumeLayout(false);

}

#endregion

/// <summary>

/// 应用程序的主入口点。

/// </summary>

[STAThread]

static void Main()

{

Application.Run(new WinForm()); //启动当前Form线程上的应用程序消息循环

}

//要点1

// 通过C#提供的事件接口添加自己的鼠标按键事件的响应函数

//

private void Form1_MouseDown1(object sender, System.Windows.Forms.MouseEventArgs e)

{

if(e.Button==System.Windows.Forms.MouseButtons.Left)

System.Windows.Forms.MessageBox.Show("消息被Form1_MouseDown1函数响应");

}

private void Form1_MouseDown2(object sender, System.Windows.Forms.MouseEventArgs e)

{

if(e.Button==System.Windows.Forms.MouseButtons.Left)

System.Windows.Forms.MessageBox.Show("消息被Form1_MouseDown2函数响应");

}

//要点2

//通过覆盖基类的事件引发函数拦截消息

//

protected override   void OnMouseDown( MouseEventArgs e)

{

if(e.Button==System.Windows.Forms.MouseButtons.Left)

System.Windows.Forms.MessageBox.Show("消息被OnMouseDown函数响应");

//如果需要截获消息,可将base.OnMouseDown(e);语句注释掉

base.OnMouseDown(e);

}

//要点3

//通过覆盖基类的窗体函数拦截消息

//

protected override void WndProc(ref System.Windows.Forms.Message e)

{

//如果需要截获消息,

//if(e.Msg==0x0201)// WM_LBUTTONDOWN

//    System.Windows.Forms.MessageBox.Show("消息被WndProc函数响应");

//else

//    base.WndProc(ref e);

//不需要截获消息则为

if(e.Msg==0x0201)// WM_LBUTTONDOWN

System.Windows.Forms.MessageBox.Show("消息被WndProc函数响应");

base.WndProc(ref e);

}

}

}

以上代码我们首先用类CLButtonDownFilter实现了IMessageFilter接口,在WinForm初始化的时候我们安装了消息筛选器。程序实际执行的时候,在点击鼠标左键的时候,程序仅仅会弹出一个"App中鼠标左键按下"的消息框。因为我们在消息发往窗体前拦截了它,所以窗体将接收不到WM_LBUTTONDOWN消息。

如果我们把

if (m.Msg==0x0201)// WM_LBUTTONDOWN

{

System.Windows.Forms.MessageBox.Show("App中鼠标左键按下");

return true;

}

改成

if (m.Msg==0x0201)// WM_LBUTTONDOWN

{

System.Windows.Forms.MessageBox.Show("App中鼠标左键按下");

return false;

}

那么,我们在Application类处理消息后,消息将继续发往窗体。窗体的函数将可以处理此消息。程序执行效果是顺序弹出5个消息框。

1:<<App中鼠标左键按下>>

2:<<消息被WndProc函数响应>>

3:<<消息被OnMouseDown函数响应>>

4:<<消息被Form1_MouseDown1函数响应>>

5:<<消息被Form1_MouseDown2函数响应>>

其实本文中已经说的挺详细的.弹出的对话框只是为了让你更直观的看出导致的结果.

先定义没过滤时的效果.

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown1);

private void Form1_MouseDown1(object sender, System.Windows.Forms.MouseEventArgs e)

{

if (e.Button == System.Windows.Forms.MouseButtons.Left)

MessageBox.Show("消息被Form1_MouseDown1函数响应");

}

主要有两种方法过滤实现过滤

第一种:

protected override void WndProc(ref Message m)

{

if (m.Msg == 0x0201)

return;

else

base.WndProc(ref m);

}

第二种

不重写WndProc

//实现消息过滤器接口

public class CLButtonDownFilter : IMessageFilter

{

public bool PreFilterMessage(ref Message m)

{

if (m.Msg == 0x0201)// WM_LBUTTONDOWN

{

//返回值为true,表示消息已被处理,不要再往后传递,因此消息被截获

//返回值为false,表示消息未被处理,需要再往后传递,因此消息未被截获

return true;

}

return false;

}

}

CLButtonDownFilter MyFilter = new CLButtonDownFilter();

System.Windows.Forms.Application.AddMessageFilter(MyFilter);

参考出处:

http://blog.sina.com.cn/s/blog_3f39ffb50100dyee.html

http://www.cnblogs.com/mymhj/archive/2012/11/14/2770552.html

http://blog.csdn.net/yuan_hs_hf/article/details/16891585

上面的对事件的编号(就是Message.Msg属性)不清楚的,可以查看我的另外一篇文章。Message类的属性Msg所关联的消息ID

==================================================================================

01. PreTranslateMessage函数,常用于屏蔽MFC对话框中默认的Enter和ESC消息

函数原型:BOOL PreTranslateMessage(MSG* pMsg)

用法举例:

BOOL CTestDlg::PreTranslateMessage(MSG* pMsg)
{
    if(pMsg->message == WM_KEYDOWN){
        if(pMsg->wParam == VK_ESCAPE){
            return TRUE;
        }
        if(pMsg->wParam == VK_RETURN){
            return TRUE; // 对话框内部控件不可以接收到回车消息!!
        }
    }

return CDialog::PreTranslateMessage(pMsg);
}

02.响应系统按键

if(pMsg->message==WM_SYSKEYDOWN)
{
   if(pMsg->wParam==VK_MENU)
    MessageBox("alt");
}

if((pMsg->wParam==VK_F9) && (GetAsyncKeyState(VK_MENU)<0))
{  
   MessageBox(_T("同时按下了Alt键和F9键"));  
}

函数:GetAsyncKeyState()
功能:确定用户当前是否按下了键盘上的一个键
原型:SHORT GetAsyncKeyState(int vKey);

参数:nVirtKey指出要检查键的虚键代码。结果的高位指出该键当前是否被按下(是为1,否为0)。
常用键的VK值:
VK_SHIFT Shift键
VK_LSHIFT 左Shift键
VK_RSHIFT 右Shift键
VK_CONTROL Ctrl键
VK_LCONTROL 左Ctrl键
VK_RCONTROL 右Ctril键
VK_MENU Alt键
VK_LMENU 左Alt键
VK_RMENU 右Alt键
VK_LBUTTON 鼠标左键
VK_RBUTTON 鼠标右键

另一个函数GetKeyState与GetAsyncKeyState函数不同。GetAsyncKeyState在按下某键的同时调用,判断正在按下某键。
GetKeyState则在按过某键之后再调用,它返回最近的键盘消息从线程的队列中移出时的键盘状态,判断刚按过了某键。

与RegisterHotKey()相比,GetAsyncKeyState()的优点在于可以监控鼠标按键,缺点是需要使用定时器。

==========================================================================

出处:http://blog.csdn.net/bookish_2010_prj/article/details/5873896

C# Message 消息处理的更多相关文章

  1. Message类的属性Msg所关联的消息ID

    在做C#的Message消息处理的时候,用到了消息的msg编号不知道对应的是什么事件,所以才从网上找来资料如下,在文章最后我会给出资料的出处的. WM_NULL=0x0000 WM_CREATE=0x ...

  2. Delphi之静态方法,虚方法virtual,动态dynamic,抽象abstract,消息

    Delphi之静态方法,虚方法virtual,动态dynamic,抽象abstract,消息 http://www.cnblogs.com/zhwx/archive/2012/08/28/266055 ...

  3. Delphi 中同类型方法的说明

    对象的方法能定义成静态(static).虚拟(virtual).动态(dynamic)或消息处理(message).请看下面 的例子: TFoo = class procedure IAmAStati ...

  4. XCLNetTools1.0(ASP.NET常用类库)

    版权声明:本文为博主原创文章,未经博主允许不得转载. 2016-01-01开放所有源代码: 项目地址:https://github.com/xucongli1989/XCLNetTools 下载地址: ...

  5. WCF基于MSMQ的事件代理服务

    前言 公司目前楼主负责的项目正在改版升级,对之前的服务也在作调整,项目里有个操作日志的模块,就决定把日志单独提取出来,做个日志服务,所以就有了这篇文章 正文 MSMQ作为消息队列,B/S项目调用日志服 ...

  6. 一套高可用、易伸缩、高并发的IM群聊架构方案设计实践

    本文原题为“一套高可用群聊消息系统实现”,由作者“于雨氏”授权整理和发布,内容有些许改动,作者博客地址:alexstocks.github.io.应作者要求,如需转载,请联系作者获得授权. 一.引言 ...

  7. Openfire源码阅读(一)

    本篇先分析openfire源码的主要流程,模块细节后续再继续分析: 一.简介: Openfire是开源的实时协作服务器(RTC),它是基于公开协议XMPP(RFC-3920),并在此基础上实现了XMP ...

  8. 《深入理解Android 卷III》第二章 深入理解Java Binder和MessageQueue

    <深入理解Android 卷III>即将公布.作者是张大伟.此书填补了深入理解Android Framework卷中的一个主要空白,即Android Framework中和UI相关的部分. ...

  9. liteos队列(五)

    1. 概述 队列又称消息队列,是一种常用于任务间通信的数据结构,实现了接收来自任务或中断的不固定长度的消息,并根据不同的接口选择传递消息是否存放在自己空间.任务能够从队列里面读取消息,当队列中的消息是 ...

随机推荐

  1. p2p网络中的NAT穿透技术----常见NAT穿越解决方案

    转:http://blog.csdn.net/cllzw/article/details/46438257 常见NA丁穿越解决方案 NAT技术在缓解IPv4地址紧缺问题.构建防火墙.保证网络安全等方面 ...

  2. Python的return self和return一个新的对象区别

    目的:设计一个有理数相加.如3/5 + 7/15 = 80/75 return self 输入: class Rational0: def __init__(self, num, den=1): se ...

  3. [RK3288][Android6.0] 调试笔记 --- user版本默认显示开发者选项【转】

    本文转载自:https://blog.csdn.net/kris_fei/article/details/70157137 Platform: ROCKCHIPOS: Android 6.0Kerne ...

  4. php执行shell不阻塞方法

    大家都知道php执行系统命令的方法有: system() 输出并返回最后一行shell结果. exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面. passth ...

  5. json对象与字符串互转方法

    字符串转json对象: var data = eval( '(' + str + ')' ); json对象转字符串: var jsonStr = JSON.stringify( obj );

  6. 偶尔出现button不能点击的情况

    原因: 1.没有加点击事件 2.从别的项目或者别的场景导入的预制体,要更换canvas才能点击,不然没反应

  7. Request对象获得参数方法:query和body方法

    express框架Request对象获得参数方法:query和body方法 req.query 该属性用法很简单,直接获取地址栏传递的参数:例如: //引入依赖 var express = requi ...

  8. QBZT Day3(zhx ak IOI)

    动态规划 DP和前几天学的东西不大一样,动态规划和数据结构相比是一个非常抽象的东西 先来看看斐波那契数列 定义是F0=0,F1=1,Fn=F(n-1)+F(n-2) 0,1,1,2,3,5,8,13, ...

  9. 五一清北学堂培训之Day 3之DP

    今天又是长者给我们讲小学题目的一天 长者的讲台上又是布满了冰红茶的一天 ---------------------------------------------------------------- ...

  10. for...else: 一个程序引发的陷阱

    python3菜鸟教程有一段关于for循环和else搭配的代码: for n in range(2, 10): for x in range(2, n): if n % x == 0: print(n ...