Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Custom QStyledItemDelegate and QTableView
Forum Updated to NodeBB v4.3 + New Features

Custom QStyledItemDelegate and QTableView

Scheduled Pinned Locked Moved Solved Qt for Python
10 Posts 3 Posters 2.5k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • alomA Offline
    alomA Offline
    alom
    wrote on last edited by alom
    #1

    I'm trying to customize my QTableView to have the following:

    1. Hover event that spans entire row, when hovering over a cell.
    2. Selection event that paints the rect of the last cell in a selected row(any cells in a row can trigger this).

    I'm starting to read up on delegates and views. I'm still abit unclear on who, the delegate or view, is responsible to call the update. For example I figured I need to get the current hovered index and retrieve the current row from that. I'm just not sure if the view needs to tell the delegate to update all items in that row or does the delegate collect the items in the row when I'm testing the State_MouseOver. As for the model my assumptions so far is that the model is responsible just for the data.

    I've found a similar thread for #1 in the cpp forum:
    https://forum.qt.io/topic/12794/mousehover-entire-row-selection-in-qtableview
    I'm having a bit of trouble translating the working code to Python. It looks like they set a setMouseOver(), disableMouseOver(), and a mouseMoveEvent(). In the code the Delegate has access to the view to call the custom setMouseOver() and pass the row. How can I get a similar behavior? or is the a better way to achieve my two goals?

    Cheers

    from PySide2 import QtGui, QtCore, QtWidgets
    import sys
    
    class CustomTableDelegate(QtWidgets.QStyledItemDelegate):
        def __init__(self, parent=None):
            super(CustomTableDelegate, self).__init__(parent)
    
        def paint(self, painter, option, index):
            if option.state & QtWidgets.QStyle.State_MouseOver:
                painter.fillRect(option.rect, QtGui.QColor(143,143,143))
                QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
            else:
                painter.fillRect(option.rect, QtGui.QColor(38,38,38))
                QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
    
    
    class CustomTableView(QtWidgets.QTableView):
        def __init__(self, parent=None):
            super(CustomTableView, self).__init__(parent)
    
        def setMouseOver(self, row):
            # print(self.model.columnCount())
            print(row)
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        data = [["v001", "stuff", "fixing source", '2020-01-14', ''],
                ["v002", "more stuff", "currently broken", '2020-06-30', ''],
                ["v003", "too much stuff", "scaling issues", '2020-02-08', ''],
                ["v088", "still missing stuff", "not coment", '2020-11-13', ''],]
    
        table_view = CustomTableView()
        table_view.setMouseTracking(True)
        table_view.setItemDelegate(CustomTableDelegate())
        table_view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
        table_view.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
    
        header = table_view.horizontalHeader()
        header.setStretchLastSection(True)
        vertical_header = table_view.verticalHeader()
        vertical_header.hide()
    
        model = QtGui.QStandardItemModel()
        model.setHorizontalHeaderLabels(['version', 'file', 'comment', 'date'])
        table_view.setModel(model)
    
    
        for r in range(len(data)):
            for c in range(4):
                model.setItem(r, c, QtGui.QStandardItem(data[r][c]))
    
        table_view.show()
        sys.exit(app.exec_())
    
    
    JonBJ 1 Reply Last reply
    0
    • alomA alom

      I'm trying to customize my QTableView to have the following:

      1. Hover event that spans entire row, when hovering over a cell.
      2. Selection event that paints the rect of the last cell in a selected row(any cells in a row can trigger this).

      I'm starting to read up on delegates and views. I'm still abit unclear on who, the delegate or view, is responsible to call the update. For example I figured I need to get the current hovered index and retrieve the current row from that. I'm just not sure if the view needs to tell the delegate to update all items in that row or does the delegate collect the items in the row when I'm testing the State_MouseOver. As for the model my assumptions so far is that the model is responsible just for the data.

      I've found a similar thread for #1 in the cpp forum:
      https://forum.qt.io/topic/12794/mousehover-entire-row-selection-in-qtableview
      I'm having a bit of trouble translating the working code to Python. It looks like they set a setMouseOver(), disableMouseOver(), and a mouseMoveEvent(). In the code the Delegate has access to the view to call the custom setMouseOver() and pass the row. How can I get a similar behavior? or is the a better way to achieve my two goals?

      Cheers

      from PySide2 import QtGui, QtCore, QtWidgets
      import sys
      
      class CustomTableDelegate(QtWidgets.QStyledItemDelegate):
          def __init__(self, parent=None):
              super(CustomTableDelegate, self).__init__(parent)
      
          def paint(self, painter, option, index):
              if option.state & QtWidgets.QStyle.State_MouseOver:
                  painter.fillRect(option.rect, QtGui.QColor(143,143,143))
                  QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
              else:
                  painter.fillRect(option.rect, QtGui.QColor(38,38,38))
                  QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
      
      
      class CustomTableView(QtWidgets.QTableView):
          def __init__(self, parent=None):
              super(CustomTableView, self).__init__(parent)
      
          def setMouseOver(self, row):
              # print(self.model.columnCount())
              print(row)
      
      if __name__ == '__main__':
          app = QtWidgets.QApplication(sys.argv)
          data = [["v001", "stuff", "fixing source", '2020-01-14', ''],
                  ["v002", "more stuff", "currently broken", '2020-06-30', ''],
                  ["v003", "too much stuff", "scaling issues", '2020-02-08', ''],
                  ["v088", "still missing stuff", "not coment", '2020-11-13', ''],]
      
          table_view = CustomTableView()
          table_view.setMouseTracking(True)
          table_view.setItemDelegate(CustomTableDelegate())
          table_view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
          table_view.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
      
          header = table_view.horizontalHeader()
          header.setStretchLastSection(True)
          vertical_header = table_view.verticalHeader()
          vertical_header.hide()
      
          model = QtGui.QStandardItemModel()
          model.setHorizontalHeaderLabels(['version', 'file', 'comment', 'date'])
          table_view.setModel(model)
      
      
          for r in range(len(data)):
              for c in range(4):
                  model.setItem(r, c, QtGui.QStandardItem(data[r][c]))
      
          table_view.show()
          sys.exit(app.exec_())
      
      
      JonBJ Online
      JonBJ Online
      JonB
      wrote on last edited by
      #2

      @alom

      In the code the Delegate has access to the view to call the custom setMouseOver() and pass the row. How can I get a similar behavior?

      Just answering this bit. There is a lot of code/alternatives in the link you quote! But glancing through I see:

      TableView::TableView(QWidget *parent) : QTableView(parent), currHovered(-1)
      {
          Delegate *delegate = new Delegate;
          delegate->setView(this);
      

      So that is (one way) to have Delegate have access to the QTableView, which I think is what you asked in the quoted question.

      1 Reply Last reply
      0
      • alomA Offline
        alomA Offline
        alom
        wrote on last edited by
        #3

        @JonB
        Thanks, I should have mentioned that I was referencing the post from 26 Jan 2012, 03:17.

        So currently I'm assigning my custom delegate to the treeview

        table_view.setItemDelegate(CustomTableDelegate())
        

        My cpp is very minimal, but are they sub classing the tableview and creating a delegate instance inside of it? If so would I get rid of the setItemDelegate() on the treeview aswell?

        That was the only example I could find of someone trying something similar to my needs.

        1 Reply Last reply
        0
        • alomA Offline
          alomA Offline
          alom
          wrote on last edited by alom
          #4

          So I was able to get a "working" solution. If someone could take a look and see if this is the right idea that would really help me out. Qt sometimes give me a false sense that I've done something right then later realize I've completely butchered it :P

          I ended up struggling to find a way for the view to update the delegate and found the mouseMoveEvent to trigger a repaint. I passed the hovered row from the mouse to the delegate before the repaint.

          As for the paint() in the delegate, I've noticed in some examples uses the following:

          painter.save()
          ~some painter codes
          painter.restore()
          

          From the documentation: painter.save()
          "Saves the current painter state (pushes the state onto a stack)"
          That doesn't really help me under stand why this is needed, as if i comment it out, the code seems to still work. Can someone elaborate the use cases for the painter.save()/restore() please?

          from PySide2 import QtGui, QtCore, QtWidgets
          import sys
          
          class CustomTableDelegate(QtWidgets.QStyledItemDelegate):
              def __init__(self, parent=None):
                  super(CustomTableDelegate, self).__init__(parent)
                  self.__current_row = -1
          
              def paint(self, painter, option, index):
                  painter.save()
                  value = index.data(QtCore.Qt.DisplayRole)
                  if self.__current_row  == index.row():
                      if index.column() == 3:
                          painter.fillRect(option.rect, QtGui.QColor(143,143,143))
                          painter.setPen(QtGui.QColor(0,255,00))
                          painter.drawText(option.rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, value)
                      else:
                          painter.fillRect(option.rect, QtGui.QColor(100,100,100))
                          painter.setPen(QtGui.QColor(0, 255, 255))
                          painter.drawText(option.rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, value)
                  else:
                      painter.fillRect(option.rect, QtGui.QColor(38, 38, 38))
                      painter.setPen(QtGui.QColor(200, 200, 200))
                      painter.drawText(option.rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, value)
                  painter.restore()
          
              def sizeHint(self, *args, **kwargs):
                  pass
          
              def set_current_row(self, row):
                  self.__current_row = row
          
          
          
          class CustomTableView(QtWidgets.QTableView):
              def __init__(self, parent=None):
                  super(CustomTableView, self).__init__(parent)
          
              def mouseMoveEvent(self, event):
                  indx = self.indexAt(event.pos())
                  self.itemDelegate().set_current_row(indx.row())
                  self.viewport().repaint()
          
          
          if __name__ == '__main__':
              app = QtWidgets.QApplication(sys.argv)
              data = [["v001", "stuff", "fixing source", '2020-01-14', ''],
                      ["v002", "more stuff", "currently broken", '2020-06-30', ''],
                      ["v003", "too much stuff", "scaling issues", '2020-02-08', ''],
                      ["v088", "still missing stuff", "not coment", '2020-11-13', ''],]
          
              table_view = CustomTableView()
              table_view.setShowGrid(False)
              table_view.setMouseTracking(True)
              table_view.setItemDelegate(CustomTableDelegate(table_view))
              table_view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
              table_view.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
          
              header = table_view.horizontalHeader()
              header.setStretchLastSection(True)
              vertical_header = table_view.verticalHeader()
              vertical_header.hide()
          
              model = QtGui.QStandardItemModel()
              model.setHorizontalHeaderLabels(['version', 'file', 'comment', 'date'])
              table_view.setModel(model)
              for r in range(len(data)):
                  for c in range(4):
                      model.setItem(r, c, QtGui.QStandardItem(data[r][c]))
          
              table_view.show()
              sys.exit(app.exec_())
          
          1 Reply Last reply
          0
          • SGaistS Offline
            SGaistS Offline
            SGaist
            Lifetime Qt Champion
            wrote on last edited by
            #5

            Hi,

            The idea behind the painter save/restore combo is to keep said painter "clean" i.e. you return it in the same state as you received it. The fact that it seems to work the same way without it just means that there's no change that influence the next painting step.

            You could be using a dynamic chain of helper functions that modifies the painter, if you modify the transformation matrix in one of them and do not restore the painter before the next function, then this one will not paint the way you expect it.

            Interested in AI ? www.idiap.ch
            Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

            1 Reply Last reply
            1
            • alomA Offline
              alomA Offline
              alom
              wrote on last edited by
              #6

              @SGaist
              Thanks that definitely makes sense, I haven't done any complicated painting yet, just starting to read up on it now.

              Is using the mouseMoveEvent a good use to update the delegate in this case or is there a better way?
              Cheers

              1 Reply Last reply
              0
              • SGaistS Offline
                SGaistS Offline
                SGaist
                Lifetime Qt Champion
                wrote on last edited by
                #7

                Are you in fact moving the selection along the mouse move ?

                Interested in AI ? www.idiap.ch
                Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                1 Reply Last reply
                0
                • alomA Offline
                  alomA Offline
                  alom
                  wrote on last edited by
                  #8

                  the mouse move is meant to control just the highlighted text, I added some more code from above that tests the selection state to repaint the selection a bit differently.

                  if option.state & QtWidgets.QStyle.State_Selected:
                  # do selection repainting...
                  

                  I think it's working, at least visually it is :) i should print out the current selection just to be sure though.

                  1 Reply Last reply
                  0
                  • SGaistS Offline
                    SGaistS Offline
                    SGaist
                    Lifetime Qt Champion
                    wrote on last edited by
                    #9

                    Since it's only meant for highlighting, another possibility would be to use QRubberBand to draw a rectangle on top of your QTableView.

                    Interested in AI ? www.idiap.ch
                    Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                    1 Reply Last reply
                    0
                    • alomA Offline
                      alomA Offline
                      alom
                      wrote on last edited by
                      #10

                      interesting, I'll have a play with QRubberBand, Cheers!

                      1 Reply Last reply
                      0

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved