如何通過自定義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的這個滾動條。
下麵是該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