Skip to content Skip to sidebar Skip to footer

PyQT: QPushButton Delegate In Column Of A Treeview

I know there has been a question with the same goal in C++, but I didn't succeed implementing a button delegate in a treeview. So I ask a new question here, even if I know there wa

Solution 1:

Using setIndexWidget might be a solution, but since you have to set it for every last child item, this might require too much coding and could be prone to bugs and headaches... I'd suggest to draw the button directly in the delegate using QStyle's methods.

This is a simple implementation:

class ButtonDelegate(QtGui.QStyledItemDelegate):
    def paint(self, painter, option, index):
        QtGui.QStyledItemDelegate.paint(self, painter, option, index)
        if index.model().hasChildren(index):
            return
        rect = option.rect
        btn = QtGui.QStyleOptionButton()
        btn.rect = QtCore.QRect(rect.left() + rect.width() - 30, rect.top(), 30, rect.height())
        btn.text = '...'
        btn.state = QtGui.QStyle.State_Enabled|(QtGui.QStyle.State_MouseOver if option.state & QtGui.QStyle.State_MouseOver else QtGui.QStyle.State_None)
        QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_PushButton, btn, painter)

And here's how it could look like...

Example of implementation

Obviously you have to implement your own signal, but, since editorEvent automatically handles mouse events, this would be a simple task of detecting if the event.pos() is within the "virtual" button QRect.

In the example above I added a simple hover detection, which sets the "mouse-over look" on the button whenever the mouse pointer is above the item, but you might prefer to use the hover style only when the pointer is actually on the button.

Since paint doesn't give you the possibility to detect the mouse position, there are two possible solutions. Either you use a delegate exclusively for the view and add the view's instance as a parameter when you init the delegate (then you can easily use mapFromGlobal with QtGui.QCursor.pos() and retrieve its position according to the option.rect - just remember that you have to map it to the viewport, not the view); or you create a specific UserRole in which you write the event.pos() to the item from the editorEvent method, then read it from the paint method. To achieve that, mouseTracking must be enabled on the view.

Some further comments

As you can imagine, the look of the item will have issues when the tree level is too deep and the view's width isn't wide enough to show the item itself. You could look into the option.rect's available size, if you don't want to hide the item or don't show the button if there's not enough space available.

Another drawback is that the item's text might be painted over by the button, with some styles this could result in some ugly appearance. You could decide to elide the text using fontMetrics' elideText() method (by subctracting the button's width) or simply "mask" the right side of the button if the fontMetrics of the item's text does not allow it to be completely shown.

Be careful with that, though: if you have more than one column, the "hidden" right part of the button won't be hidden at all, but it will be part of the next column.


Post a Comment for "PyQT: QPushButton Delegate In Column Of A Treeview"