繁体   English   中英

如何只重画PyQt4中QWidget的一部分?

[英]how to repaint only part of a QWidget in PyQt4?

我正在尝试创建一个显示大数字网格的程序(例如,用6乘4000网格填充),用户可以在其中通过键盘或鼠标在光标周围移动并在网格中输入数字。 (这是给吉他谱程序的。)我是python GUI编程的新手,到目前为止,我的想法是在主窗口内的QScrollArea内部有一个非常大的QWidget窗口(例如1000x80000像素)。 问题在于,每当鼠标单击或移动光标时,整个对象都将重新绘制,从而导致延迟,而当我只想重新绘制我为使事情更快进行的所有更改时。 在PyQt中,是否可以缓冲已经绘制的图形并仅更改需要更改的图形?

编辑:我已经发布了以下代码,该代码已在Mac OS 10.7上与python3.3一起运行。 要点是,在TabWindow 初始化函数中,可以通过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])

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM