0

I hope somebody could help me with this annoying issue I've been battling for a while. I have managed with the code attached to insert buttons in a tableview in a delegate column.

The problem is that to press the button, I need to "activate" the containing cell with a double click. Once the cell is active I can press the button, so in total I need 3 click to press it. This could become confusing for your average user.

I posted this problem to the pyqt mailing list, and the answer I got was:

"What happens is, when the TableWidget receives a click, it creates an editor, but the editor has not yet received the click. That is perfect in most cases, but in case you draw a button it isn't."

Has anyone been here before?

Thanks in advance, Cris

class AnimLinkButtons(QtGui.QStyledItemDelegate):
    mouse_isPressed = False

    def __init__(self, parent = None):
        QtGui.QStyledItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        column = index.column()
        button = QtGui.QPushButton(parent)
        button.setText(self.text(index))
        ButtonLoc = self.ButtonLocation(option)
        button.setGeometry(ButtonLoc)
        Cellvalue = index.model().data(index, QtCore.Qt.EditRole)
        row = index.row()
        AssetId = index.model().data(index.model().index(row, 0)).toString()

        AnimTurntablePath = shotsGetData.getAssetTurntablePath(AssetId)
        browsePath = shotsGetData.getAssetPath(AssetId)
        #toAvidComp = shotsGetData.gettoAvidComp(ShotId)

        # Connect to button
        button.clicked.connect(lambda: self.mousePressEvent(index, browsePath, AssetTurntablePath))
        return button

    def setEditorData(self, editor, index):
        button = editor
        if not button:
            return

    def setModelData(self, editor, model, index):
        button = editor
        if not button:
            return

    def updateEditorGeometry(self, editor, option, index):
        ButtonLoc = self.ButtonLocation(option)
        editor.setGeometry(ButtonLoc)

    def paint(self, painter, option, index):
        opt = QtGui.QStyleOptionButton()
        #opt.icon = self.icon()
        opt.text = self.text(index)
        opt.rect = option.rect
        opt.palette = option.palette
        opt.rect = self.ButtonLocation(opt)
        QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_PushButton, opt, painter)

    def ButtonLocation(self,  option):
        r = option.rect
        x = r.left() + 10
        y = r.top() + 10
        w = 30;
        h = 30
        return QRect(x,y,w,h);

    def text(self,  index):
        #print self.column
        column = index.column()
        if column == 7:
            return QtCore.QString("Mov")


    def mousePressEvent(self, index, browsePath , AssetTurntablePath):
        column = index.column()
        print "PRESSSED"

        if column == 7:
            subprocess.Popen(AnimTurntablePath, shell=True)
Avaris
  • 35,883
  • 7
  • 81
  • 72
user1028826
  • 71
  • 1
  • 8

2 Answers2

4

I've recently made something similar. If you made your button in the createEditor, you'll activate it in the edit mode. That happens when you double click on the cell. So the comment from the mailing list is correct.

Doing that without editing is tricky. The button you painted in the paint method is just a painting, not an actual button. You should monitor editorEvent for mouse events. It captures mouseButtonPress and mouseButtonRelease events for this purpose.

Furthermore, if you want the 'visual' effects of clicking a button, that complicates things. Anyway, below is a basic implementation. It emits buttonClicked signal with row and column.

Note: Repainting relies on the fact that the delegate will receive events from other columns also. This will most probably won't work properly if you use it with setItemDelegateForColumn, because in that case the delegate will receive events from that column only. So, I'd suggest that you use it for the whole table and paint according to the column.

class ButtonDelegate(QtGui.QStyledItemDelegate):
    buttonClicked = QtCore.pyqtSignal(int, int)

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

    def paint(self, painter, option, index):
        painter.save()
        opt = QtGui.QStyleOptionButton()
        opt.text = index.data().toString()
        opt.rect = option.rect
        opt.palette = option.palette
        if self._pressed and self._pressed == (index.row(), index.column()):
            opt.state = QtGui.QStyle.State_Enabled | QtGui.QStyle.State_Sunken
        else:
            opt.state = QtGui.QStyle.State_Enabled | QtGui.QStyle.State_Raised
        QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_PushButton, opt, painter)
        painter.restore()

    def editorEvent(self, event, model, option, index):
        if event.type() == QtCore.QEvent.MouseButtonPress:
            # store the position that is clicked
            self._pressed = (index.row(), index.column())
            return True
        elif event.type() == QtCore.QEvent.MouseButtonRelease:
            if self._pressed == (index.row(), index.column()):
                # we are at the same place, so emit
                self.buttonClicked.emit(*self._pressed)
            elif self._pressed:
                # different place.
                # force a repaint on the pressed cell by emitting a dataChanged
                # Note: This is probably not the best idea
                # but I've yet to find a better solution.
                oldIndex = index.model().index(*self._pressed)
                self._pressed = None
                index.model().dataChanged.emit(oldIndex, oldIndex)
            self._pressed = None
            return True
        else:
            # for all other cases, default action will be fine
            return super(ButtonDelegate, self).editorEvent(event, model, option, index)
Avaris
  • 35,883
  • 7
  • 81
  • 72
  • Thanks Avaris for your help on this issue. You say don't use setItemDelegateForColumn, what should I use instead? I also got a reply in the mailing list, have a look at this example if it helps you: http://www.gulon.co.uk/2013/01/30/button-delegate-for-qtableviews/ Thanks again! Cris – user1028826 Feb 08 '13 at 10:40
  • @user1028826: I meant, use `setItemDelegate` to set the delegate for the whole view. This is only true for the visual effects. If you don't want them you can use any way to put this delegate. As for the example, that is, err, not very nice. It's abusing the delegate to do something out of its purpose. And you'll be creating a whole lot of `QPushButton`s in the end. For large tables, it's possible to have resource issues. – Avaris Feb 08 '13 at 10:59
2

Question is written long ago, so I don't take a lot of time to answer... But had same problem a few days ago. Found solution on stackoverflow, but cannot find the post anymore, forgot bookmark and upvote... But still have link to solution file on github!

See her for example

The important thing is to use openPersistentEditor(index) on the QTableView. Call this when init the view and your delegate widgets will be drawn immediatly.

Case you use a Proxy Model, be aware to use the QModelInstances of the proxy model when calling openPersistentEditor(index).

this.myself
  • 2,101
  • 2
  • 22
  • 36