线程化Python中的Qt信号/插槽

2024-04-25 13:50:16 发布

您现在位置:Python中文网/ 问答频道 /正文

我在使用PyQt4插槽/信号时遇到问题。在

我用的是Pyric,我在听遥控器上的按键。这部分我要在Qt之外工作。当从按钮监听线程发出信号并试图调用主线程中的插槽时,我的问题就来了。在

My button listener是一个QObject,初始化如下:

buttonPressed = pyqtSignal(int)

def __init__(self):
    super(ButtonEvent, self).__init__()
    self.buttonPressed.connect(self.onButtonPressed)

def run(self):
    print 'running'
    while(self._isListening):
        s = pylirc.nextcode()
        if (s):
            print 'emitting'
            self.buttonPressed.emit(int(s[0]))

为了测试目的,onButtonPressed插槽是按钮侦听器的内部插槽。在

要将按钮侦听器移动到另一个线程来执行此工作,我使用以下命令:

^{pr2}$

然后在主线程中,我有我的VideoTableController类,它包含主线程中不被调用的槽。在__init__里面有这个:

class VideoTableController(QObject):
    def __init__(self, buttonEvent):
        buttonEvent.buttonPressed.connect(self.onButtonPressed)

其中onButtonPressed在本例中是:

@pyqtSlot(int)
def onButtonPressed(self, bid):
    print 'handling button press'
    if bid not in listenButtons: return
    { ButtonEnum.KEY_LEFT : self.handleBack,
    #...

所以当我启动事件线程时,它开始正确地监听。当我按下遥控器上的按钮时,onButtonPressed槽在ButtonEvent类内部被正确调用,但是{}内驻留在主线程中的槽不会被调用。我在把插槽连接到信号后开始了监听线程,我测试了另一种方法,但是没有用。在

我四处看看,但什么也没找到。在阅读了You're doing it wrong之后,我改为使用QObject。如有任何帮助,我们将不胜感激。如果你还需要什么,请告诉我。在

编辑:感谢您的回复!这里有一大块代码给你们:

ButtonEvent(这个类使用singleton模式,请原谅代码编写不好,因为我对Python的这一领域还不太熟悉):

import pylirc
from PyQt4.QtCore import QObject, pyqtSignal, QThread, pyqtSlot
from PyQt4 import QtCore

class ButtonEvent(QObject):
    """
    A class used for firing button events
    """

    _instance = None
    _blocking = 0
    _isListening = False

    buttonPressed = pyqtSignal(int)

    def __new__(cls, configFileName="~/.lircrc", blocking=0, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(ButtonEvent, cls).__new__(cls, args, kwargs)
            cls._blocking = blocking
            if not pylirc.init("irexec", configFileName, blocking):
                raise RuntimeError("Problem initilizing PyLIRC")
            cls._isListening = True

        return cls._instance

    def __init__(self):
        """
        Creates an instance of the ButtonEvent class
        """
        super(ButtonEvent, self).__init__()
        self.buttonPressed.connect(self.button)
    ### init

    def run(self):
        print 'running'
        while(self._isListening):
            s = pylirc.nextcode()
            if (s):
                print 'emitting'
                self.buttonPressed.emit(int(s[0]))

    def stopListening(self):
        print 'stopping'
        self._isListening = False

    @pyqtSlot(int)
    def button(self, bid):
        print 'Got ' + str(bid)


def setupAndConnectButtonEvent(configFileName="~/.lircrc", blocking=0):
    """
    Initializes the ButtonEvent and puts it on a QThread.
    Returns the QThread it is running on.
    Does not start the thread
    """
    event = ButtonEvent().__new__(ButtonEvent, configFileName, blocking)
    eventThread = QThread()
    event.moveToThread(eventThread)
    eventThread.started.connect(event.run)
    return eventThread

这是视频表控制器:

from ControllerBase import ControllerBase
from ButtonEnum import ButtonEnum
from ButtonEvent import ButtonEvent
from PyQt4.QtCore import pyqtSlot
from PyQt4 import QtCore

class VideoTableController(ControllerBase):

    listenButtons = [ ButtonEnum.KEY_LEFT,   
                      ButtonEnum.KEY_UP,     
                      ButtonEnum.KEY_OK,     
                      ButtonEnum.KEY_RIGHT,  
                      ButtonEnum.KEY_DOWN,   
                      ButtonEnum.KEY_BACK ]

    def __init__(self, model, view, parent=None):
        super(VideoTableController, self).__init__(model, view, parent)
        self._currentRow = 0
        buttonEvent = ButtonEvent()
        buttonEvent.buttonPressed.connect(self.onButtonPressed)
        self.selectRow(self._currentRow)

    @pyqtSlot(int)
    def onButtonPressed(self, bid):
        print 'handling button press'
        if bid not in listenButtons: return
        { ButtonEnum.KEY_LEFT : self.handleBack,
          ButtonEnum.KEY_UP : self.handleUp,
          ButtonEnum.KEY_OK : self.handleOk,
          ButtonEnum.KEY_RIGHT : self.handleRight,
          ButtonEnum.KEY_DOWN : self.handleDown,
          ButtonEnum.KEY_BACK : self.handleBack,
        }.get(bid, None)()

下面是我的启动脚本:

import sys
from PyQt4 import QtCore, QtGui
from ui_main import Ui_MainWindow
from VideoTableModel import VideoTableModel
from VideoTableController import VideoTableController
from ButtonEvent import *

class Main(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.buttonEvent = ButtonEvent()
        self.bEventThread = setupAndConnectButtonEvent()
        model = VideoTableModel("/home/user/Videos")
        self.ui.videoView.setModel(model)
        controller = VideoTableController(model, self.ui.videoView)
        self.bEventThread.start()

    def closeEvent(self, event):
        self.buttonEvent.stopListening()
        self.bEventThread.quit()
        event.accept()


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    buttonEvent = ButtonEvent()
    myapp = Main()
    myapp.show()
    sys.exit(app.exec_())    

Tags: keyfromimportselfifinitdefint
3条回答

原来我只是犯了一个愚蠢的Python错误。信号正确发出,事件循环在所有线程中都正常运行。我的问题是,在我的Main.__init__函数中,我创建了一个VideoTableController对象,但是我没有在Main中保留一个副本,所以我的controller没有持续存在,这意味着插槽也离开了。当把它换成

self.controller = VideoTableController(model, self.ui.videoView)

所有的东西都在附近,而且插槽被正确地调用了。在

故事的寓意:这并不总是对图书馆的误用,它可能是对语言的误用。在

我还没有真正测试过这个(因为我无法访问您编译的UI文件),但我相当肯定我是对的。在

ButtonEvent的run方法(应该在线程中运行)可能在主线程中运行(可以通过导入pythonthreading模块并添加print threading.current_thread().name行来测试这一点。要解决这个问题,请用@pyqtSlot()装饰run方法

如果这不能解决这个问题,请将上面的print语句添加到不同的位置,直到找到不应该在主线程中运行的东西。下面一行的SO答案可能包含修复它的答案。在

有关详细信息,请参阅以下答案:https://stackoverflow.com/a/20818401/1994235

似乎最快的解决方法是在此处更改ButtonEvent代码:

...
def run(self):
    print 'running'
    while(self._isListening):
        s = pylirc.nextcode()
        if (s):
            print 'emitting'
            self.buttonPressed.emit(int(s[0]))
...

为此:

^{pr2}$

对这个问题的简短解释是PyQt在内部使用代理,这样可以确保避免这种情况。毕竟,您的方法应该是基于connect语句的slot。在

好吧。。。现在,我鼓励您考虑一下您当前的软件设计。似乎您正在使用一个专用线程中的类来处理Qt按钮事件。这也许是个好主意,我不确定,但至少我以前没见过这个。在

我认为你将来可以用一种更好的方法将这个类从按钮信号直接连接到处理程序插槽。但是,这不是专用线程中的run“槽”,而是canonical处理程序。在

在多线程应用程序中引入超出需要的复杂性并不是一个好的设计实践。希望这有帮助。在

相关问题 更多 >