在嵌入式应用和一些安全软件中经常需要不通过物理键盘输入,虽然微软提供了也一个软键盘,但这个软件盘不能定制界面不能自动感应当前光标是否处于输入状态,所以有时候我们还是需要自己来实现这个软键盘。本文将讲解自己实现软键盘时涉及到的几个关键技术。
一、浮动窗体的实现
软键盘的窗体和普通窗体不一样,这个窗体在成为当前窗体时,不会影响其它进程的窗体的光标焦点。也就是说虽然这个窗体现在为当前激活的前台窗体,但光标仍然停在其他进程的窗体上。
如上图所示,虽然软键盘在记事本的前面,但光标仍然在记事本上。
要实现这个技术,我们必须要把当前窗体设置为浮动工具条才行。这里我给出 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;
}
}
.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }
如上代码就是将 Winform 指定为浮动工具条窗体。只要在winform 的类中重载 CreateParams 函数,并按上述代码编写就可以了。
二、如何检测当前处于输入状态
在一些嵌入式设备中,我们没有物理键盘,所有的输入都是通过触摸屏和软键盘输入。那么这个时候,我们必须要做到只有处于输入状态时才弹出软键盘,否则如果软键盘一直在界面上,既不美观也妨碍其他程序的正常使用。
要实现这个功能,我们能想到的最直接的方法是 windows 是否会在当前处于输入状态下时发一个什么事件,或者通过什么钩子程序来实现。但我研究了很久,没有找到这种方法。如果哪位知道这种方法,不妨在回复中告诉我。
我目前找到的方法是定时询问 windows 的当前窗体是否处于输入状态(http://www.my400800.cn )。
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;
}
.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); } 上面是 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);
.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }
三、模拟键盘输入
模拟键盘输入比较简单,.Net 提供了一个静态函数来模拟键盘输入
System.Windows.Forms.SendKeys.Send
这个函数很简单,而且微软的帮助也很全面了,我这里就不多说了。
另外我们还可以用更加底层的 API 函数来模拟键盘的输入
[DllImport("user32.dll"
)]
static
extern
void
keybd_event(byte
bVk, byte
bScan, uint
dwFlags,
int
dwExtraInfo);
.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }
这个函数是 keybd_event,关于这个函数的使用,微软的帮助也写的很清楚,这里也不重述了。