如何重绘PyQt4中只有部分QWidget?

时间:2013-12-27 01:59:16

标签: python buffer pyqt4 qwidget

我正在尝试创建一个显示大数字网格的程序(例如,填充6乘4000网格),用户可以通过键盘或鼠标移动光标并在网格中输入数字。 (这是一个吉他指法程序。)我是python GUI编程的新手,到目前为止,我的想法是在主窗口内的QScrollArea内部有一个非常大的QWidget窗口(例如,1000x80000像素)。问题是,每次鼠标点击或光标移动都会导致整个事情重新绘制,导致延迟,当我只想重新绘制我刚才所做的任何更改以使事情变得更快时。在PyQt中,有没有办法缓冲已绘制的图形并只更改需要更改的图形?

编辑:我发布了下面的代码,我在Mac OS 10.7上使用python3.3运行。重点是在TabWindow init 函数中,网格大小可以通过numXGrid和numYGrid设置(当前设置为200和6),并且此网格由generateRandomTablatureData()填充随机数方法。如果网格中充满了数字,那么每次按键都会出现明显的滞后现象,大网格会变得更糟。 (由于生成数据也存在初始延迟,但我的问题是每次按键后的延迟,我认为是由于必须重新绘制每个数字。)

有两个文件。这是主要的,我称之为FAIT.py:

import time
start_time = time.time()
import random
import sys
from PyQt4 import QtGui, QtCore

import Tracks

# generate tracks
tracks = [Tracks.Track(), Tracks.Track(), Tracks.Track()]

fontSize = 16
# margins
xMar = 50
yMar = 50
trackMar = 50      # margin between tracks

class MainWindow(QtGui.QWidget):

    def __init__(self):
        super(MainWindow, self).__init__()        
        self.initUI()
        end_time = time.time()
        print("Initializing time was %g seconds" % (end_time - start_time))

    def initUI(self): 
        # attach QScrollArea to MainWindow                      
        l = QtGui.QVBoxLayout(self)
        l.setContentsMargins(0,0,0,0)
        l.setSpacing(0)
        s=QtGui.QScrollArea()
        l.addWidget(s)

        # attach TabWindow to QScrollArea so we can paint on it
        self.tabWindow=TabWindow(self)  
        self.tabWindow.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.setFocusPolicy(QtCore.Qt.NoFocus)
        vbox=QtGui.QVBoxLayout(self.tabWindow)

        s.setWidget(self.tabWindow)

        self.positionWindow()   # set size and position of main window
        self.setWindowTitle('MainWindow')    
        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())     

    def keyPressEvent(self, e):
        print('key pressed in MainWindow')

    def mousePressEvent(self, e):
        print('mouse click in MainWindow')


