閱讀875 返回首頁    go 阿裏雲 go 技術社區[雲棲]


如何通過自定義MessageFilter的方式利用按鍵方式操作控件滾動條[附源代碼]

很長一段時間內,一直在做一個SCSF(Smart Client Software Factory)的項目,已經進入UAT階段。最近,用戶提出了一個要求:需要通過按鍵方式來控製豎直滾動條。具體來講就是說,如果一個容器內容過多,用戶可以通過按鍵PageUp和PageDown來控製上下的滾動。剛開始,我試圖采用注冊事件的方式來實現,但是效果不理想,一來是沒有一個單一的地方來對所有相關空間進行事件注冊操作,二來如果容器被子控件完全遮擋,容器空間的事件將不會正常出發。有個同事提示采用自定義MessageFilter的方式,我覺得可行,於是進行了一番嚐試。

一、實現原理簡介

對於一個Windows Form應用來說,所有事件的觸發都是采用消息(Message)的方式來實現的。比如,你點擊了一個按鈕,Windows會為這個操作之生成一個消息,並將這個消息分發(Dispatch)給按鈕對象。如果能夠在消息被分發給目標對象之前,能夠對該消息進行了攔截,那麼我們就可以按照我們希望的方式從新生成一個消息,並將其發送給我希望的目標對象,那麼就能過隨心所欲地控製目標對象的行為了。而自定義MessageFilter為我們提供了一個最好的消息攔截方式。

就拿我們上麵給出控製滾動條的場景來說,當前容器由於內容過多而產生豎直滾動條(假設子控件的寬度和容器相同),用戶鍵入PageDown按鍵試圖向下滾動。Windows為本次鍵盤操作生成一個消息,並分發給目標對象(可能並不是我們需要控製的當前容器對象)。在此期間,我們通過MessageFilter對該消息實施攔截,從新產生一個基於“向下滾動”操作的消息,並分發給我們需要對其進行控製的容器,那麼就實現了對於容器空間滾動條進行控製的目的。

二、實例應用場景簡介

熟悉SCSF的朋友應該很清楚,SCSF的通過一個稱為Shell的Form作為主界麵,利用一個稱為Workspace的容器最為整個應用的工作平台。應用動態運行過程中,各個Module的界麵采用相同的方式添加到該Workspace之中。下圖的就是我們將要演示的例子運行時的截圖,為了簡單起見,我直接通過一個System.Windows.Forms.TabControl作為Workspace。主菜單的兩個菜單項分別代表兩個模塊,點擊相應的菜單項後,會把相應的界麵添加到Workspace中。在這裏,我通過System.Windows.Forms.UserControl的方式定義Customer和Order模塊的界麵,當Customer和Order菜單被點擊之後,會動態地在TabControl中添加相應的TabPage,並把相應的UserControl置於其中。由於整個TabControl的高度時固定的,而TabPage中顯示的內容則依賴於具體的邏輯,所以對於內容過多的TabPage,將會有一個豎直滾動條。而我們需要通過按鍵的方式控製的就是當前TabPage的這個滾動條。

image

下麵是該Form相關的代碼,靜態屬性ActiveTabPage代表當前顯示的TabPage。UserInfo和OrderInfo是兩個UserControl,代表與具體模塊相關的界麵呈現。

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Windows.Forms;
   4:  
   5: namespace MessageFilterDemos
   6: {
   7:     public partial class MainForm : Form
   8:     {
   9:         public static TabPage ActiveTabPage
  10:         { get;private set; }
  11:  
  12:         private IDictionary<string, UserControl> keyedViews
  13:         { get; set; }
  14:  
  15:         public MainForm()
  16:         {
  17:             InitializeComponent();
  18:             this.keyedViews = new Dictionary<string, UserControl>();
  19:         }
  20:  
  21:         protected override void OnLoad(EventArgs e)
  22:         {
  23:             base.OnLoad(e);
  24:             this.keyedViews.Add("CustomerInfo", new CustomerInfo());
  25:             this.keyedViews.Add("OrderInfo", new OrderInfo());
  26:         }
  27:  
  28:         private void Show(string key, string text, UserControl view)
  29:         {
  30:             if (!this.mainWorkspace.TabPages.ContainsKey(key))
  31:             {
  32:                 this.mainWorkspace.TabPages.Add(key, text);
  33:                 this.mainWorkspace.TabPages[key].Controls.Add(view);
  34:                 this.mainWorkspace.TabPages[key].AutoScroll = true;
  35:             }
  36:             this.mainWorkspace.SelectedTab = this.mainWorkspace.TabPages[key];
  37:             ActiveTabPage = this.mainWorkspace.TabPages[key];
  38:         }  
  39:  
  40:         private void ordeToolStripMenuItem_Click(object sender, EventArgs e)
  41:         {
  42:             this.Show("OrderInfo", "Order", keyedViews["OrderInfo"]);
  43:         }
  44:  
  45:         private void customerToolStripMenuItem_Click(object sender, EventArgs e)
  46:         {
  47:             this.Show("CustomerInfo", "Customer", keyedViews["CustomerInfo"]);
  48:         }
  49:  
  50:         private void mainWorkspace_SelectedIndexChanged(object sender, EventArgs e)
  51:         {
  52:             ActiveTabPage = this.mainWorkspace.SelectedTab;
  53:         }     
  54:     }
  55: }

 

