How to make subclass of QStyledItemDelegate react

2019-08-13 00:18发布

问题:

On my way to solve the problems I stated in earlier questions (question 1, question 2) alone, I succeeded to implement a custom QStyledItemDelegate which meets my demands. Here is a minimal working example illustrating the current state:

import sys
import PySide.QtCore as core
import PySide.QtGui as gui


class DataRef(object):

    def __init__(self, i):
        self.i = i

    def upperLabel(self):
        return u'upperLabel {0}'.format(self.i)

    def lowerLabel(self):
        return u'lowerLabel {0}'.format(self.i)

    def pixmap(self):
        return gui.QPixmap(90, 90)

class MyListModel(core.QAbstractListModel):

    def __init__(self, parent=None):
        super(MyListModel, self).__init__(parent)
        self._items = [DataRef(i) for i in range(20)]

    def rowCount(self, parent=core.QModelIndex()):
        return len(self._items)

    def data(self, index, role=core.Qt.DisplayRole):

        if not index.isValid():
            return None

        if role == core.Qt.DisplayRole:
            return self._items[index.row()]
        return

class MyListDelegate(gui.QStyledItemDelegate):

    w = 300
    imSize = 90
    pad = 5
    h = imSize + 2*pad
    sepX = 10

    def __init__(self, parent=None):
        super(MyListDelegate, self).__init__(parent)

    def paint(self, painter, option, index):
        mouseOver = option.state in [73985, 73729]

        if option.state & gui.QStyle.State_Selected:
            painter.fillRect(option.rect, painter.brush())

        pen = painter.pen()
        painter.save()

        x,y = (option.rect.x(), option.rect.y())
        dataRef = index.data()
        pixmap = dataRef.pixmap()
        upperLabel = dataRef.upperLabel()
        lowerLabel = dataRef.lowerLabel()

        if mouseOver:
            newPen = gui.QPen(core.Qt.green, 1, core.Qt.SolidLine)
            painter.setPen(newPen)
        else:
            painter.setPen(pen)
        painter.drawRect(x, y, self.w, self.h)
        painter.setPen(pen)

        x += self.pad
        y += self.pad

        painter.drawPixmap(x, y, pixmap)

        font = painter.font()
        textHeight  = gui.QFontMetrics(font).height()

        sX = self.imSize + self.sepX
        sY = textHeight/2

        font.setBold(True)
        painter.setFont(font)
        painter.drawText(x+sX, y-sY, 
                         self.w-self.imSize-self.sepX, self.imSize,
                         core.Qt.AlignVCenter,
                         upperLabel)
        font.setBold(False)
        font.setItalic(True)
        painter.setFont(font)
        painter.drawText(x+sX, y+sY,
                         self.w-self.imSize-self.sepX, self.imSize,
                         core.Qt.AlignVCenter,
                         lowerLabel)

        painter.restore()

    def sizeHint(self, option, index):
        return core.QSize(self.w, self.imSize+2*self.pad)

    def editorEvent(self, event, model, option, index):
        if event.type() == core.QEvent.MouseButtonRelease:
            print 'Clicked on Item', index.row()
        if event.type() == core.QEvent.MouseButtonDblClick:
            print 'Double-Clicked on Item', index.row()
        return True

if __name__ == '__main__':


    app = gui.QApplication(sys.argv)
    app.setStyleSheet('QListView::item:hover {background: none;}')
    mw = gui.QMainWindow()

    model = MyListModel()
    view = gui.QListView()
    view.setItemDelegate(MyListDelegate(parent=view))
    view.setSpacing(5)
    view.setModel(model)

    mw.setCentralWidget(view)
    mw.show()

    sys.exit(app.exec_())

I used a dummy class DataRef which returns the dummy labels and pixmap for the delegate. The delegate is simply a rectangular outline with a pixmap at the left and 2 lines of formatted text at the right. The 'editorEvent' enables me to detect clicks and double-clicks.

Problems

The MyListDelegate.paint() function receives option.state values which seem strange to me. They do not correspond to a QStyle.State which I know. So I'm now using this large int numbers which I got from simply printing int(option.state). Anyway: it doesn't work quite well! The lower border of the frame does not change it's color and strange things happen sometimes.

Can anyone show me a better way to do that? Optimally, using colors from QStyle for changing the outline and background color, so that it is customizable using the StyleSheet?

Any hints or explanations are highly appreciated.

回答1:

The value of option.state is the result of a bitwise or operation of many QStyle.state flags.

To see if you currently need to draw the mouseOver state, you just need to do:

if option.state & QStyle.State_MouseOver:
    # draw mouseover stuff
else:
    # don't draw mouse over stuff

There may be many other flags set, which you can choose to handle or not by writing similar if statements (all the flags are listed in the c++ documentation I linked to above).

I suspect this is why you are seeing inconsistent behaviour. You are only catching a couple of the mouseOver state flags and ignoring times when other (unrelated) style flags are also set. I suggest reading up about bitwise and-ing and or-ing to learn why flags are combined and extracted like this, and you you get results that are really large integers when you print it.

Finally, I suspect that the lower border is not changing colour because the next item below is drawing the border on top of it. This probably means you calculation for the size of the rectangle is wrong. I suggest doing some debugging along this lines to see if you can work out why.