I'm making guitar tablature software where, when playback is started, the graphics update every 16th note (say, by moving a cursor to the right one space). I'm having trouble figuring out how to update QGraphicsItems from a thread without problems. In the example below (simplified from the original program), I have a QThread do the playback and redraw a QGraphicsRectItem to the right every 0.02 seconds. The problem is that the rectangle often freezes and remains frozen even after playback has been stopped.
Could anyone let me know what a better way to update a QGraphicsView from a thread would be?
#!/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()