在嵌入式應用和一些安全軟件中經常需要不通過物理鍵盤輸入,雖然微軟提供了也一個軟鍵盤,但這個軟件盤不能定製界面不能自動感應當前光標是否處於輸入狀態,所以有時候我們還是需要自己來實現這個軟鍵盤。本文將講解自己實現軟鍵盤時涉及到的幾個關鍵技術。
一、浮動窗體的實現
軟鍵盤的窗體和普通窗體不一樣,這個窗體在成爲當前窗體時,不會影響其它進程的窗體的光標焦點。也就是說雖然這個窗體現在爲當前激活的前臺窗體,但光標仍然停在其他進程的窗體上。
如上圖所示,雖然軟鍵盤在記事本的前面,但光標仍然在記事本上。
要實現這個技術,我們必須要把當前窗體設置爲浮動工具條纔行。這裏我給出 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 函數,並按上述代碼編寫就可以了。
二、如何檢測當前處於輸入狀態
在一些嵌入式設備中,我們沒有物理鍵盤,所有的輸入都是通過觸摸屏和軟鍵盤輸入。那麼這個時候,我們必須要做到只有處於輸入狀態時才彈出軟鍵盤,否則如果軟鍵盤一直在界面上,既不美觀也妨礙其他程序的正常使用。
要實現這個功能,我們能想到的最直接的方法是 windows 是否會在當前處於輸入狀態下時發一個什麼事件,或者通過什麼鉤子程序來實現。但我研究了很久,沒有找到這種方法。如果哪位知道這種方法,不妨在回覆中告訴我。
我目前找到的方法是定時詢問 windows 的當前窗體是否處於輸入狀態。
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,則表示當前窗體處於可輸入狀態。
相關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 提供了一個靜態函數來模擬鍵盤輸入
System.Windows.Forms.SendKeys.Send
這個函數很簡單,而且微軟的幫助也很全面了,我這裏就不多說了。
另外我們還可以用更加底層的 API 函數來模擬鍵盤的輸入
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags,
int dwExtraInfo);
這個函數是 keybd_event,關於這個函數的使用,微軟的幫助也寫的很清楚,這裏也不重述了。