最近真的真的太忙了,以至於一個多月都沒喲更新我的blog。昨天晚上,一個網上的朋友看了我的 ToolBox的文章,問我一個問題,他說如何讓ToolBox控件也能響應鍵盤操作,也就是用Up,down按鍵來選 擇工具箱控件裡的Item,他添加了鍵盤事件,但是不起作用。一開始做這個控件的時候也只是演示一下控 件的制作過程,只用了很短的時間做了一個,只考慮了用鼠標選取,沒有考慮鍵盤操作,我想要添加鍵盤 操作無非重載KeyDown事件,針對Up,Down做一些響應就可以了。可是添加了重載了OnKeyDown事件後,結 果和那位朋友所說的一樣,沒有任何作用,我設了斷點,調試了一下,發現KeyDown根本捕獲不到Up, Down按鍵的點擊,是什麼原因呢,是不是忘記設控件的風格以便讓它能夠獲得焦點?於是,我使用了語句 :
SetStyle(ControlStyles.Selectable, true);依然沒有效果,當我們在控件上按下Down鍵的時候,另 一個控件獲得了焦點。這時Up,Down按鈕只是起到了導航的作用就像Tab鍵一樣。
接下來,我在測試工程的窗體上放置了一個ListBox控件做一個對比,其實ToolBox和ListBox在界面表 現上有相似之處,就是都有子Item,並且在ListBox上點擊Down是起作用的,ListBox並沒有失去焦點,這 說明這時Up,Down按鍵沒有成為導航鍵。我想Windows一定是對默認的導航鍵Up,Down,Left,Right有默認 的處理,除非你希望你的控件希望自己處理這些鍵。用反匯編工具看了一下ListBoxControl控件的源代碼 ,發現一個有趣的函數:
protected override bool IsInputKey(Keys keyData) { if ((keyData & Keys.Alt) == Keys.Alt) { return false; } switch ((keyData & Keys.KeyCode)) { case Keys.Prior: case Keys.Next: case Keys.End: case Keys.Home: return true; } return base.IsInputKey(keyData); }
在這裡面,ListBoxControl允許Prior,Next,End,Home成為有效的輸入鍵,接著一路跟下去,看看 WinForm控件的基類Control的這個函數是如何處理的:
[UIPermission(SecurityAction.InheritanceDemand, Window=UIPermissionWindow.AllWindows)] protected virtual bool IsInputKey(Keys keyData) { if ((keyData & Keys.Alt) != Keys.Alt) { int num = 4; switch ((keyData & Keys.KeyCode)) { case Keys.Left: case Keys.Up: case Keys.Right: case Keys.Down: num = 5; break; case Keys.Tab: num = 6; break; } if (this.IsHandleCreated) { return ((((int) this.SendMessage(0x87, 0, 0)) & num) != 0); } } return false; }
注意這一行return ((((int) this.SendMessage(0x87, 0, 0)) & num) != 0);0x87是什麼 windows消息呢,打開WinUser.h文件,發現是WM_GETDLGCODE,在MSDN中的描述是這樣的:
The WM_GETDLGCODE message is sent to the window procedure associated with a control. By default, the system handles all keyboard input to the control; the system interprets certain types of keyboard input as dialog box navigation keys. To override this default behavior, the control can respond to the WM_GETDLGCODE message to indicate the types of input it wants to process itself.
也就是說windows用這個消息來判斷哪些類型的輸入交給控件本身來處理。然後,我注意到,對於方向 導航鍵,函數都給於一個值5與this.SendMessage(0x87, 0, 0))的返回值進行與操作,那麼 this.SendMessage(0x87, 0, 0))的返回值都可能是什麼值呢,WinUser.h中是這樣聲明的:
/**//* * Dialog Codes */ #define DLGC_WANTARROWS 0x0001 /* Control wants arrow keys */ #define DLGC_WANTTAB 0x0002 /* Control wants tab keys */ #define DLGC_WANTALLKEYS 0x0004 /* Control wants all keys */ #define DLGC_WANTMESSAGE 0x0004 /* Pass message to control */ #define DLGC_HASSETSEL 0x0008 /* Understands EM_SETSEL message */ #define DLGC_DEFPUSHBUTTON 0x0010 /* Default pushbutton */ #define DLGC_UNDEFPUSHBUTTON 0x0020 /* Non-default pushbutton */ #define DLGC_RADIOBUTTON 0x0040 /* Radio button */ #define DLGC_WANTCHARS 0x0080 /* Want WM_CHAR messages */ #define DLGC_STATIC 0x0100 /* Static item: don't include */ #define DLGC_BUTTON 0x2000 /* Button item: can be checked */ 5最貼切的表達就是DLGC_WANTMESSAGE |
DLGC_WANTARROWS,也就是將方向鍵發送給控件處理,對於6呢,也就是DLGC_WANTMESSAGE| DLGC_WANTTAB,將Tab鍵發送給控件處理。
從這段代碼裡和控件實際的行為我們可以得出一個結論,那就是,控件本身是不處理方向鍵和Tab鍵的 ,因為他們有默認的行為,也就是支持焦點在窗體的控件之間轉換。如果你想要處理這些導航鍵,那麼結 論很簡單,就是重載IsInputKey方法,它是一個保護類型的虛方法。
在ToolBox控件的代碼裡重載IsinputKey方法:
protected override bool IsInputKey(Keys keyData) { if ((keyData & Keys.Alt) == Keys.Alt) { return false; } switch ((keyData & Keys.KeyCode)) { case Keys.Up: case Keys.Down: return true; } return base.IsInputKey(keyData); }
當用戶點擊的鍵是Up,Down的時候,返回true,這時我們的OnKeyDown方法裡就可以捕獲到Up,Down的 點擊事件了。