在嵌入式應用和一些安全軟件中經常需要不通過物理鍵盤輸入,雖然微軟提供了也一個軟鍵盤,但這個軟件盤不能定制界面不能自動感應當前光標是否處于輸入狀態(tài),所以有時候我們還是需要自己來實現這個軟鍵盤。本文將講解自己實現軟鍵盤時涉及到的幾個關鍵技術。
一、浮動窗體的實現
軟鍵盤的窗體和普通窗體不一樣,這個窗體在成為當前窗體時,不會影響其它進程的窗體的光標焦點。也就是說雖然這個窗體現在為當前激活的前臺窗體,但光標仍然停在其他進程的窗體上。
如上圖所示,雖然軟鍵盤在記事本的前面,但光標仍然在記事本上。
要實現這個技術,我們必須要把當前窗體設置為浮動工具條才行。這里我給出 C# Winform 的實現方法:
private const int WS_EX_TOOLWINDOW = 0x00000080; private const int WS_EX_NOACTIVATE = 0x08000000; protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.ExStyle |= (WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW); cp.Parent = IntPtr.Zero; // Keep this line only if you used UserControl return cp; //return base.CreateParams; } }
如上代碼就是將 Winform 指定為浮動工具條窗體。只要在winform 的類中重載 CreateParams 函數,并按上述代碼編寫就可以了。
二、如何檢測當前處于輸入狀態(tài)
在一些嵌入式設備中,我們沒有物理鍵盤,所有的輸入都是通過觸摸屏和軟鍵盤輸入。那么這個時候,我們必須要做到只有處于輸入狀態(tài)時才彈出軟鍵盤,否則如果軟鍵盤一直在界面上,既不美觀也妨礙其他程序的正常使用。
要實現這個功能,我們能想到的最直接的方法是 windows 是否會在當前處于輸入狀態(tài)下時發(fā)一個什么事件,或者通過什么鉤子程序來實現。但我研究了很久,沒有找到這種方法。如果哪位知道這種方法,不妨在回復中告訴我。
我目前找到的方法是定時詢問 windows 的當前窗體是否處于輸入狀態(tài)。
IntPtr hWnd = GetForegroundWindow(); uint processId; uint threadid = GetWindowThreadProcessId(hWnd, out processId); GUITHREADINFO lpgui = new GUITHREADINFO(); lpgui.cbSize = Marshal.SizeOf(lpgui); if (GetGUIThreadInfo(threadid, ref lpgui)) { if (lpgui.hwndCaret != 0) { return hWnd; } }
如上面代碼所示
首先我們通過 GetForegroundWindows API 得到當前窗體的句柄。然后我們再通過 GetGUIThreadInfo 得到當前窗體的一些屬性。這些屬性在 GUITHREADINFO 中定義
public struct GUITHREADINFO { public int cbSize; public int flags; public int hwndActive; public int hwndFocus; public int hwndCapture; public int hwndMenuOwner; public int hwndMoveSize; public int hwndCaret; public System.Drawing.Rectangle rcCaret; }
上面是 GUITHREADINFO 結構。我們可以通過這個信息得到當前窗體中當前焦點的子窗口句柄,當前獲得光標的子窗口句柄,當前正激活的子窗體句柄等等。這里我們只要用到當前獲得光標的子窗口句柄,就是 hwndCaret 。如果hwndCaret 不為0,則表示當前窗體處于可輸入狀態(tài)。
相關API函數的 C# 定義如下:
[DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetGUIThreadInfo(uint idThread, ref GUITHREADINFO lpgui); [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", SetLastError = true)] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
三、模擬鍵盤輸入
模擬鍵盤輸入比較簡單,.Net 提供了一個靜態(tài)函數來模擬鍵盤輸入
System.Windows.Forms.SendKeys.Send
這個函數很簡單,而且微軟的幫助也很全面了,我這里就不多說了。
另外我們還可以用更加底層的 API 函數來模擬鍵盤的輸入
[DllImport("user32.dll")] static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
這個函數是 keybd_event,關于這個函數的使用,微軟的幫助也寫的很清楚,這里也不重述了。