Opening new window while out of focus using “keyboard”

会有一股神秘感。 提交于 2021-02-02 09:28:26

问题


I am trying to track my keypresses, using the module "keyboard" while a PySide2 Widget is not in focus, which works fine. However when I try to create a new Widget using a "keyboard" shortcut the program crashes. Opening a window on a button press works fine. I can also call non UI functions using "keyboard" eg. the print function without any problem.

Do you know a way to fix this and open a new window using "keyboard" or any other method, while a PySide2 window is not in focus. In this example I want to open a new window on "CTRL+D". The Problem exists both for PySide2 and PyQt5.

This is my shortened code:

import sys
import json
import os
import keyboard
from PySide2.QtWidgets import QApplication, QWidget, QGridLayout, QKeySequenceEdit, QLabel, QPushButton, QShortcut
from PySide2.QtCore import Qt, QObject, Signal, Slot # Qt.Key_W beispielsweise

#from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QKeySequenceEdit, QLabel, QPushButton, QShortcut
#from PyQt5.QtCore import Qt, QObject, pyqtSignal as Signal, pyqtSlot as Slot # Qt.Key_W beispielsweise


class ConfigWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initUi()
        self.init_shortcuts()
        self.show()

    def initUi(self):
        self.setGeometry(300,300, 400, 250)
        self.setWindowTitle("Settings")
        grid = QGridLayout()
        self.setLayout(grid)

        self.keyseq = QKeySequenceEdit("CTRL+D")
        grid.addWidget(self.keyseq, 0, 0)

        s_button = QPushButton("Safe")
        grid.addWidget(s_button, 1, 0)

        cl_button = QPushButton("Close")
        grid.addWidget(cl_button, 1, 1)
        cl_button.clicked.connect(self.close)

        open_button = QPushButton("openw")
        grid.addWidget(open_button, 2, 0)
        open_button.clicked.connect(self.call_item_parser)

    def keyPressEvent(self, event): #event:PySide2.QtGui.QKeyEvent
        if event.key() == Qt.Key_Escape:
            self.close()

    # shortcuts are listened to, while program is running
    def init_shortcuts(self):
        str_value = self.keyseq.keySequence().toString()
        print("Binding _price_keyseq to {}".format(str_value))
        keyboard.add_hotkey(str_value, self.call_item_parser)
        # keyboard.add_hotkey(str_value, print, args=("this works")) # this would work


    def call_item_parser(self):
        self.h_w = ParseWindow()
        self.h_w.setWindowTitle("New Window")
        self.h_w.setGeometry(100, 100, 100, 100)
        self.h_w.show()


class ParseWindow(QWidget):
    def __init__(self):
        super().__init__()


app = QApplication(sys.argv)
w = ConfigWindow()
sys.exit(app.exec_())

回答1:


The problem is caused because the callback registered in keyboard is executed in a secondary thread as can be verified by modifying the following part of the code and printing threading.current_thread(). In Qt it is forbidden to create any widget in another thread since they are not thread-safe.

def call_item_parser(self):
    print(threading.current_thread())
    self.h_w = ParseWindow()
    self.h_w.setWindowTitle("New Window")
    self.h_w.setGeometry(100, 100, 100, 100)
    self.h_w.show()
print(threading.current_thread())
app = QApplication(sys.argv)
w = ConfigWindow()
sys.exit(app.exec_())

Output:

<_MainThread(MainThread, started 140144979916608)>
Binding _price_keyseq to ctrl+a
<Thread(Thread-10, started daemon 140144220817152)>

One possible solution is to use a signal to send the information to the main thread, and invoke the callback in the main thread.

import sys
from functools import partial
import platform
import threading

import keyboard


from PySide2.QtCore import Qt, QObject, Signal, Slot
from PySide2.QtGui import QKeySequence
from PySide2.QtWidgets import (
    QApplication,
    QWidget,
    QGridLayout,
    QKeySequenceEdit,
    QPushButton,
)


class KeyBoardManager(QObject):
    activated = Signal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._callbacks = dict()
        self.activated.connect(self._handle_activated)

    @property
    def callbacks(self):
        return self._callbacks

    def register(self, shortcut, callback, *, args=(), kwargs=None):
        self.callbacks[shortcut] = (callback, args, kwargs or {})
        keyboard.add_hotkey(shortcut, partial(self.activated.emit, shortcut))

    @Slot(str)
    def _handle_activated(self, shortcut):
        values = self.callbacks.get(shortcut)
        if values is not None:
            callback, args, kwargs = self._callbacks[shortcut]

            callback(*args, **kwargs)


class ConfigWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initUi()
        self.init_shortcuts()
        self.show()

    def initUi(self):
        self.setGeometry(300, 300, 400, 250)
        self.setWindowTitle("Settings")
        grid = QGridLayout(self)

        self.keyseq = QKeySequenceEdit("CTRL+A")
        grid.addWidget(self.keyseq, 0, 0)

        s_button = QPushButton("Safe")
        grid.addWidget(s_button, 1, 0)

        cl_button = QPushButton("Close")
        grid.addWidget(cl_button, 1, 1)
        cl_button.clicked.connect(self.close)

        open_button = QPushButton("openw")
        grid.addWidget(open_button, 2, 0)
        open_button.clicked.connect(self.call_item_parser)

    def keyPressEvent(self, event):  # event:PySide2.QtGui.QKeyEvent
        if event.key() == Qt.Key_Escape:
            self.close()

    # shortcuts are listened to, while program is running
    def init_shortcuts(self):
        self.keyboard_manager = KeyBoardManager()

        str_value = self.keyseq.keySequence().toString()
        if platform.system() == "Linux":
            str_value = str_value.lower()
        print("Binding _price_keyseq to {}".format(str_value))
        self.keyboard_manager.register(str_value, self.call_item_parser)

    def call_item_parser(self):
        print(threading.current_thread())
        self.h_w = ParseWindow()
        self.h_w.setWindowTitle("New Window")
        self.h_w.setGeometry(100, 100, 100, 100)
        self.h_w.show()


class ParseWindow(QWidget):
    pass


def main():
    print(threading.current_thread())
    app = QApplication(sys.argv)
    w = ConfigWindow()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Output:

<_MainThread(MainThread, started 140037641176896)>
Binding _price_keyseq to ctrl+a
<_MainThread(MainThread, started 140037641176896)>


来源:https://stackoverflow.com/questions/62181832/opening-new-window-while-out-of-focus-using-keyboard

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!