从线程修改QGraphicsItem的正确方法?

时间:2014-02-02 17:36:27

标签: multithreading qt pyqt qgraphicsview qthread

我正在制作吉他指法软件,当开始播放时,图形会每16个音符更新一次(例如,将光标移动到右侧的一个空格)。我无法弄清楚如何从线程更新QGraphicsItems没有问题。在下面的示例中(从原始程序简化),我有一个QThread进行播放并每0.02秒重新绘制一个QGraphicsRectItem。问题是矩形经常冻结并且即使在停止播放后仍然保持冻结状态。

有谁能告诉我从线程更新QGraphicsView的更好方法是什么?

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
import random
import sys
import threading

from PyQt4 import QtGui, QtCore

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        # add TablatureWindow
        self.tabWidget = QtGui.QTabWidget()
        self.setCentralWidget(self.tabWidget)
        self.setWindowTitle('Tablature Editor')    

        self.tablatureWindow = TablatureWindow(self)
        self.tabWidget.removeTab(0)
        self.tabWidget.addTab(self.tablatureWindow, 'Untitled')
        self.tablatureWindow.setFocus()

        self.positionWindow()       # center and enlargen window

        self.show()

    def positionWindow(self):
        qr = self.frameGeometry()
        cp = QtGui.QDesktopWidget().availableGeometry().center()
        width = QtGui.QDesktopWidget().availableGeometry().width() - 100
        height = QtGui.QDesktopWidget().availableGeometry().height() - 100
        self.resize(width, height)
        qr = self.frameGeometry()
        cp = QtGui.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())  


class TablatureWindow(QtGui.QGraphicsView):    
    def __init__(*args, **kwargs):
        start_time = time.time()

        self = args[0] 
        self._parent = args[1]

        QtGui.QGraphicsView.__init__(self)    
        self.scene = QtGui.QGraphicsScene(self)
        self.setScene(self.scene)

        self.scene.setSceneRect(QtCore.QRectF(0, 0, 20000, 2000))

        self.cursorItem = QtGui.QGraphicsRectItem(100, 100, 20, 20)
        self.cursorItem.setBrush(QtCore.Qt.black)
        self.scene.addItem(self.cursorItem)
        self.centerOn(0,0)

        self.isPlaying = False


    def keyPressEvent(self, e):
        key = e.key()

        # "p" starts or stops playback
        if key == QtCore.Qt.Key_P:
            if self.isPlaying == False:
                self.beginPlayback()
            else:
                self.stopPlayback()

    def beginPlayback(self):
        print('begin')
        self.isPlaying = True

        self.playbackThread = PlaybackThread(self)
        self.playbackThread.start()

    def stopPlayback(self):
        print('stop')
        self.isPlaying = False

        self.playbackThread.stopPlayback()


class PlaybackThread(QtCore.QThread):
    def __init__(self, parent):
        QtCore.QThread.__init__(self)
        self._parent = parent

        self.doStopThread = False

    def run(self):
        self.startTime = time.time()

        dt = 0.02    # move every dt seconds

        for i in range(0, 2000):            # keep going right for 1000 spaces
            if not self.doStopThread:

                x = self._parent.cursorItem.rect().x()
                y = self._parent.cursorItem.rect().y()
                w = self._parent.cursorItem.rect().width()
                h = self._parent.cursorItem.rect().height()

                self._parent.cursorItem.setRect(x+10, y, w, h)

                ideal_dt = (i+1) * dt + self.startTime
                dt2 = ideal_dt-time.time()
                time.sleep(dt2)             # sleep for remaining time


    def stopPlayback(self):
        self.doStopThread = True

    def __del__(self):
        self.wait()


def main():

    app = QtGui.QApplication(sys.argv)
    ex = MainWindow()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main() 

2 个答案:

答案 0 :(得分:0)

我想我已经通过用QTimer替换线程找到了部分答案:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
import random
import sys
import threading

from PyQt4 import QtGui, QtCore

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        # add TablatureWindow
        self.tabWidget = QtGui.QTabWidget()
        self.setCentralWidget(self.tabWidget)
        self.setWindowTitle('Tablature Editor')    

        self.tablatureWindow = TablatureWindow(self)
        self.tabWidget.removeTab(0)
        self.tabWidget.addTab(self.tablatureWindow, 'Untitled')
        self.tablatureWindow.setFocus()

        self.positionWindow()       # center and enlargen window

        self.show()

    def positionWindow(self):
        qr = self.frameGeometry()
        cp = QtGui.QDesktopWidget().availableGeometry().center()
        width = QtGui.QDesktopWidget().availableGeometry().width() - 100
        height = QtGui.QDesktopWidget().availableGeometry().height() - 100
        self.resize(width, height)
        qr = self.frameGeometry()
        cp = QtGui.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())  


class TablatureWindow(QtGui.QGraphicsView):    
    def __init__(*args, **kwargs):
        start_time = time.time()

        self = args[0] 
        self._parent = args[1]

        QtGui.QGraphicsView.__init__(self)    
        self.scene = QtGui.QGraphicsScene(self)
        self.setScene(self.scene)

        self.scene.setSceneRect(QtCore.QRectF(0, 0, 20000, 2000))

        self.cursorItem = QtGui.QGraphicsRectItem(100, 100, 20, 20)
        self.cursorItem.setBrush(QtCore.Qt.black)
        self.scene.addItem(self.cursorItem)
        self.centerOn(0,0)

        self.isPlaying = False


    def keyPressEvent(self, e):
        key = e.key()

        # "p" starts or stops playback
        if key == QtCore.Qt.Key_P:
            if self.isPlaying == False:
                self.beginPlayback()
            else:
                self.stopPlayback()

    def beginPlayback(self):
        print('begin')
        self.isPlaying = True

        self.playback = Playback(self)

    def stopPlayback(self):
        print('stop')
        self.isPlaying = False

        self.playback.stopPlayback()


class Playback:
    def __init__(self, parent):
        self._parent = parent

        self.timer = QtCore.QTimer()
        QtCore.QObject.connect(
            self.timer,
            QtCore.SIGNAL("timeout()"),
            self.doStuff)

        self.timer.start(20)        # 20 ms

    def doStuff(self):
        x = self._parent.cursorItem.rect().x()
        y = self._parent.cursorItem.rect().y()
        w = self._parent.cursorItem.rect().width()
        h = self._parent.cursorItem.rect().height()

        self._parent.cursorItem.setRect(x+10, y, w, h)

        self._parent.update()

    def stopPlayback(self):
        self.timer.stop()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = MainWindow()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main() 

我不再看到冻结(但是,无论如何),虽然我确实看到一些图形故障,例如当我滚动窗口时从移动矩形留下的小矩形。

答案 1 :(得分:0)

您无法使用非主线程中的GUI对象。我对Cannot send posted events for objects in another thread的回答完全适用于此。