如何为QTableWidget设置选择滑块?

问题描述 投票:0回答:1

我正在寻找合并离散滑块和QTableWidget的解决方案(请参见所附的屏幕截图)。滑块用作选择指针(而不是默认选择荧光笔)。如何使用Qt(PyQt)实施?

Table slider

python pyqt qtablewidget
1个回答
0
投票

小前提。从技术上讲,根据StackOverflow标准,您的问题不是一个很好的问题。我将在此答案的结尾进行解释。

获得您想要的东西并不容易,最重要的是,因为并不是为此目的而创建滑块(并且有很多UX原因您不应该这样做,请转到User Experience询问它们)。

诀窍是创建一个将表小部件作为父级的QSlider。与父级一起创建窗口小部件可确保子窗口小部件始终被封闭在父级边界之内(仅对于QMainWindow和QDialog后代而言为假),只要该窗口小部件未添加到父级布局中即可。这使您可以自由设置其几何形状(位置和大小)。

在下面的示例中,我添加了一个内部QSlider,但是有关此小部件的主要问题是以使其值位置与表内容对齐的方式对其进行对齐。

class GhostHeader(QtWidgets.QHeaderView):
    '''
    A "fake" vertical header that does not paint its sections
    '''
    def __init__(self, parent):
        super().__init__(QtCore.Qt.Vertical, parent)
        self.setSectionResizeMode(self.Fixed)

    def paintEvent(self, event):
        pass


class SliderTable(QtWidgets.QTableWidget):
    def __init__(self, rows=0, columns=0, parent=None):
        super().__init__(rows, columns, parent)
        self.horizontalHeader().setStretchLastSection(True)
        self.setHorizontalHeaderLabels(['Item table'])
        self.setVerticalHeader(GhostHeader(self))

        # create a slider that is a child of the table; there is no layout, but
        # setting the table as its parent will cause it to be shown "within" it.
        self.slider = QtWidgets.QSlider(QtCore.Qt.Vertical, self)
        # by default, a slider has its maximum on the top, let's invert this
        self.slider.setInvertedAppearance(True)
        self.slider.setInvertedControls(True)
        # show tick marks at each slider value, on both sides
        self.slider.setTickInterval(1)
        self.slider.setTickPosition(self.slider.TicksBothSides)
        self.slider.setRange(0, max(0, self.rowCount() - 1))
        # not necessary, but useful for wheel and click interaction
        self.slider.setPageStep(1)
        # disable focus on the slider
        self.slider.setFocusPolicy(QtCore.Qt.NoFocus)

        self.slider.valueChanged.connect(self.selectRowFromSlider)
        self.slider.valueChanged.connect(self.updateSlider)
        self.verticalScrollBar().valueChanged.connect(self.updateSlider)

        self.model().rowsInserted.connect(self.modelChanged)
        self.model().rowsRemoved.connect(self.modelChanged)

    def selectRowFromSlider(self, row):
        if self.currentIndex().isValid():
            column = self.currentIndex().column()
        else:
            column = 0
        self.setCurrentIndex(self.model().index(row, column))

    def modelChanged(self):
        self.slider.setMaximum(max(0, self.rowCount() - 1))
        self.updateSlider()

    def updateSlider(self):
        slider = self.slider
        option = QtWidgets.QStyleOptionSlider()
        slider.initStyleOption(option)
        style = slider.style()

        # get the available extent of the slider
        available = style.pixelMetric(style.PM_SliderSpaceAvailable, option, slider)
        # compute the space between the top of the slider and the position of
        # the minimum value (0)
        deltaTop = (slider.height() - available) // 2
        # do the same for the maximum
        deltaBottom = slider.height() - available - deltaTop

        # the vertical center of the first item
        top = self.visualRect(self.model().index(0, 0)).center().y()
        # the vertical center of the last
        bottom = self.visualRect(self.model().index(self.model().rowCount() - 1, 0)).y()

        # get the slider width and adjust the size of the "ghost" vertical header
        width = self.slider.sizeHint().width()
        left = self.frameWidth() + 1
        self.verticalHeader().setFixedWidth(width // 2 + left)

        viewGeo = self.viewport().geometry()
        headerHeight = viewGeo.top()
        # create the rectangle for the slider geometry
        rect = QtCore.QRect(0, headerHeight + top, width, headerHeight + bottom - top // 2)
        # adjust to the values computed above
        rect.adjust(0, -deltaTop + 1, 0, -deltaBottom)
        # translate it so that its center will be between the vertical header and
        # the table contents
        rect.translate(left, 0)
        self.slider.setGeometry(rect)

        # set the mask, in case the item view is scrolled, so that the top of the
        # slider won't be shown in the horizontal header
        visible = self.rect().adjusted(0, viewGeo.top(), 0, 0)
        mask = QtGui.QPainterPath()
        topLeft = slider.mapFromParent(visible.topLeft())
        bottomRight = slider.mapFromParent(visible.bottomRight() + QtCore.QPoint(1, 1))
        mask.addRect(QtCore.QRectF(topLeft, bottomRight))
        self.slider.setMask(QtGui.QRegion(mask.toFillPolygon(QtGui.QTransform()).toPolygon()))

    def currentChanged(self, current, previous):
        super().currentChanged(current, previous)
        if current.isValid():
            self.slider.setValue(current.row())

    def resizeEvent(self, event):
        # whenever the table is resized (even when first shown) call the base
        # implementation (which is required for correct drawing of items and
        # selections), then update the slider
        super().resizeEvent(event)
        self.updateSlider()


class Test(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QVBoxLayout(self)
        self.table = SliderTable()
        self.table.setRowCount(4)
        self.table.setColumnCount(1)
        self.table.setHorizontalHeaderLabels(['Item table'])
        layout.addWidget(self.table)
        for row in range(self.table.rowCount()):
            item = QtWidgets.QTableWidgetItem('item {}'.format(row + 1))
            item.setTextAlignment(QtCore.Qt.AlignCenter)
            self.table.setItem(row, 0, item)

为什么这个问题<不好?嗯,这很危险地接近“我不知道该怎么做,你能帮我吗?”限制。您应该提供任何minimal, reproducible example(不管它是否无效,您都应该进行一些研究并展示您的努力),而且即使在评论中进行了一些澄清之后,问题还是有点含糊不清部分。长话短说:如果太难了,并且您无法使其正常工作,那么您可能仍需要学习和锻炼才能达到目的。要耐心,研究文档:幸运的是,Qt文档通常写得很好,所以只是时间问题。

© www.soinside.com 2019 - 2024. All rights reserved.