class TabWindow(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        # size of tablature grid
        numXGrid = 200
        numYGrid = 6

        # initialize tablature information first
        for i in range(0, len(tracks)):
            tracks[i].numXGrid = numXGrid        
        self.arrangeTracks()    # figure out offsets for each track
        self.trackFocusNum = 0       # to begin with, focus is on track 0

        self.windowSizeX = tracks[0].x0 + tracks[0].dx*(tracks[0].numXGrid+2)
        self.windowSizeY = tracks[0].y0
        for i in range(0, len(tracks)):
            self.windowSizeY = self.windowSizeY + tracks[i].dy * tracks[i].numYGrid + trackMar
        self.resize(self.windowSizeX,self.windowSizeY)    # size of actual tablature area

        # generate random tablature data for testing
        self.generateRandomTablatureData()


    def keyPressEvent(self, e):
        print('key pressed in TabWindow')
        i = self.trackFocusNum
        if e.key() == QtCore.Qt.Key_Up:
            tracks[i].moveCursorUp()
        if e.key() == QtCore.Qt.Key_Down:
            tracks[i].moveCursorDown()
        if e.key() == QtCore.Qt.Key_Left:
            tracks[i].moveCursorLeft()
        if e.key() == QtCore.Qt.Key_Right:
            tracks[i].moveCursorRight()

        # check for number input
        numberKeys = (QtCore.Qt.Key_0,
                      QtCore.Qt.Key_1,  
                      QtCore.Qt.Key_2, 
                      QtCore.Qt.Key_3, 
                      QtCore.Qt.Key_4, 
                      QtCore.Qt.Key_5, 
                      QtCore.Qt.Key_6, 
                      QtCore.Qt.Key_7, 
                      QtCore.Qt.Key_8, 
                      QtCore.Qt.Key_9)
        if e.key() in numberKeys:
            num = int(e.key())-48
            # add data
            tracks[i].data.addToTab(tracks[i].iCursor, tracks[i].jCursor, num)

            # convert entered number to pitch and play note (do later)

        # spacebar, backspace, or delete remove data
        if e.key() in (QtCore.Qt.Key_Space, QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
            tracks[i].data.removeFromTab(tracks[i].iCursor, tracks[i].jCursor)

        self.update()

    def mousePressEvent(self, e):
        print('mouse click in TabWindow')
        xPos = e.x()
        yPos = e.y()
        # check tracks one by one
        for i in range(0, len(tracks)):
            if (tracks[i].isPositionInside(xPos, yPos)):
                tracks[i].moveCursorToPosition(xPos, yPos)
                self.trackFocusNum = i
                break
            else:
                continue

        self.update()

    def paintEvent(self, e):
        qp = QtGui.QPainter()
        qp.begin(self)

        qp.setPen(QtCore.Qt.black)
        qp.setBrush(QtCore.Qt.white)
        qp.drawRect(0, 0, self.windowSizeX, self.windowSizeY)

        self.paintTracks(qp)
        self.paintTunings(qp)
        self.paintCursor(qp)
        self.paintNumbers(qp)
        qp.end()

    def paintTracks(self, qp):
        qp.setPen(QtCore.Qt.black)
        qp.setBrush(QtCore.Qt.white)
        for i in range(0, len(tracks)):
            qp.drawPolyline(tracks[i].polyline)

    def paintCursor(self, qp):
        i = self.trackFocusNum
        qp.setPen(QtCore.Qt.black)
        qp.setBrush(QtCore.Qt.black)
        qp.drawPolygon(tracks[i].getCursorQPolygon())

    def paintNumbers(self, qp):
        # iterate through tracks, and iterate through numbers on each track
        for i in range(0, len(tracks)):
            # make sure track has data to draw
            if len(tracks[i].data.data) > 0:
                for j in range(0, len(tracks[i].data.data)):
                    # do actual painting here

                    # first set color to be inverse cursor color if at cursor
                    if i == self.trackFocusNum and  \
                       tracks[i].iCursor == tracks[i].data.data[j][0] and  \
                       tracks[i].jCursor == tracks[i].data.data[j][1]:
                        qp.setPen(QtCore.Qt.white)
                    else:
                        qp.setPen(QtCore.Qt.black)
                    font = QtGui.QFont('Helvetica', fontSize)
                    qp.setFont(font) 
                    text = str(tracks[i].data.data[j][2])
                    x1 = tracks[i].convertIndexToPositionX(tracks[i].data.data[j][0])
                    y1 = tracks[i].convertIndexToPositionY(tracks[i].data.data[j][1])
                    dx = tracks[i].dx
                    dy = tracks[i].dy

                    # height and width of number character(s)
                    metrics = QtGui.QFontMetrics(font)
                    tx = metrics.width(text)
                    ty = metrics.height()

                    # formula for alignment:
                    # xMar = (dx-tx)/2 plus offset
                    x11 = x1 + (dx-tx)/2
                    y11 = y1 + dy/2+ty/2
                    qp.drawText(x11, y11, text)

    def paintTunings(self, qp):
        qp.setPen(QtCore.Qt.black)
        font = QtGui.QFont('Helvetica', fontSize)
        qp.setFont(font)        
        for i in range(0, len(tracks)):
            for j in range(0, tracks[i].numStrings):
                text = tracks[i].convertPitchToLetter(tracks[i].stringTuning[j])
                # height and width of characters
                metrics = QtGui.QFontMetrics(font)
                tx = metrics.width(text)
                ty = metrics.height()

                x11 = tracks[i].x0 - tx - 10
                y11 = tracks[i].convertIndexToPositionY(j) + (tracks[i].dy+ty)/2
                qp.drawText(x11, y11, text)

    def arrangeTracks(self):
        tracks[0].x0 = xMar
        tracks[0].y0 = yMar
        tracks[0].generateGridQPolyline()

        for i in range(1, len(tracks)):
            tracks[i].x0 = xMar
            tracks[i].y0 = tracks[i-1].y0 + tracks[i-1].height + trackMar
            tracks[i].generateGridQPolyline()

    def generateRandomTablatureData(self):
        t1 = time.time()
        for i in range(0, len(tracks)):
            # worst case scenario: fill every number
            for jx in range(0, tracks[i].numXGrid):
                for jy in range(0, tracks[i].numYGrid):
                    val = random.randint(0,9)
                    tracks[i].data.addToTab(jx, jy, val)
        t2 = time.time()
        print("Random number generating time was %g seconds" % (t2 - t1))



def main():

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


if __name__ == '__main__':
    main()

这是另一个文件Tracks.py,它包含Track类和补充方法:

# contains classes and methods relating to individual tracks

import math
from PyQt4 import QtGui, QtCore

# class for containing information about a track
class Track:
    def __init__(self):
        self.data = TabulatureData()

        # position offset
        self.x0 = 0
        self.y0 = 0

        self.dx = 20    # default rectangle width
        self.dy = 40    # default rectangle height

        # current cursor index coordinates
        self.iCursor = 0
        self.jCursor = 0

        # default size of grid 
        self.numXGrid = 4000
        self.numYGrid = 6
        self.numStrings = self.numYGrid

        # calculated maximum width and height in pixels
        self.maxWidth = self.dx * self.numXGrid
        self.maxHeight = self.dy * self.numYGrid

        # generate points of grid and cursor
        self.generateGridQPolyline()

        # tuning
        self.setTuning([40, 45, 50, 55, 59, 64])

        # calculate bounds
        self.height = self.numYGrid*self.dy
        self.width = self.numXGrid*self.dx

    def getCursorIndexX(self, xPos):
        iPos = int(math.floor( (xPos-self.x0)/self.dx ))
        return iPos

    def getCursorIndexY(self, yPos):
        jPos = int(math.floor( (yPos-self.y0)/self.dy ))
        return jPos

    def convertIndexToCoordinates(self, iPos, jPos):
        return [self.ConvertIndexToPositionX(iPos), 
                self.ConvertIndexToPositionY(jPos)]

    def convertIndexToPositionX(self, iPos):
        return self.x0 + iPos*self.dx

    def convertIndexToPositionY(self, jPos):
        return self.y0 + jPos*self.dy

    def getCursorQPolygon(self):
        x1 = self.convertIndexToPositionX(self.iCursor)
        y1 = self.convertIndexToPositionY(self.jCursor)
        x2 = self.convertIndexToPositionX(self.iCursor+1)
        y2 = self.convertIndexToPositionY(self.jCursor+1)
        return QtGui.QPolygonF([QtCore.QPoint(x1, y1), 
                                 QtCore.QPoint(x1, y2), 
                                 QtCore.QPoint(x2, y2), 
                                 QtCore.QPoint(x2, y1)])

    def generateGridQPolyline(self):
        self.points = []      
        self.polyline = QtGui.QPolygonF()
        for i in range(0, self.numXGrid):
            for j in range(0, self.numYGrid):
                x1 = self.convertIndexToPositionX(i)
                y1 = self.convertIndexToPositionY(j)
                x2 = self.convertIndexToPositionX(i+1)
                y2 = self.convertIndexToPositionY(j+1)
                self.points.append([(x1, y1), (x1, y2), (x2, y2), (x2, y1)])
                self.polyline << QtCore.QPoint(x1,y1) <<    \
                                 QtCore.QPoint(x1,y2) <<    \
                                 QtCore.QPoint(x2,y2) <<    \
                                 QtCore.QPoint(x2,y1) <<    \
                                 QtCore.QPoint(x1,y1)      
            # smoothly connect different rows
            jLast = self.numYGrid-1
            x1 = self.convertIndexToPositionX(i)
            y1 = self.convertIndexToPositionY(jLast)
            x2 = self.convertIndexToPositionX(i+1)
            y2 = self.convertIndexToPositionY(jLast+1)
            self.polyline << QtCore.QPoint(x2,y1)


    def isPositionInside(self, xPos, yPos):
        if (xPos >= self.x0 and xPos <= self.x0 + self.width and
            yPos >= self.y0 and yPos <= self.y0 + self.height):
            return True
        else:
            return False

    def moveCursorToPosition(self, xPos, yPos):
        self.iCursor = self.getCursorIndexX(xPos)
        self.jCursor = self.getCursorIndexY(yPos)
        self.moveCursorToIndex(self.iCursor, self.jCursor)

    def moveCursorToIndex(self, iPos, jPos):
        # check if bounds are breached, and if cursor's already there, 
        # and if not, move cursor
        if iPos == self.iCursor and jPos == self.jCursor:
            return
        if iPos >= 0 and iPos < self.numXGrid:
            if jPos >= 0 and jPos < self.numYGrid:
                self.iCursor = iPos
                self.jCursor = jPos
        return

    def moveCursorUp(self):
        self.moveCursorToIndex(self.iCursor, self.jCursor-1)

    def moveCursorDown(self):
        self.moveCursorToIndex(self.iCursor, self.jCursor+1)

    def moveCursorLeft(self):
        self.moveCursorToIndex(self.iCursor-1, self.jCursor)

    def moveCursorRight(self):
        self.moveCursorToIndex(self.iCursor+1, self.jCursor)


    # return pitch in midi integer notation
    def convertNumberToPitch(self, jPos, pitchNum):
        return pitchNum + self.stringTuning[(self.numStrings-1) - jPos]   

    def convertPitchToLetter(self, pitchNum):
        p = pitchNum % 12
        letters = ('C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B')
        return letters[p]

    def setTuning(self, tuning):
        self.stringTuning = tuning


class TabulatureData:
    def __init__(self):
        self.data = []

    def addToTab(self, i, j, value):
        # check if data is already there, and remove it first
        if self.getValue(i, j) > -1:
            self.removeFromTab(i, j)
        self.data.append([i, j, value])

    def getValue(self, i, j):
        possibleTuples = [x for x in self.data if x[0] == i and x[1] == j]
        if possibleTuples == []:
            return -1
        elif len(possibleTuples) > 1:
            print('Warning: more than one number at a location!')
        return possibleTuples[0][2]        # return third number of tuple

    def removeFromTab(self, i, j):
        # first get value, if it exists
        value = self.getValue(i,j)
        if value == -1:
            return
        else:        
            # if it exists, then remove
            self.data.remove([i, j, value])

1 个答案:

答案 0 :(得分:1)

1000 * 80000真的很大。 那么,也许你应该尝试QGLWidget或类似的东西? 或者根据Qt文档,您应该设置要重新绘制的区域。

一些缓慢的小部件需要通过仅绘制请求的区域进行优化:QPaintEvent :: region()。此速度优化不会更改结果,因为在事件处理期间将绘制剪切到该区域。例如,QListView和QTableView就是这样做的。