如何为Windows制作T9风格的屏幕键盘?

3 投票
1 回答
2628 浏览
提问于 2025-04-16 00:46

有时候晚上,我喜欢在床上看电影或者在线追剧。这很方便,因为我的电脑就在桌子旁边,我只需要把其中一个显示器转过来,关闭另一个屏幕,然后把鼠标移过去。可是我的键盘不太够长,重新布线又不方便,第二天回到桌子上就用不了。有时候我在看电影的时候,朋友们想跟我聊天,我希望能在不站起来、转动显示器、移动鼠标再坐回椅子上的情况下,跟他们聊聊。

我想做一个屏幕上的虚拟键盘,用鼠标来操作——但我希望它能像老式手机的T9键盘那样设计,这样可以减少点击次数和移动鼠标的麻烦,避免点不到目标。我想用Python来做这个,因为我对这个语言比较熟悉,但我不知道从哪里开始。

我不太确定的是,如何在不影响聊天窗口的情况下点击这个虚拟键盘。这样可以实现吗?或者这个应用能不能记住上一个聚焦的控件,然后把输入发送给它?

另外,我需要外部库来管理窗口和发送输入吗?

非常感谢任何帮助,如果已经有类似的东西(用任何语言),指引我去找也非常感谢。

如果我开发出来,我一定会开源,并在这里分享项目链接,以防其他人觉得这个东西有用 :)

1 个回答

3

大约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这个方法效果不是很好(我根本没法让它工作)。

我还找到了一篇关于识别最后一个活动窗口的文章。

撰写回答