1、消息概述
Windows下應用程序的執行是經過消息驅動的。消息是整個應用程序的工做引擎,咱們須要理解掌握咱們使用的編程語言是如何封裝消息的原理。
1 什麼是消息(Message)
消息就是通知和命令。在.NET框架類庫中的System.Windows.Forms命名空間中微軟採用面對對象的方式從新定義了Message。新的消息(Message)結構的公共部分屬性基本與早期的同樣,不過它是面對對象的。
公共屬性:
HWnd 獲取或設定消息的處理函數
Msg 獲取或設定消息的ID號
Lparam 指定消息的LParam字段
Wparam 指定消息的WParam字段
Result 指定爲響應消息處理函數而向OS系統返回的值
2 消息驅動的過程
全部的外部事件,如鍵盤輸入、鼠標移動、按動鼠標都由OS系統轉換成相應的消息發送到應用程序的消息隊列。每一個應用程序都有一段相應的程序代碼來檢索、分發這些消息到對應的窗體,而後由窗體的處理函數來處理。
2、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_MouseDown一、Form1_MouseDown2在咱們單擊鼠標左鍵的時候都會被調用,並且調用的順序和咱們添加委託的順序一致。
WM_LBUTTONDOWN消息首先被Application類從應用程序消息隊列中取出,而後分發到相應的窗體。窗體使用MouseDown事件中的函數指針調用已經添加的響應函數。因此C#中的事件字段實質上是一個函數指針列表,用來維護一些消息到達時的響應函數的地址。
3、結論
C#中消息的工做流程:
C#中的消息被Application類從應用程序消息隊列中取出,而後分發到消息對應的窗體,窗體對象的第一個響應函數是對象中的protected override void WndProc(ref System.Windows.Forms.Message e)方法。
它再根據消息的類型調用默認的消息響應函數(如OnMouseDown),默認的響應函數而後根據對象的事件字段(如this.MouseDown )中的函數指針列表,調用用戶所加入的響應函數(如Form1_MouseDown1和Form1_MouseDown2),並且調用順序和用戶添加順序一致。
4、再回首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);
這是我本身在作項目中實現將關閉窗口變爲隱藏窗口的代碼:
//關閉窗體-> 隱藏窗體
protected override void WndProc(ref Message msg)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_CLOSE = 0xF060;
if (msg.Msg == WM_SYSCOMMAND && ((int)msg.WParam == SC_CLOSE))
{
this.Hide();
return;
}
base.WndProc(ref msg);
}