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. QStyledItemDelegate QSS implementation (PySide2)

QStyledItemDelegate QSS implementation (PySide2)

Scheduled Pinned Locked Moved Solved Qt for Python
9 Posts 2 Posters 1.8k Views
  • 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.
  • Q Offline
    Q Offline
    QtLand
    wrote on last edited by QtLand
    #1

    Hello,

    I've been struggling to get this working correctly in PySide2, but it feels like I'm 90% of the way there.
    There's a few issues that I'm unable to find enough resources on to solve by myself; I would really appreciate any help from more experienced users.

    Thanks

    Goal

    I've created a combobox delegate, which works as expected.
    I also want to draw the appearance of a QComboBox (including the associated QSS) when the user hovers over an item.
    On hover, the delegate should look identical to a QComboBox.

    Problem

    After some trial and error, I've managed to figure out how to achieve this, but unfortunately I needed to create an instance of QComboBox and it's also missing foreground/text color.

    Questions

    1. How do I draw the combobox (with its QSS), without creating an instance of the QComboBox within the delegate? (see comment "Q1." in example).
    2. How do I apply the foreground/text color? (see comment "Q2." in example).
    3. Looking at the source for QStyledItemDelegate::paint, is there an equivalent of QStyledItemDelegatePrivate::widget(option) in PySide2?
    4. Is there a better way to achieve what I'm attempting here? (draw combobox on hover)

    8929e340-6f66-4c37-bfe0-4913b7fb52af-image.png
    Working snippet attached below.

    Example

    The last column has the delegate assigned to it.
    When the user hovers over an item, it should appear the same as the QComboBox I've included above.
    ee34f0c2-ca69-4657-9ee9-840beadc5f2c-image.png
    However, as you can see it's currently missing the foreground/text color, but everything else appears to be working fine.

    Snippet

    import sys
    from PySide2 import QtCore, QtGui, QtWidgets
    
    QSS = """
    QComboBox {
      background: rgb(125, 125, 125);
      color: cyan;
      border: 2px solid transparent;
    }
    QComboBox QAbstractItemView {
      background: rgb(125, 125, 125);
      border: 2px solid cyan;
    }
    """
    TEST_DATA = ["AAA", "BBB", "CCC", "DDD"]
    
    
    class ExampleComboBoxDelegate(QtWidgets.QStyledItemDelegate):
        def __init__(self, parent: QtWidgets.QAbstractItemView):
            super().__init__(parent=parent)
            self.parent().setMouseTracking(True)
    
        def createEditor(self, parent, option, index) -> QtWidgets.QWidget:
            editor = QtWidgets.QComboBox(parent=parent)
            editor.addItems(TEST_DATA)
    
            return editor
    
        def setEditorData(self, editor, index):
            data = str(index.data(QtCore.Qt.DisplayRole))
            item_index = editor.findText(data)
    
            if item_index >= 0:
                editor.setCurrentIndex(item_index)
    
        def setModelData(self, editor: QtWidgets.QWidget, model, index):
            data = editor.currentText()
            model.setData(index, data)
    
        # No QSS.
        def paint(self, painter, option, index):
            painter.save()
    
            hover_opt = QtWidgets.QStyleOptionComboBox()
            hover_opt.rect = option.rect
            hover_opt.currentText = str(index.data(QtCore.Qt.DisplayRole))
            self.initStyleOption(option, index)
    
            # On hover, draw QComboBox.
            if option.state & QtWidgets.QStyle.State_MouseOver:
                style = option.widget.style() if option.widget else QtWidgets.QApplication.style()
                style.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, hover_opt, painter)
    
            painter.restore()
    
        # With QSS.
        def paint(self, painter, option, index):
            # Q1. How do I completely eliminate this step?
            cbox = QtWidgets.QComboBox(parent=self.parent())
            cbox.blockSignals(True)
            cbox.setVisible(False)
    
            # Paint.
            painter.save()
    
            self.initStyleOption(option, index)
            hover_opt = QtWidgets.QStyleOptionComboBox()
            hover_opt.initFrom(cbox)
            hover_opt.rect = option.rect
            hover_opt.currentText = str(index.data(QtCore.Qt.DisplayRole))
            style = cbox.style() or QtWidgets.QApplication.style()
    
            # On hover, draw QComboBox with QSS.
            if option.state & QtWidgets.QStyle.State_MouseOver:
                style.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, hover_opt, painter, cbox)
    
            # Draw text.
            # Q2. How do I apply the foreground/text color for this step from the QSS?
            style.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, hover_opt, painter, cbox)
    
            painter.restore()
    
    
    class Example(QtWidgets.QWidget):
        def __init__(self, parent: QtWidgets.QWidget = None):
            super().__init__(parent=parent)
            self.setStyleSheet(QSS)
            self.setMinimumSize(500, 500)
    
            self.__setup()
            self.__populate()
    
        def __setup(self):
            # Base.
            main_layout = QtWidgets.QVBoxLayout()
            self.setLayout(main_layout)
    
            # Styled combobox.
            lyt = QtWidgets.QHBoxLayout()
            self.__lbl = QtWidgets.QLabel("This is what the delegate should look like: ", self)
            self.__cbox = QtWidgets.QComboBox(self)
            self.__cbox.addItems(TEST_DATA)
            lyt.addWidget(self.__lbl)
            lyt.addWidget(self.__cbox)
            main_layout.addLayout(lyt)
    
            # Table.
            self.__model = QtGui.QStandardItemModel()
    
            self.__table = QtWidgets.QTableView(self)
            self.__table.setModel(self.__model)
            main_layout.addWidget(self.__table)
    
        def __populate(self):
            # Populate with test data.
            for index in range(0, 10):
                items = []
    
                for item_data in TEST_DATA:
                    item = QtGui.QStandardItem(item_data)
                    items.append(item)
    
                self.__model.appendRow(items)
    
            # Delegate.
            self.__table.setItemDelegateForColumn(3, ExampleComboBoxDelegate(parent=self.__table))
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
    
        window = Example()
        window.show()
        sys.exit(app.exec_())
    
    
    JonBJ 1 Reply Last reply
    0
    • Q QtLand

      How do I draw the combobox (with its QSS), without creating an instance of the QComboBox within the delegate? (see comment "Q1." in example).

      Even if there's no better solution for my first question, I can live with the dummy widget.

      How do I apply the foreground/text color? (see comment "Q2." in example).

      However, this has to be possible somehow, since my example implementation is already taking into account the QSS for everything (seemingly) besides the foreground/text of the control.

      Why must Qt be so difficult at times? 😭

      Q Offline
      Q Offline
      QtLand
      wrote on last edited by QtLand
      #9

      Leaving my working solution here.

      How do I draw the combobox (with its QSS), without creating an instance of the QComboBox within the delegate? (see comment "Q1." in example).

      This remains unresolved. It's not a big deal.

      How do I apply the foreground/text color? (see comment "Q2." in example).

      I apparently created this "bug" in my example, it did not exist in my working code and is no longer an issue.
      If the dummy widget is created in paint, Qt ignores the foreground/text palette.
      Moving it to __init__ resolves the issue.

      This doesn't make any sense to me, but it is what it is. 🤷‍♂️

      Solution

      My cursor is hovering over row 3, col 4.
      ed6ce536-2cf7-4fe0-b6a9-7811831564c3-image.png

      Respects the item's background/foreground roles as well:
      c5977c18-85c9-4b6a-b675-d7d65f59b724-image.png

      Snippet

      import sys
      from PySide2 import QtCore, QtGui, QtWidgets
      
      QSS = """
      QComboBox {
        background: rgb(125, 125, 125);
        color: cyan;
        border: 2px solid transparent;
      }
      QComboBox QAbstractItemView {
        background: rgb(125, 125, 125);
        border: 2px solid cyan;
      }
      """
      TEST_DATA = ["AAA", "BBB", "CCC", "DDD"]
      
      
      class ExampleComboBoxDelegate(QtWidgets.QStyledItemDelegate):
          def __init__(self, parent: QtWidgets.QAbstractItemView):
              super().__init__(parent=parent)
              self.parent().setMouseTracking(True)
      
              # Q1. How do I completely eliminate this step?
              self.__proxy_cbox = QtWidgets.QComboBox(parent=self.parent())
              self.__proxy_cbox.blockSignals(True)
              self.__proxy_cbox.setVisible(False)
      
          def createEditor(self, parent, option, index) -> QtWidgets.QWidget:
              editor = QtWidgets.QComboBox(parent=parent)
              editor.addItems(TEST_DATA)
      
              return editor
      
          def setEditorData(self, editor, index):
              data = str(index.data(QtCore.Qt.DisplayRole))
              item_index = editor.findText(data)
      
              if item_index >= 0:
                  editor.setCurrentIndex(item_index)
      
          def setModelData(self, editor: QtWidgets.QWidget, model, index):
              data = editor.currentText()
              model.setData(index, data)
      
          # With QSS.
          def paint(self, painter, option, index):
              # Paint.
              painter.save()
      
              self.initStyleOption(option, index)
      
              widget_option = QtWidgets.QStyleOptionComboBox()
              widget_option.initFrom(self.__proxy_cbox)  # Inherit style from QComboBox.
              widget_option.rect = option.rect
              widget_option.currentText = str(index.data(QtCore.Qt.DisplayRole))
      
              style = self.__proxy_cbox.style() or QtWidgets.QApplication.style()
      
              # Hover: Draw QComboBox control without widget implementation.
              if option.state & QtWidgets.QStyle.State_MouseOver:
                  style.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, widget_option, painter, self.__proxy_cbox)
                  style.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, widget_option, painter, self.__proxy_cbox)
      
              # Draw standard control.
              else:
                  style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter, self.__proxy_cbox)
      
              painter.restore()
      
      
      class Example(QtWidgets.QWidget):
          def __init__(self, parent: QtWidgets.QWidget = None):
              super().__init__(parent=parent)
              self.setStyleSheet(QSS)
              self.setMinimumSize(500, 500)
      
              self.__setup()
              self.__populate()
      
          def __setup(self):
              # Base.
              main_layout = QtWidgets.QVBoxLayout()
              self.setLayout(main_layout)
      
              # Styled combobox.
              lyt = QtWidgets.QHBoxLayout()
              self.__lbl = QtWidgets.QLabel("This is what the delegate should look like: ", self)
              self.__cbox = QtWidgets.QComboBox(self)
              self.__cbox.addItems(TEST_DATA)
              lyt.addWidget(self.__lbl)
              lyt.addWidget(self.__cbox)
              main_layout.addLayout(lyt)
      
              # Table.
              self.__model = QtGui.QStandardItemModel()
      
              self.__table = QtWidgets.QTableView(self)
              self.__table.setModel(self.__model)
              main_layout.addWidget(self.__table)
      
          def __populate(self):
              # Populate with test data.
              for index in range(0, 10):
                  items = []
      
                  for item_data in TEST_DATA:
                      item = QtGui.QStandardItem(item_data)
                      # item.setBackground(QtGui.QColor(255, 0, 0))
                      items.append(item)
      
                  self.__model.appendRow(items)
      
              # Delegate.
              self.__table.setItemDelegateForColumn(3, ExampleComboBoxDelegate(parent=self.__table))
      
      
      if __name__ == '__main__':
          app = QtWidgets.QApplication(sys.argv)
      
          window = Example()
          window.show()
          sys.exit(app.exec_())
      
      

      Thanks for the help @JonB

      1 Reply Last reply
      1
      • Q QtLand

        Hello,

        I've been struggling to get this working correctly in PySide2, but it feels like I'm 90% of the way there.
        There's a few issues that I'm unable to find enough resources on to solve by myself; I would really appreciate any help from more experienced users.

        Thanks

        Goal

        I've created a combobox delegate, which works as expected.
        I also want to draw the appearance of a QComboBox (including the associated QSS) when the user hovers over an item.
        On hover, the delegate should look identical to a QComboBox.

        Problem

        After some trial and error, I've managed to figure out how to achieve this, but unfortunately I needed to create an instance of QComboBox and it's also missing foreground/text color.

        Questions

        1. How do I draw the combobox (with its QSS), without creating an instance of the QComboBox within the delegate? (see comment "Q1." in example).
        2. How do I apply the foreground/text color? (see comment "Q2." in example).
        3. Looking at the source for QStyledItemDelegate::paint, is there an equivalent of QStyledItemDelegatePrivate::widget(option) in PySide2?
        4. Is there a better way to achieve what I'm attempting here? (draw combobox on hover)

        8929e340-6f66-4c37-bfe0-4913b7fb52af-image.png
        Working snippet attached below.

        Example

        The last column has the delegate assigned to it.
        When the user hovers over an item, it should appear the same as the QComboBox I've included above.
        ee34f0c2-ca69-4657-9ee9-840beadc5f2c-image.png
        However, as you can see it's currently missing the foreground/text color, but everything else appears to be working fine.

        Snippet

        import sys
        from PySide2 import QtCore, QtGui, QtWidgets
        
        QSS = """
        QComboBox {
          background: rgb(125, 125, 125);
          color: cyan;
          border: 2px solid transparent;
        }
        QComboBox QAbstractItemView {
          background: rgb(125, 125, 125);
          border: 2px solid cyan;
        }
        """
        TEST_DATA = ["AAA", "BBB", "CCC", "DDD"]
        
        
        class ExampleComboBoxDelegate(QtWidgets.QStyledItemDelegate):
            def __init__(self, parent: QtWidgets.QAbstractItemView):
                super().__init__(parent=parent)
                self.parent().setMouseTracking(True)
        
            def createEditor(self, parent, option, index) -> QtWidgets.QWidget:
                editor = QtWidgets.QComboBox(parent=parent)
                editor.addItems(TEST_DATA)
        
                return editor
        
            def setEditorData(self, editor, index):
                data = str(index.data(QtCore.Qt.DisplayRole))
                item_index = editor.findText(data)
        
                if item_index >= 0:
                    editor.setCurrentIndex(item_index)
        
            def setModelData(self, editor: QtWidgets.QWidget, model, index):
                data = editor.currentText()
                model.setData(index, data)
        
            # No QSS.
            def paint(self, painter, option, index):
                painter.save()
        
                hover_opt = QtWidgets.QStyleOptionComboBox()
                hover_opt.rect = option.rect
                hover_opt.currentText = str(index.data(QtCore.Qt.DisplayRole))
                self.initStyleOption(option, index)
        
                # On hover, draw QComboBox.
                if option.state & QtWidgets.QStyle.State_MouseOver:
                    style = option.widget.style() if option.widget else QtWidgets.QApplication.style()
                    style.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, hover_opt, painter)
        
                painter.restore()
        
            # With QSS.
            def paint(self, painter, option, index):
                # Q1. How do I completely eliminate this step?
                cbox = QtWidgets.QComboBox(parent=self.parent())
                cbox.blockSignals(True)
                cbox.setVisible(False)
        
                # Paint.
                painter.save()
        
                self.initStyleOption(option, index)
                hover_opt = QtWidgets.QStyleOptionComboBox()
                hover_opt.initFrom(cbox)
                hover_opt.rect = option.rect
                hover_opt.currentText = str(index.data(QtCore.Qt.DisplayRole))
                style = cbox.style() or QtWidgets.QApplication.style()
        
                # On hover, draw QComboBox with QSS.
                if option.state & QtWidgets.QStyle.State_MouseOver:
                    style.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, hover_opt, painter, cbox)
        
                # Draw text.
                # Q2. How do I apply the foreground/text color for this step from the QSS?
                style.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, hover_opt, painter, cbox)
        
                painter.restore()
        
        
        class Example(QtWidgets.QWidget):
            def __init__(self, parent: QtWidgets.QWidget = None):
                super().__init__(parent=parent)
                self.setStyleSheet(QSS)
                self.setMinimumSize(500, 500)
        
                self.__setup()
                self.__populate()
        
            def __setup(self):
                # Base.
                main_layout = QtWidgets.QVBoxLayout()
                self.setLayout(main_layout)
        
                # Styled combobox.
                lyt = QtWidgets.QHBoxLayout()
                self.__lbl = QtWidgets.QLabel("This is what the delegate should look like: ", self)
                self.__cbox = QtWidgets.QComboBox(self)
                self.__cbox.addItems(TEST_DATA)
                lyt.addWidget(self.__lbl)
                lyt.addWidget(self.__cbox)
                main_layout.addLayout(lyt)
        
                # Table.
                self.__model = QtGui.QStandardItemModel()
        
                self.__table = QtWidgets.QTableView(self)
                self.__table.setModel(self.__model)
                main_layout.addWidget(self.__table)
        
            def __populate(self):
                # Populate with test data.
                for index in range(0, 10):
                    items = []
        
                    for item_data in TEST_DATA:
                        item = QtGui.QStandardItem(item_data)
                        items.append(item)
        
                    self.__model.appendRow(items)
        
                # Delegate.
                self.__table.setItemDelegateForColumn(3, ExampleComboBoxDelegate(parent=self.__table))
        
        
        if __name__ == '__main__':
            app = QtWidgets.QApplication(sys.argv)
        
            window = Example()
            window.show()
            sys.exit(app.exec_())
        
        
        JonBJ Offline
        JonBJ Offline
        JonB
        wrote on last edited by JonB
        #2

        @QtLand
        I don't know the answers to a lot of this, but I think I do to two:

        Looking at the source for QStyledItemDelegate::paint, is there an equivalent of QStyledItemDelegatePrivate::widget(option) in PySide2?

        No, because anything with ...Private is internal and not exposed to you/the outside world from the C++ definition. PySide is not going to allow you to access it.

        How do I draw the combobox (with its QSS), without creating an instance of the QComboBox within the delegate? (see comment "Q1." in example).

        I don't know whether you have to do it this way, but I think your implementation will cause headaches:

        cbox = QtWidgets.QComboBox(parent=self.parent())

        I would not want to specify any parent here. Do you have to in order to make something work? By giving it a parent you are making it a "real, visible" combobox in the hierarchy. That's probably why you have to blockSignals(True) and setVisible(False). Have you tried just cbox = QtWidgets.QComboBox() here? Also, unless I am missing something, your implementation leaves an (invisible) combobox attached to the parent forever?

        Q 1 Reply Last reply
        0
        • JonBJ JonB

          @QtLand
          I don't know the answers to a lot of this, but I think I do to two:

          Looking at the source for QStyledItemDelegate::paint, is there an equivalent of QStyledItemDelegatePrivate::widget(option) in PySide2?

          No, because anything with ...Private is internal and not exposed to you/the outside world from the C++ definition. PySide is not going to allow you to access it.

          How do I draw the combobox (with its QSS), without creating an instance of the QComboBox within the delegate? (see comment "Q1." in example).

          I don't know whether you have to do it this way, but I think your implementation will cause headaches:

          cbox = QtWidgets.QComboBox(parent=self.parent())

          I would not want to specify any parent here. Do you have to in order to make something work? By giving it a parent you are making it a "real, visible" combobox in the hierarchy. That's probably why you have to blockSignals(True) and setVisible(False). Have you tried just cbox = QtWidgets.QComboBox() here? Also, unless I am missing something, your implementation leaves an (invisible) combobox attached to the parent forever?

          Q Offline
          Q Offline
          QtLand
          wrote on last edited by QtLand
          #3

          HI @JonB

          @JonB said in QStyledItemDelegate QSS implementation (PySide2):

          No, because anything with ...Private is internal and not exposed to you/the outside world from the C++ definition. PySide is not going to allow you to access it.

          Yeah I figured as much; I was curious about what ::widget even refers to, but I wasn't able to find any implementation of it in the source code for QStyledItemDelegate or QAbstractItemDelegate.

          I don't know whether you have to do it this way, but I think your implementation will cause headaches:

          Yes, while my implementation works, I agree it isn't ideal, which is why I'm asking if there's a way to avoid doing it this way.
          Problem is I can't figure out how to inherit the QSS for QComboBox without actually creating an instance of one.

          I would not want to specify any parent here. Do you have to in order to make something work?

          By not providing the parent, it won't inherit the QSS from a top-level widget, such as a global stylesheet applied to the QMainWindow or QDialog.
          The QComboBox will have default styling instead of the one I've defined.

          JonBJ 2 Replies Last reply
          0
          • Q QtLand

            HI @JonB

            @JonB said in QStyledItemDelegate QSS implementation (PySide2):

            No, because anything with ...Private is internal and not exposed to you/the outside world from the C++ definition. PySide is not going to allow you to access it.

            Yeah I figured as much; I was curious about what ::widget even refers to, but I wasn't able to find any implementation of it in the source code for QStyledItemDelegate or QAbstractItemDelegate.

            I don't know whether you have to do it this way, but I think your implementation will cause headaches:

            Yes, while my implementation works, I agree it isn't ideal, which is why I'm asking if there's a way to avoid doing it this way.
            Problem is I can't figure out how to inherit the QSS for QComboBox without actually creating an instance of one.

            I would not want to specify any parent here. Do you have to in order to make something work?

            By not providing the parent, it won't inherit the QSS from a top-level widget, such as a global stylesheet applied to the QMainWindow or QDialog.
            The QComboBox will have default styling instead of the one I've defined.

            JonBJ Offline
            JonBJ Offline
            JonB
            wrote on last edited by
            #4

            @QtLand said in QStyledItemDelegate QSS implementation (PySide2):

            By not providing the parent, it won't inherit the QSS from a top-level widget, such as a global stylesheet applied to the QMainWindow or QDialog.

            Ahhhh, I see.

            I don't think having to create a "dummy" widget to access its properties is necessarily bad, I have done it myself and seen others do it. I do think you should check whether I am right that your implementation leaves the combobox in permanent existence, though.

            Q 1 Reply Last reply
            0
            • JonBJ JonB

              @QtLand said in QStyledItemDelegate QSS implementation (PySide2):

              By not providing the parent, it won't inherit the QSS from a top-level widget, such as a global stylesheet applied to the QMainWindow or QDialog.

              Ahhhh, I see.

              I don't think having to create a "dummy" widget to access its properties is necessarily bad, I have done it myself and seen others do it. I do think you should check whether I am right that your implementation leaves the combobox in permanent existence, though.

              Q Offline
              Q Offline
              QtLand
              wrote on last edited by
              #5

              @JonB said in QStyledItemDelegate QSS implementation (PySide2):

              I don't think having to create a "dummy" widget to access its properties is necessarily bad, I have done it myself and seen others do it. I do think you should check whether I am right that your implementation leaves the combobox in permanent existence, though.

              That's a fair point; I significantly simplified this example so I missed that detail.
              In my working code, I'm creating the dummy widget in the __init__ of the delegate, rather than within paint implementation.
              So while it does create a permanent invisible QComboBox, only one instance exists, rather than multiple premanent instances being created every time paint is called, as in my example code.

              1 Reply Last reply
              1
              • Q QtLand

                HI @JonB

                @JonB said in QStyledItemDelegate QSS implementation (PySide2):

                No, because anything with ...Private is internal and not exposed to you/the outside world from the C++ definition. PySide is not going to allow you to access it.

                Yeah I figured as much; I was curious about what ::widget even refers to, but I wasn't able to find any implementation of it in the source code for QStyledItemDelegate or QAbstractItemDelegate.

                I don't know whether you have to do it this way, but I think your implementation will cause headaches:

                Yes, while my implementation works, I agree it isn't ideal, which is why I'm asking if there's a way to avoid doing it this way.
                Problem is I can't figure out how to inherit the QSS for QComboBox without actually creating an instance of one.

                I would not want to specify any parent here. Do you have to in order to make something work?

                By not providing the parent, it won't inherit the QSS from a top-level widget, such as a global stylesheet applied to the QMainWindow or QDialog.
                The QComboBox will have default styling instead of the one I've defined.

                JonBJ Offline
                JonBJ Offline
                JonB
                wrote on last edited by JonB
                #6

                @QtLand
                I think https://stackoverflow.com/questions/21516991/qt-qss-and-drawcomplexcontrol from 10 years ago is going down much the same route as you have!

                Did you see the accepted solution:

                I think that the only way is to grab widget with QPixmap::grabWidget(). And to use this image in delegate. Seems, that it is not possible to do because of QSS limitation

                ? Seems scary, but interesting. Did you try it, I'm not sure whether it's indicating that should work or does not?

                And see the discussion in https://stackoverflow.com/questions/19138100/how-to-draw-control-with-qstyle-and-with-specified-qss. And our own @SGaist in https://forum.qt.io/topic/37291/qss-and-drawcomplexcontrol here a mere decade ago.

                Q 1 Reply Last reply
                0
                • JonBJ JonB

                  @QtLand
                  I think https://stackoverflow.com/questions/21516991/qt-qss-and-drawcomplexcontrol from 10 years ago is going down much the same route as you have!

                  Did you see the accepted solution:

                  I think that the only way is to grab widget with QPixmap::grabWidget(). And to use this image in delegate. Seems, that it is not possible to do because of QSS limitation

                  ? Seems scary, but interesting. Did you try it, I'm not sure whether it's indicating that should work or does not?

                  And see the discussion in https://stackoverflow.com/questions/19138100/how-to-draw-control-with-qstyle-and-with-specified-qss. And our own @SGaist in https://forum.qt.io/topic/37291/qss-and-drawcomplexcontrol here a mere decade ago.

                  Q Offline
                  Q Offline
                  QtLand
                  wrote on last edited by
                  #7

                  @JonB said in QStyledItemDelegate QSS implementation (PySide2):

                  ? Seems scary, but interesting. Did you try it, I'm not sure whether it's indicating that should work or does not?

                  This implementation has a few problems:

                  • It doesn't render the text at all, only the control.
                  • A painted pixmap would simply stretch the image when the width/height changes of the column/row, since the widget it was drawn from has a different size. You'd have to force the dummy widget to scale with the delegate occupied column and the associated row.

                  I decided to test it out anyways:

                      def paint(self, painter, option, index):
                          cbox = QtWidgets.QComboBox(parent=self.parent())
                          cbox.blockSignals(True)
                          cbox.setVisible(False)
                  
                          pxm = cbox.grab()  # QPixmap.grabWidget() is deprecated.
                  
                          painter.save()
                  
                          if option.state & QtWidgets.QStyle.State_MouseOver:
                              painter.drawPixmap(option.rect, pxm)
                  
                          painter.restore()
                  

                  Resized the column:
                  cf1c6477-4efa-497f-be41-a6a1bd4e83b5-image.png

                  Q 1 Reply Last reply
                  0
                  • Q QtLand

                    @JonB said in QStyledItemDelegate QSS implementation (PySide2):

                    ? Seems scary, but interesting. Did you try it, I'm not sure whether it's indicating that should work or does not?

                    This implementation has a few problems:

                    • It doesn't render the text at all, only the control.
                    • A painted pixmap would simply stretch the image when the width/height changes of the column/row, since the widget it was drawn from has a different size. You'd have to force the dummy widget to scale with the delegate occupied column and the associated row.

                    I decided to test it out anyways:

                        def paint(self, painter, option, index):
                            cbox = QtWidgets.QComboBox(parent=self.parent())
                            cbox.blockSignals(True)
                            cbox.setVisible(False)
                    
                            pxm = cbox.grab()  # QPixmap.grabWidget() is deprecated.
                    
                            painter.save()
                    
                            if option.state & QtWidgets.QStyle.State_MouseOver:
                                painter.drawPixmap(option.rect, pxm)
                    
                            painter.restore()
                    

                    Resized the column:
                    cf1c6477-4efa-497f-be41-a6a1bd4e83b5-image.png

                    Q Offline
                    Q Offline
                    QtLand
                    wrote on last edited by
                    #8

                    How do I draw the combobox (with its QSS), without creating an instance of the QComboBox within the delegate? (see comment "Q1." in example).

                    Even if there's no better solution for my first question, I can live with the dummy widget.

                    How do I apply the foreground/text color? (see comment "Q2." in example).

                    However, this has to be possible somehow, since my example implementation is already taking into account the QSS for everything (seemingly) besides the foreground/text of the control.

                    Why must Qt be so difficult at times? 😭

                    Q 1 Reply Last reply
                    0
                    • Q QtLand

                      How do I draw the combobox (with its QSS), without creating an instance of the QComboBox within the delegate? (see comment "Q1." in example).

                      Even if there's no better solution for my first question, I can live with the dummy widget.

                      How do I apply the foreground/text color? (see comment "Q2." in example).

                      However, this has to be possible somehow, since my example implementation is already taking into account the QSS for everything (seemingly) besides the foreground/text of the control.

                      Why must Qt be so difficult at times? 😭

                      Q Offline
                      Q Offline
                      QtLand
                      wrote on last edited by QtLand
                      #9

                      Leaving my working solution here.

                      How do I draw the combobox (with its QSS), without creating an instance of the QComboBox within the delegate? (see comment "Q1." in example).

                      This remains unresolved. It's not a big deal.

                      How do I apply the foreground/text color? (see comment "Q2." in example).

                      I apparently created this "bug" in my example, it did not exist in my working code and is no longer an issue.
                      If the dummy widget is created in paint, Qt ignores the foreground/text palette.
                      Moving it to __init__ resolves the issue.

                      This doesn't make any sense to me, but it is what it is. 🤷‍♂️

                      Solution

                      My cursor is hovering over row 3, col 4.
                      ed6ce536-2cf7-4fe0-b6a9-7811831564c3-image.png

                      Respects the item's background/foreground roles as well:
                      c5977c18-85c9-4b6a-b675-d7d65f59b724-image.png

                      Snippet

                      import sys
                      from PySide2 import QtCore, QtGui, QtWidgets
                      
                      QSS = """
                      QComboBox {
                        background: rgb(125, 125, 125);
                        color: cyan;
                        border: 2px solid transparent;
                      }
                      QComboBox QAbstractItemView {
                        background: rgb(125, 125, 125);
                        border: 2px solid cyan;
                      }
                      """
                      TEST_DATA = ["AAA", "BBB", "CCC", "DDD"]
                      
                      
                      class ExampleComboBoxDelegate(QtWidgets.QStyledItemDelegate):
                          def __init__(self, parent: QtWidgets.QAbstractItemView):
                              super().__init__(parent=parent)
                              self.parent().setMouseTracking(True)
                      
                              # Q1. How do I completely eliminate this step?
                              self.__proxy_cbox = QtWidgets.QComboBox(parent=self.parent())
                              self.__proxy_cbox.blockSignals(True)
                              self.__proxy_cbox.setVisible(False)
                      
                          def createEditor(self, parent, option, index) -> QtWidgets.QWidget:
                              editor = QtWidgets.QComboBox(parent=parent)
                              editor.addItems(TEST_DATA)
                      
                              return editor
                      
                          def setEditorData(self, editor, index):
                              data = str(index.data(QtCore.Qt.DisplayRole))
                              item_index = editor.findText(data)
                      
                              if item_index >= 0:
                                  editor.setCurrentIndex(item_index)
                      
                          def setModelData(self, editor: QtWidgets.QWidget, model, index):
                              data = editor.currentText()
                              model.setData(index, data)
                      
                          # With QSS.
                          def paint(self, painter, option, index):
                              # Paint.
                              painter.save()
                      
                              self.initStyleOption(option, index)
                      
                              widget_option = QtWidgets.QStyleOptionComboBox()
                              widget_option.initFrom(self.__proxy_cbox)  # Inherit style from QComboBox.
                              widget_option.rect = option.rect
                              widget_option.currentText = str(index.data(QtCore.Qt.DisplayRole))
                      
                              style = self.__proxy_cbox.style() or QtWidgets.QApplication.style()
                      
                              # Hover: Draw QComboBox control without widget implementation.
                              if option.state & QtWidgets.QStyle.State_MouseOver:
                                  style.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, widget_option, painter, self.__proxy_cbox)
                                  style.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, widget_option, painter, self.__proxy_cbox)
                      
                              # Draw standard control.
                              else:
                                  style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter, self.__proxy_cbox)
                      
                              painter.restore()
                      
                      
                      class Example(QtWidgets.QWidget):
                          def __init__(self, parent: QtWidgets.QWidget = None):
                              super().__init__(parent=parent)
                              self.setStyleSheet(QSS)
                              self.setMinimumSize(500, 500)
                      
                              self.__setup()
                              self.__populate()
                      
                          def __setup(self):
                              # Base.
                              main_layout = QtWidgets.QVBoxLayout()
                              self.setLayout(main_layout)
                      
                              # Styled combobox.
                              lyt = QtWidgets.QHBoxLayout()
                              self.__lbl = QtWidgets.QLabel("This is what the delegate should look like: ", self)
                              self.__cbox = QtWidgets.QComboBox(self)
                              self.__cbox.addItems(TEST_DATA)
                              lyt.addWidget(self.__lbl)
                              lyt.addWidget(self.__cbox)
                              main_layout.addLayout(lyt)
                      
                              # Table.
                              self.__model = QtGui.QStandardItemModel()
                      
                              self.__table = QtWidgets.QTableView(self)
                              self.__table.setModel(self.__model)
                              main_layout.addWidget(self.__table)
                      
                          def __populate(self):
                              # Populate with test data.
                              for index in range(0, 10):
                                  items = []
                      
                                  for item_data in TEST_DATA:
                                      item = QtGui.QStandardItem(item_data)
                                      # item.setBackground(QtGui.QColor(255, 0, 0))
                                      items.append(item)
                      
                                  self.__model.appendRow(items)
                      
                              # Delegate.
                              self.__table.setItemDelegateForColumn(3, ExampleComboBoxDelegate(parent=self.__table))
                      
                      
                      if __name__ == '__main__':
                          app = QtWidgets.QApplication(sys.argv)
                      
                          window = Example()
                          window.show()
                          sys.exit(app.exec_())
                      
                      

                      Thanks for the help @JonB

                      1 Reply Last reply
                      1
                      • Q QtLand has marked this topic as solved on
                      • Q QtLand has marked this topic as unsolved on
                      • Q QtLand has marked this topic as solved on

                      • Login

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