Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Add an always visible spinbox colum in QtableView



  • Re: How to add a spinbox column and combo box column in QTableWidget or QTableView?

    Hello everyone !

    I'm bumping this topic because I need to perform the same thing as my fellow. It's kind of hard to find such example on the web, so I need your help.

    So, I need to integrate a spinbox in a TableView, and to have this spinbox always visible and "clickable".

    I have few questions:

    • I understood that I have to use in some way a QStyleOptionsSpinBox, but I don't have any idea of how to use its specific attributes like buttonSymbols. In fact, I don't really have the idea of how to use this option item in my situation.
    • Related to the first point, how can I draw the different elements of the spinbox like the buttons ?
    • I want to retrieve the index of the spin box. However, it's located in my QAbstractTableModel. I don't have any idea of to properly get this information from this delegate.

    Note that I'm using python Pyside2 binding, which doesn't change anything related to the function signatures and usage..

    class SpinBoxDelegate(QtWidgets.QStyledItemDelegate):
        def __init__(self, parent):
            super().__init__(parent)
    
        def createEditor(self, parent, option, index):
            return QtWidgets.QSpinBox(parent)
    
        def setEditorData(self, editor, index):
            editor.setValue(int(index.data()))
    
        def updateEditorGeometry(self, editor, option, index):
            editor.setGeometry(option.rect)
    
        def paint(self, painter, option, index):
            painter.save()
    
            # How to use all the specific attributes of this option ?
            option_spinbox = QtWidgets.QStyleOptionSpinBox()
            option_spinbox.rect = option.rect
            option_spinbox.state = option.state
    
            import pdb
            pdb.set_trace()
    
            # Here, I want to retrieve the spin box index, which is stored in my model...
            painter.drawText(option_spinbox.rect, "Test")
    
            if option_spinbox.state & QtWidgets.QStyle.State_Selected:
                painter.setPen(QtGui.QPen(QtGui.QColor(255, 0, 0)))
                painter.drawRect(option_spinbox.rect)
                painter.restore()
                return
    
            painter.setPen(QtGui.QPen(QtGui.QColor(0, 255, 0)))
            painter.drawRect(option_spinbox.rect)
    
            painter.restore()
            return
    

    For the moment, here is the result of



  • I finally achieved to a good little result:

    demo

    I have some questions remaining:
    For the moment, the editor shows when double clicking anywhere on the column 2.
    I would like to show the editor only if the user clicked on the number on the center, and not on the arrows on the side.

    For that, I would need to retrieve the mouse position when entering the function createEditor, but I don't know how to do.
    I guess that I can re-implement in some way the function that makes the editor popping...

    Also, is my code ugly in term of conception ? What can I improve to make it prettier then ?

    from PySide2 import QtCore, QtWidgets, QtGui
    from model_ui.segment_model import Columns
    
    class SpinBoxDelegate(QtWidgets.QStyledItemDelegate):
        """This delegates allow a custom display of a spinbox into a tableview"""
        def __init__(self, parent):
            super().__init__(parent)
    
        def is_segment_analyzed(self, index):
            """Checks from a given SegmentModel index, if the corresponding
            segment have been analyzed
            """
    
            analyzed_index = index.siblingAtColumn(Columns.analyzing.value)
            return not index.model().get_attribute_from_index(analyzed_index)
    
        def createEditor(self, parent, option, index):
            """Returns a new SpinBox widget if the segment have been analyzed"""
            if self.is_segment_analyzed(index):
                return QtWidgets.QSpinBox(parent)
            return None
    
        def setEditorData(self, editor, index):
            """Handles the way editor store new data"""
            editor.setValue(int(index.data()))
    
        def editorEvent(self, event, model, option, index):
            """This function is used to handle the interactions with handmade
            buttons.
            """
    
            # If the event is a click event
            if event.type() == QtCore.QEvent.MouseButtonRelease:
                # Retrieves the button positions
                left_arrow_button, right_arrow_button = self.get_left_right_arrow_buttons(option)
    
                # Retrieves the combo index value from the model index
                current_combo_value = int(index.data())
    
                value_changed = False
                # If left button was clicked, we decrease combo index
                if left_arrow_button.contains(event.pos()):
                    if current_combo_value > 0:
                        value_changed = True
                        current_combo_value = current_combo_value - 1
    
                # If left button was clicked, we increase combo index
                if right_arrow_button.contains(event.pos()):
                    # TODO: Globally handle right combo limit (for all the project)
                    value_changed = True
                    current_combo_value = current_combo_value + 1
    
                # Report the changes to the model
                if value_changed:
                    model.setData(index, current_combo_value)
    
        def updateEditorGeometry(self, editor, option, index):
            editor.setGeometry(option.rect)
    
        def get_left_arrow_button(self, option):
            """Creates the left arrow button, depending on the option rect area"""
            arrow_buttons_size = option.rect.height()//1.5
            left_arrow_box = QtCore.QRect(
                option.rect.left()+1,
                option.rect.top()+(option.rect.height()//4.5),
                arrow_buttons_size,
                arrow_buttons_size)
            return left_arrow_box
    
        def paint_left_arrow_button(self, painter, option):
            """Creates the left arrow button et paints it"""
            left_arrow_box = self.get_left_arrow_button(option)
            painter.drawRect(left_arrow_box)
            painter.fillRect(left_arrow_box, QtGui.QColor(255, 255, 255))
            painter.drawText(left_arrow_box, QtCore.Qt.AlignCenter, "<")
    
        def get_right_arrow_button(self, option):
            """Creates the right arrow button, depending on the option rect area"""
            arrow_buttons_size = option.rect.height()//1.5
            right_arrow_box = QtCore.QRect(
                option.rect.right()-arrow_buttons_size,
                option.rect.top()+(option.rect.height()//4.5),
                arrow_buttons_size,
                arrow_buttons_size)
            return right_arrow_box
    
        def paint_right_arrow_button(self, painter, option):
            """Creates the right arrow button et paints it"""
            right_arrow_box = self.get_right_arrow_button(option)
            painter.drawRect(right_arrow_box)
            painter.fillRect(right_arrow_box, QtGui.QColor(255, 255, 255))
            painter.drawText(right_arrow_box, QtCore.Qt.AlignCenter, ">")
    
        def get_left_right_arrow_buttons(self, option):
            return self.get_left_arrow_button(option), self.get_right_arrow_button(option)
    
        def paint_left_right_arrow_buttons(self, painter, option):
            self.paint_left_arrow_button(painter, option)
            self.paint_right_arrow_button(painter, option)
    
        def paint(self, painter, option, index):
            painter.save()
    
            if not self.is_segment_analyzed(index):
                painter.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255)))
                painter.drawRect(option.rect)
                painter.restore()
                return
    
            option_spinbox = QtWidgets.QStyleOptionSpinBox()
            option_spinbox.rect = option.rect
            option_spinbox.state = option.state
    
            # By default: white pen (eraser)
            painter.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255)))
    
            # If the segment have been analyzed: we draw information
            # Else, we proint a blank cell
            if self.is_segment_analyzed(index):
                painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0)))
    
                painter.drawText(option_spinbox.rect, QtCore.Qt.AlignCenter,
                                 str(index.data()))
    
                self.paint_left_right_arrow_buttons(painter, option_spinbox)
    
                painter.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255)))
    
                # If the segment associated to the spinbox is selected: green line
                if option_spinbox.state & QtWidgets.QStyle.State_Selected:
                    painter.setPen(QtGui.QPen(QtGui.QColor(0, 255, 0)))
    
            # Drawing boundaries
            painter.drawRect(option_spinbox.rect)
    
            painter.restore()
    
        def sizeHint(self, option, index):
            """Gives an hint about the minimal size of the element to the view"""
            number_of_digits_to_draw = len(str(index.data()))
            arrow_button_width = 25
    
            width = number_of_digits_to_draw * 10 + 2 * arrow_button_width
            height = 45
    
            return QtCore.QSize(width, height)
    

Log in to reply