0

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 was a previous question with the same goal. I just ask for further explanation.

So since a few days, I manage to implement a multi-node tree model within a treeview thanks to this video tutorial with the code source here and here but now I would like to add a delegate pushbutton next to each child of the last level of the tree model. After consulting Qt documentation about model/view programming, searching around and reading chapter 14, 15 and 16 from the book of Mark Summerfield Rapid GUI Programming with Python and QT and searching how to add Qwidget delegate by using createEditor(), setEditordata() and setModelData() I still struggle to display "The" button…

So I started to create the button class delegate:

class ButtonDelegate(QtGui.QItemDelegate):
    """
    A delegate that places a fully functioning Button in every
    cell of the column to which it's applied
    """

    def __init__(self, parent):
        QtGui.QItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        button = QtGui.QPushButton("Simple button", parent)
        self.connect(button, QtCore.SIGNAL("clicked()"), self, QtCore.SLOT("handle_button()"))
        return button

    @QtCore.pyqtSlot()
    def handle_button(self):
        sender = self.sender()
        print("Button pushed: {}".format(self.sender()))

Here you see that I didn't implement setEditordata() and setModelData(), because I have connected the button to def handle_button(self) and I just want to print the name of the child for the moment (just to make it simple).

I use setItemDelegateForColumn(int, AbstractItemDelegate) for setting up the delegate I also know that for displaying the button you have to specify this method to the treeview QAbstractItemView.openPersistentEditor(QModelIndex). But I don't know what to put as argument...

Again I am sorry to "repost" a question but at the moment I am in a dead end...I don't know where to start.

I write my code in Python, but if you answer in C++ I will figure it out, don't worry.

Thanks again for the help.

bam94
  • 3
  • 1
  • 3
  • A delegate will only be visible when actually *editing* an item. An [index widget](https://doc.qt.io/archives/qt-4.8/qabstractitemview.html#setIndexWidget) might be more appropriate. – ekhumoro Dec 16 '17 at 23:18

1 Answers1

0

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.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Thank you for your answer @musicamante I will take a look at the `paint()` method and `edtiorevent()` ;) – bam94 Dec 18 '17 at 10:46
  • Note that in the example above I was drawing "over" the already drawn delegate (the class's `QtGui.QStyledItemDelegate.paint` call at the beginning). If you need more control over the drawing, you will have to instantiate the QStyleOptionViewItem, init it using the option argument and eventually use QStyledItemDelegate.initStyleOption. Also, remember that `option`'s properties behave like python properties: their values are read and set as normal variables and not with function calls. – musicamante Dec 18 '17 at 11:07
  • I thought for handling button clicked, get inspired by those answer [here](https://stackoverflow.com/questions/11777637/adding-button-to-qtableview) and [here](https://stackoverflow.com/questions/37972839/dynamic-qtreeview-with-custom-delegates?rq=1). The final goal of my treeview is to add a QLineEdit in anoter column and these button. For the QlineEdit I found several references on the net, or I will simply just allowed the column to be editable. The main problem was how to handle button as delegate ;) – bam94 Dec 18 '17 at 13:09