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.
The value of
option.state
is the result of a bitwise or operation of manyQStyle.state
flags.To see if you currently need to draw the
mouseOver
state, you just need to do: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.