1. 程式人生 > >C# 訊息處理機制及自定義過濾方式

C# 訊息處理機制及自定義過濾方式

一、訊息概述 Windows 下應用程式的執行是通過訊息驅動的。訊息是整個應用程式的工作引擎,我們需要理解掌握我們使用的程式語言是如何封裝訊息的原理。1. 什麼是訊息(Message) 訊息就是通知和命令。在.NET框架類庫中的System.Windows.Forms名稱空間中微軟採用面對物件的方式重新定義了Message。新的訊息(Message)結構的公共部分屬性基本與早期的一樣,不過它是面對物件的。 公共屬性:

public IntPtr HWnd { get; set; } 獲取或設定訊息的視窗控制代碼
public int Msg { get; set; } 獲取或設定訊息的 ID 號
public IntPtr Result { get; set; } 指定為響應訊息處理而向 Windows 返回的值
public IntPtr LParam { get; set; } 指定訊息的 System.Windows.Forms.Message.LParam 欄位
public IntPtr WParam { get; set; } 獲取或設定訊息的 System.Windows.Forms.Message.WParam 欄位

2. 訊息驅動的過程 所有的外部事件,如鍵盤輸入、滑鼠移動、按動滑鼠都由OS系統轉換成相應的訊息傳送到應用程式的訊息佇列。每個應用程式都有一段相應的程式程式碼來檢索、分發這些訊息到對應的窗體,然後由窗體的處理函式來處理。

二、C#中的訊息的封裝 C#對訊息重新進行了面對物件的封裝,在C#中訊息被封裝成了事件。System.Windows.Forms.Application 類具有用於啟動和停止應用程式和執行緒以及處理Windows訊息的方法。

呼叫Run以啟動當前執行緒上的應用程式訊息迴圈,並可以選擇使其窗體可見。 呼叫Exit或ExitThread來停止訊息迴圈。 C#中用Application類來處理訊息的接收和傳送。訊息的迴圈是由它負責的。 從本質上來講,每個窗體一般都對應一個窗體過程處理函式。那麼,C#的一個Form例項(相當於一個窗體)收到訊息後是如何處理訊息的?其實,這個問題的分析也就是展示了C#的訊息封裝原理。 實現滑鼠左鍵按下的訊息的響應(WM_LBUTTONDOWN)

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        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;
 
// 摘要:
//     表示將處理窗體、控制元件或其他元件的 MouseDown、MouseUp 或 MouseMove 事件的方法。
//
// 引數:
//   sender:
//     事件源。
//
//   e:
//     包含事件資料的 System.Windows.Forms.MouseEventArgs。
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.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
 
namespace WindowsFormsApplication1
{
    /// <summary>
    /// 實現訊息過濾器介面
    /// </summary>
    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;
        }
    }
 
 
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
 
            // 安裝自定義訊息過濾器
            CLButtonDownFilter MyFilter = new CLButtonDownFilter();
            Application.AddMessageFilter(MyFilter);
 
            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函式響應");
        }
 
        /// <summary>
        /// 通過覆蓋基類的窗體函式攔截訊息
        /// </summary>
        /// <param name="e"></param>
        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);
        }
 
        /// <summary>
        /// 通過覆蓋基類的事件引發函式攔截訊息
        /// </summary>
        /// <param name="e"></param>
        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);
        }
    }
}

以上程式碼我們首先用類 CLButtonDownFilter 實現了 IMessageFilter 介面,在WinForm初始化的時候我們安裝了訊息篩選器。程式實際執行的時候,在點選滑鼠左鍵的時候,程式僅僅會彈出一個"App中滑鼠左鍵按下"的訊息框。因為我們在訊息發往窗體前攔截了它,所以窗體將接收不到WM_LBUTTONDOWN訊息。 如果我們把這個程式碼塊改為返回 false,

image  那麼,我們在Application類處理訊息後,訊息將繼續發往窗體。窗體的函式將可以處理此訊息。程式執行效果是順序彈出 5 個訊息框。 1:<<App中滑鼠左鍵按下>> 2:<<訊息被WndProc函式響應>> 3:<<訊息被OnMouseDown函式響應>> 4:<<訊息被Form1_MouseDown1函式響應>> 5:<<訊息被Form1_MouseDown2函式響應>>

其實本文中已經說的挺詳細的.彈出的對話方塊只是為了讓你更直觀的看出導致的結果.