从线程修改 QGraphicsItem 的正确方法?
Posted
技术标签:
【中文标题】从线程修改 QGraphicsItem 的正确方法?【英文标题】:proper way of modifying a QGraphicsItem from a thread? 【发布时间】:2014-02-26 03:21:50 【问题描述】:我正在制作吉他指法谱软件,当开始播放时,图形每 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()
【问题讨论】:
【参考方案1】:我想我已经通过用 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()
我不再看到冻结(无论如何),但我确实看到了一些图形故障,例如当我滚动窗口时移动矩形留下的小矩形。
【讨论】:
【参考方案2】:您不能使用非主线程中的 GUI 对象。我对Cannot send posted events for objects in another thread 的回答完全适用于这里。
【讨论】:
以上是关于从线程修改 QGraphicsItem 的正确方法?的主要内容,如果未能解决你的问题,请参考以下文章
PySide/PyQT5:如何从 QGraphicsItem 发出信号?
PyQt4 - 拖动后 QGraphicsItem 位置未正确映射到场景中
使用 mouseMoveEvent 约束 QGraphicsItem 移动