在 PyQt5/Pyside2 中窃取焦点
Steal focus in PyQt5/Pyside2
我正在创建一个 Albert, Alfred or uLauncher 风格的启动器。我的应用程序 运行 在后台运行,并在按下热键时显示。我使用 pynput
来听热键。我不能使用 PyQt5 热键的功能(不是吗?)因为我需要在系统范围内监听键盘事件,而不仅仅是应用程序的范围。
按下快捷方式时,它会调用我的小部件的 show() 方法。唯一的问题是,尽管使用了 raise_
、setFocus
和 activateWindow
.
,但我无法将焦点重新放在 window 上
我找到了一个(丑陋的)解决方法,包括打开一个 QMessageBox(+ 调整它的外观使其不可见,但我没有把它放在示例代码中)并在之后立即关闭它。
当我在 Linux 上工作时,该解决方法正在完成工作,我准备好忘记它是多么丑陋,因为它完成了工作。但是我切换到 Windows(我的应用程序也必须在 运行 上),现在这个厚颜无耻的把戏似乎导致我的应用程序冻结然后崩溃。因果报应?当然可以。
无论如何,如果我的应用程序不能吸引焦点,它就毫无用处,所以我问了两个问题,我很高兴只解决一个问题。 :)
- 你知道为什么显示 QMessageBox 会导致崩溃吗?
- 你知道让注意力重新回到我的应用程序上的任何其他方法吗?
这是一个示例代码。
非常感谢:)
编辑:我刚刚发现即使停用 QMessageBox 解决方法,应用程序最终也会崩溃(在调用热键 5、20、30 次之后)。所以问题也可能出在我将快捷方式绑定到 GUI 的方式上,我担心线程问题,但这超出了我的知识范围:/
import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent
from pynput import keyboard
class Launcher(QLineEdit):
def __init__(self):
super().__init__()
self.setFixedSize(QSize(600, 50))
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setPlaceholderText('Search...')
self.installEventFilter(self)
self.set_shortcut('<ctrl>+`')
def set_shortcut(self, shortcut):
def for_canonical(f):
return lambda k: f(listener.canonical(k))
hotkey = keyboard.HotKey(
keyboard.HotKey.parse(shortcut),
self.wake_up)
listener = keyboard.Listener(
on_press=for_canonical(hotkey.press),
on_release=for_canonical(hotkey.release))
listener.start()
def wake_up(self):
print('Waking up')
self.show()
self.cheeky_focus_stealer()
def cheeky_focus_stealer(self):
self.setFocus()
self.raise_()
self.activateWindow()
# Working of linux, but causes freeze/crash on Windows 10
message_box = QMessageBox(self)
message_box.show()
message_box.hide()
def eventFilter(self, obj, event):
if obj is self and event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Escape:
self.hide()
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Launcher()
window.show()
app.exec_()
if __name__ == "__main__":
main()
我发现了我的错误,所以我在这里发布了一段更新的代码,因为它可能对任何试图将全局热键绑定到影响 GUI 的函数(也就是两个不同的线程通信)的人有所帮助。
我的错误确实是将热键触发的操作直接绑定到我的 show()
方法,这意味着 pynput
侦听器线程将尝试与 QApplication
进行通信。
诀窍是使用 pyqtSignal()
并要求它触发 show()
方法。信号本身由热键触发。
以干净的方式完成后,我的 cheeky_focus_stealer
再次工作,因为它是来自 GUI 线程的 运行。
import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent, QObject, pyqtSignal
from pynput import keyboard
class Forwarder(QObject):
signal = pyqtSignal()
class Launcher(QLineEdit):
def __init__(self):
super().__init__()
self.setFixedSize(QSize(600, 50))
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setPlaceholderText('Search...')
self.installEventFilter(self)
self.set_shortcut('<ctrl>+`')
def set_shortcut(self, shortcut):
# The forwarder must be parented to the Launcher
forwarder = Forwarder(parent=self)
forwarder.signal.connect(self.wake_up)
def for_canonical(f):
return lambda k: f(listener.canonical(k))
hotkey = keyboard.HotKey(
keyboard.HotKey.parse(shortcut),
forwarder.signal.emit)
listener = keyboard.Listener(
on_press=for_canonical(hotkey.press),
on_release=for_canonical(hotkey.release))
listener.start()
def wake_up(self):
print('Waking up')
self.show()
self.cheeky_focus_stealer()
def cheeky_focus_stealer(self):
self.setFocus()
self.raise_()
self.activateWindow()
# Working of linux, but causes freeze/crash on Windows 10
message_box = QMessageBox(self)
message_box.show()
message_box.hide()
def eventFilter(self, obj, event):
if obj is self and event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Escape:
self.hide()
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Launcher()
window.show()
app.exec_()
if __name__ == "__main__":
main()
我正在创建一个 Albert, Alfred or uLauncher 风格的启动器。我的应用程序 运行 在后台运行,并在按下热键时显示。我使用 pynput
来听热键。我不能使用 PyQt5 热键的功能(不是吗?)因为我需要在系统范围内监听键盘事件,而不仅仅是应用程序的范围。
按下快捷方式时,它会调用我的小部件的 show() 方法。唯一的问题是,尽管使用了 raise_
、setFocus
和 activateWindow
.
我找到了一个(丑陋的)解决方法,包括打开一个 QMessageBox(+ 调整它的外观使其不可见,但我没有把它放在示例代码中)并在之后立即关闭它。
当我在 Linux 上工作时,该解决方法正在完成工作,我准备好忘记它是多么丑陋,因为它完成了工作。但是我切换到 Windows(我的应用程序也必须在 运行 上),现在这个厚颜无耻的把戏似乎导致我的应用程序冻结然后崩溃。因果报应?当然可以。
无论如何,如果我的应用程序不能吸引焦点,它就毫无用处,所以我问了两个问题,我很高兴只解决一个问题。 :)
- 你知道为什么显示 QMessageBox 会导致崩溃吗?
- 你知道让注意力重新回到我的应用程序上的任何其他方法吗?
这是一个示例代码。
非常感谢:)
编辑:我刚刚发现即使停用 QMessageBox 解决方法,应用程序最终也会崩溃(在调用热键 5、20、30 次之后)。所以问题也可能出在我将快捷方式绑定到 GUI 的方式上,我担心线程问题,但这超出了我的知识范围:/
import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent
from pynput import keyboard
class Launcher(QLineEdit):
def __init__(self):
super().__init__()
self.setFixedSize(QSize(600, 50))
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setPlaceholderText('Search...')
self.installEventFilter(self)
self.set_shortcut('<ctrl>+`')
def set_shortcut(self, shortcut):
def for_canonical(f):
return lambda k: f(listener.canonical(k))
hotkey = keyboard.HotKey(
keyboard.HotKey.parse(shortcut),
self.wake_up)
listener = keyboard.Listener(
on_press=for_canonical(hotkey.press),
on_release=for_canonical(hotkey.release))
listener.start()
def wake_up(self):
print('Waking up')
self.show()
self.cheeky_focus_stealer()
def cheeky_focus_stealer(self):
self.setFocus()
self.raise_()
self.activateWindow()
# Working of linux, but causes freeze/crash on Windows 10
message_box = QMessageBox(self)
message_box.show()
message_box.hide()
def eventFilter(self, obj, event):
if obj is self and event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Escape:
self.hide()
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Launcher()
window.show()
app.exec_()
if __name__ == "__main__":
main()
我发现了我的错误,所以我在这里发布了一段更新的代码,因为它可能对任何试图将全局热键绑定到影响 GUI 的函数(也就是两个不同的线程通信)的人有所帮助。
我的错误确实是将热键触发的操作直接绑定到我的 show()
方法,这意味着 pynput
侦听器线程将尝试与 QApplication
进行通信。
诀窍是使用 pyqtSignal()
并要求它触发 show()
方法。信号本身由热键触发。
以干净的方式完成后,我的 cheeky_focus_stealer
再次工作,因为它是来自 GUI 线程的 运行。
import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent, QObject, pyqtSignal
from pynput import keyboard
class Forwarder(QObject):
signal = pyqtSignal()
class Launcher(QLineEdit):
def __init__(self):
super().__init__()
self.setFixedSize(QSize(600, 50))
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setPlaceholderText('Search...')
self.installEventFilter(self)
self.set_shortcut('<ctrl>+`')
def set_shortcut(self, shortcut):
# The forwarder must be parented to the Launcher
forwarder = Forwarder(parent=self)
forwarder.signal.connect(self.wake_up)
def for_canonical(f):
return lambda k: f(listener.canonical(k))
hotkey = keyboard.HotKey(
keyboard.HotKey.parse(shortcut),
forwarder.signal.emit)
listener = keyboard.Listener(
on_press=for_canonical(hotkey.press),
on_release=for_canonical(hotkey.release))
listener.start()
def wake_up(self):
print('Waking up')
self.show()
self.cheeky_focus_stealer()
def cheeky_focus_stealer(self):
self.setFocus()
self.raise_()
self.activateWindow()
# Working of linux, but causes freeze/crash on Windows 10
message_box = QMessageBox(self)
message_box.show()
message_box.hide()
def eventFilter(self, obj, event):
if obj is self and event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Escape:
self.hide()
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Launcher()
window.show()
app.exec_()
if __name__ == "__main__":
main()