三、自定義MessageFilter

現在我們進入重點話題,如何創建我們需要的自定義MessageFilter,由於我們這個MessageFilter旨在控製TabPag的滾動條,我們將其命名為ScrollbarControllerMessageFilter。ScrollbarControllerMessageFilter實現了接口System.Windows.Forms.IMessageFilter。下麵是IMessageFilter的定義,它僅僅包含一個唯一的成員:PreFilterMessage,對消息的攔截、篩選操作就實現在這裏。而Bool類新的返回值表示是否繼續將消息分發的目標對象。

   1: public interface IMessageFilter
   2: {
   3:     [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
   4:     bool PreFilterMessage(ref Message m);
   5: }

下麵是ScrollbarControllerMessageFilter的定義,代碼不是很複雜,在這裏隻需簡單的介紹一下流程:在PreFilterMessage方法中,先判斷當前的TabPage是否存在,如果不存在,則不加幹涉;然後通過System.Windows.Forms.Message的Msg屬性確定當前事件是否是KeyDown,如果不是則直接返回;最後根據System.Windows.Forms.Message的WParam屬性判斷當前的按鍵是否是PageUp或者PageDown,並相應的向目標對象(當前的TabPage)發送一個關於向上或者向下滾動的消息。消息的發送通過調用Native方法SendMessage實現。

   1: using System;
   2: using System.Runtime.InteropServices;
   3: using System.Windows.Forms;
   4:  
   5: namespace MessageFilterDemos
   6: {
   7:     public class ScrollbarControllerMessageFilter: IMessageFilter
   8:     {
   9:  
  10:         private const int WM_KEYDOWN = 0x100;//Key down
  11:         private const int WM_VSCROLL = 277; //Scroll
  12:         private const int SB_PAGEUP = 2; // Scroll Up
  13:         private const int SB_PAGEDOWN = 3; //Scroll Down
  14:  
  15:         #region IMessageFilter Members
  16:  
  17:         [DllImport("user32.dll")]
  18:         static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
  19:  
  20:         public bool PreFilterMessage(ref Message m)
  21:         {
  22:             if (MainForm.ActiveTabPage == null)
  23:             {
  24:                 return false;                
  25:             }
  26:  
  27:             if (WM_KEYDOWN != m.Msg)
  28:             {
  29:                 return false;
  30:             }
  31:  
  32:             if (m.WParam.ToInt32() == (int)(Keys.PageUp))
  33:             {
  34:                 SendMessage(MainForm.ActiveTabPage.Handle, WM_VSCROLL, SB_PAGEUP, 0);
  35:                 return true;
  36:             }
  37:  
  38:             if (m.WParam.ToInt32() == (int)(Keys.PageDown))
  39:             {
  40:                 SendMessage(MainForm.ActiveTabPage.Handle, WM_VSCROLL, SB_PAGEDOWN, 0);
  41:                 return true;
  42:             }
  43:  
  44:             return false;
  45:         }
  46:  
  47:         #endregion
  48:     }
  49: }

對MessageFilter的注冊很簡單,僅僅需要的是調用System.Windows.Forms.Application的AddMessageFilter方法即可。實例代碼下載地址:https://files.cnblogs.com/artech/MessageFilterDemos.zip

   1: using System;
   2: using System.Windows.Forms;
   3:  
   4: namespace MessageFilterDemos
   5: {
   6:     static class Program
   7:     {       
   8:         [STAThread]
   9:         static void Main()
  10:         {
  11:             Application.AddMessageFilter(new ScrollbarControllerMessageFilter());
  12:             Application.EnableVisualStyles();
  13:             Application.SetCompatibleTextRenderingDefault(false);
  14:             Application.Run(new MainForm());
  15:         }
  16:     }
  17: }

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 11:34:23

  上一篇:go  如何解決HP QC(Quality Center)在Windows 7下不能工作的問題
  下一篇:go  WCF技術剖析之二十三:服務實例(Service Instance)生命周期如何控製[中篇]