如何为Windows制作T9风格的屏幕键盘?
有时候晚上,我喜欢在床上看电影或者在线追剧。这很方便,因为我的电脑就在桌子旁边,我只需要把其中一个显示器转过来,关闭另一个屏幕,然后把鼠标移过去。可是我的键盘不太够长,重新布线又不方便,第二天回到桌子上就用不了。有时候我在看电影的时候,朋友们想跟我聊天,我希望能在不站起来、转动显示器、移动鼠标再坐回椅子上的情况下,跟他们聊聊。
我想做一个屏幕上的虚拟键盘,用鼠标来操作——但我希望它能像老式手机的T9键盘那样设计,这样可以减少点击次数和移动鼠标的麻烦,避免点不到目标。我想用Python来做这个,因为我对这个语言比较熟悉,但我不知道从哪里开始。
我不太确定的是,如何在不影响聊天窗口的情况下点击这个虚拟键盘。这样可以实现吗?或者这个应用能不能记住上一个聚焦的控件,然后把输入发送给它?
另外,我需要外部库来管理窗口和发送输入吗?
非常感谢任何帮助,如果已经有类似的东西(用任何语言),指引我去找也非常感谢。
如果我开发出来,我一定会开源,并在这里分享项目链接,以防其他人觉得这个东西有用 :)
1 个回答
大约12年前,我为Windows写了一个程序,它会在系统托盘里运行,当某个窗口获得焦点时,它会发送按键到那个窗口。现在我已经没有代码了,也忘记了所有细节。
不过,整个过程大概是这样的。
如果你想做一个图形界面(GUI),如果用Python的话,可能会想用PyQT或者wxPython。这两个库让你写图形界面应用变得简单(当然,你也可以直接使用Windows的API)。
不过如果是我,我会先不做图形界面编程,而是使用PythonWin。用它的图形界面工具(源代码里有很多例子)来创建一个简单的对话框(也是一个Window
),来处理事件。
对于你的应用程序,选择一个target
窗口可能有几种方法。虚拟键盘窗口可能需要抢占焦点(这样才能接收鼠标事件),但它需要知道要把按键发送到哪个窗口。
- 你可以在对话框里放一个下拉菜单,让你选择目标窗口(你可以很容易地获取每个窗口的标题来选择目标),或者
- 当你的窗口获得焦点时(有一个事件可以捕捉,类似于
WM_FOCUS
),你可以查询最后一个获得焦点的窗口,或者你可以跟踪哪些窗口获得了焦点,然后使用你最后注意到的那个窗口。
无论哪种情况,一旦你有了目标窗口的句柄,你就可以使用SendMessage
将按键发送到目标窗口。我建议一开始只发送普通的按键,等以后再考虑捕捉鼠标点击。
编辑 我能够拼凑出这个代码来将按键发送到另一个窗口。
import win32ui
import win32con
import time
from ctypes import *
PUL = POINTER(c_ulong)
class KeyBdInput(Structure):
_fields_ = [("wVk", c_ushort),
("wScan", c_ushort),
("dwFlags", c_ulong),
("time", c_ulong),
("dwExtraInfo", PUL)]
class HardwareInput(Structure):
_fields_ = [("uMsg", c_ulong),
("wParamL", c_short),
("wParamH", c_ushort)]
class MouseInput(Structure):
_fields_ = [("dx", c_long),
("dy", c_long),
("mouseData", c_ulong),
("dwFlags", c_ulong),
("time",c_ulong),
("dwExtraInfo", PUL)]
class Input_I(Union):
_fields_ = [("ki", KeyBdInput),
("mi", MouseInput),
("hi", HardwareInput)]
class Input(Structure):
_fields_ = [("type", c_ulong),
("ii", Input_I)]
def send_char(char):
FInputs = Input * 1
extra = c_ulong(0)
ii_ = Input_I()
KEYEVENTF_UNICODE = 0x4
ii_.ki = KeyBdInput( 0, ord(char), KEYEVENTF_UNICODE, 0, pointer(extra) )
x = FInputs( ( 1, ii_ ) )
windll.user32.SendInput(1, pointer(x), sizeof(x[0]))
if __name__ == '__main__':
wnd = win32ui.FindWindow(None, '* Untitled - Notepad2 (Administrator)')
type_this = 'jaraco'
wnd.SetFocus()
wnd.SetForegroundWindow()
for char in type_this:
send_char(char)
我发现PostMessage这个方法效果不是很好(我根本没法让它工作)。
我还找到了一篇关于识别最后一个活动窗口的文章。