强制键盘焦点到 LineEdit QT

Posted

技术标签:

【中文标题】强制键盘焦点到 LineEdit QT【英文标题】:Force keyboard focus to LineEdit QT 【发布时间】:2021-08-12 22:12:47 【问题描述】:

我正在尝试为 Windows 开发一个由全局键绑定触发的覆盖弹出窗口,一旦按下键绑定,它应该将焦点捕获到 QLineEdit 中。问题是,如果它被聚焦一次,但我点击外部有效地移除了焦点,之后就无法重新获得焦点。

这是我试图用来强制键盘焦点在 QLineEdit 上的代码的简化版本:

from PySide6 import QtCore, QtWidgets, QtGui
from pynput import keyboard

class MyWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        self.input = QtWidgets.QLineEdit()
        self.layout = QtWidgets.QVBoxLayout(self)
        self.layout.addWidget(self.input)

        self.input.setWindowModality(QtCore.Qt.ApplicationModal)
        self.setWindowState(QtCore.Qt.WindowActive)
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
    
    @QtCore.Slot()
    def toggle_visible(self):
        if self.isVisible():
            print("Hiding popup")
            self.hide()
        else:
            print("Showing popup")
            self.show()
            self.activateWindow()
            self.input.grabKeyboard()
            self.input.setFocus()

class KeybindPressed(QtCore.QObject):
    keybind_pressed = QtCore.Signal()

    def __call__(self):
        self.keybind_pressed.emit()


if __name__ == "__main__":
    app = QtWidgets.QApplication([])

    pressed = KeybindPressed()
    with keyboard.GlobalHotKeys("<alt>+<space>": pressed):

        widget = MyWidget()
        pressed.keybind_pressed.connect(widget.toggle_visible)
        widget.resize(800, 600)
        widget.show()

        app.exec()

这是一段记录,显示焦点停留在其他应用程序中而不是在显示时返回窗口的不良行为。

【问题讨论】:

请提供minimal reproducible example,而不是外部链接 @eyllanesc 完成。 不幸的是,在 Windows 上,这种行为是“设计使然”。如docs for activateWindow 中所述,操作系统会主动防止焦点窃取。 Qt 提供了QWindowsWindowFunctions.setWindowActivationBehavior 作为解决此问题的方法,但是 PyQt 和 PySide 都没有实现 QWindowsWindowFunctions 类。 看起来可以使用 win32api 推出您自己的解决方案。我在 Linux 上(没有这个问题),所以我无法测试任何这些,但 an article here 有一个可能的解决方案。如果这不起作用,您可以尝试移植 Qt 的 QWindowsWindow::requestActivateWindow 函数。 @ekhumoro 谢谢你,做到了!我现在可以强制集中注意力。现在写答案。 【参考方案1】:

感谢ekhumoro 我能够弄清楚如何强制专注于窗口。下面是强制聚焦所需的代码:

导入和设置

windows = False
if os.name == "nt":
    import win32gui, win32con, win32process, win32api
    win32gui.SystemParametersInfo(win32con.SPI_SETFOREGROUNDLOCKTIMEOUT, 0, win32con.SPIF_SENDWININICHANGE | win32con.SPIF_UPDATEINIFILE)
    windows = True

实际强制焦点的代码,当您的窗口应该获得焦点时会调用它:

def force_focus(qt_widget_instance: QtWidgets.QWidget):
    if windows:
        fgwin = win32gui.GetForegroundWindow()
        fg = win32process.GetWindowThreadProcessId(fgwin)[0]
        current = win32api.GetCurrentThreadId()
        if current != fg:
            win32process.AttachThreadInput(fg, current, True)
            win32gui.SetForegroundWindow(qt_widget_instance.winId())
            win32process.AttachThreadInput(fg, win32api.GetCurrentThreadId(), False)

这是实现此功能的完整示例:

from PySide6 import QtCore, QtWidgets, QtGui
from pynput import keyboard

import os

windows = False
if os.name == "nt":
    import win32gui, win32con, win32process, win32api
    win32gui.SystemParametersInfo(win32con.SPI_SETFOREGROUNDLOCKTIMEOUT, 0, win32con.SPIF_SENDWININICHANGE | win32con.SPIF_UPDATEINIFILE)
    windows = True

def force_focus(qt_widget_instance: QtWidgets.QWidget):
    if windows:
        fgwin = win32gui.GetForegroundWindow()
        fg = win32process.GetWindowThreadProcessId(fgwin)[0]
        current = win32api.GetCurrentThreadId()
        if current != fg:
            win32process.AttachThreadInput(fg, current, True)
            win32gui.SetForegroundWindow(qt_widget_instance.winId())
            win32process.AttachThreadInput(fg, win32api.GetCurrentThreadId(), False)

class MyWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        self.input = QtWidgets.QLineEdit()
        self.layout = QtWidgets.QVBoxLayout(self)
        self.layout.addWidget(self.input)

        self.input.setWindowModality(QtCore.Qt.ApplicationModal)
        self.setWindowState(QtCore.Qt.WindowActive)
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
    
    @QtCore.Slot()
    def toggle_visible(self):
        if self.isVisible():
            print("Hiding popup")
            self.hide()
        else:
            print("Showing popup")
            self.show()
            force_focus(self)
            self.activateWindow()
            self.input.grabKeyboard()
            self.input.setFocus()



class KeybindPressed(QtCore.QObject):
    keybind_pressed = QtCore.Signal()

    def __call__(self):
        self.keybind_pressed.emit()


if __name__ == "__main__":
    app = QtWidgets.QApplication([])

    pressed = KeybindPressed()
    with keyboard.GlobalHotKeys("<cmd>+<space>": pressed):
    
        widget = MyWidget()
        pressed.keybind_pressed.connect(widget.toggle_visible)
        widget.resize(800, 600)
        widget.show()

        app.exec()

【讨论】:

以上是关于强制键盘焦点到 LineEdit QT的主要内容,如果未能解决你的问题,请参考以下文章

位于虚拟键盘下方的 iOS 7.1 输入字段强制缩放焦点

在 iPad Safari 中强制隐藏键盘

WPF强制设置TextBox的焦点

Qt全屏窗口隐藏Win8/10触摸键盘

如何使用javascript强制关闭android键盘

强制 UITextView 光标到视图顶部