簡體   English   中英

在 PyQt5/Pyside2 中竊取焦點

[英]Steal focus in PyQt5/Pyside2

我正在創建一個啟動器,采用AlbertAlfreduLauncher的風格。 我的應用程序在后台運行,並在按下熱鍵時顯示。 我使用pynput來收聽熱鍵。 我不能使用 PyQt5 熱鍵的功能(不能嗎?),因為我需要監聽系統 scope 中的鍵盤事件,而不僅僅是應用程序的 scope。

按下快捷方式時,它會調用我的小部件的 show() 方法。 唯一的問題是,盡管使用了raise_setFocusactivateWindow ,但我無法將焦點重新放在我的 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(即兩個不同的線程通信)的 function 的人有所幫助。

我的錯誤確實是將熱鍵觸發的操作直接綁定到我的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()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM