簡體   English   中英

使用 pyqt5 設計器制作指定表

[英]Making a specified table using pyqt5 designer

我想使用 pyqt 設計器制作如下圖所示的特定表格,但我無法獲得好的結果。 我想在 window 中制作這張表,並包含相同的元素和相同的尺寸。 我嘗試使用 LineEdits 和 Qlabels 使用布局,但我也做不到。 謝謝你。

前提:你的問題沒有顯示出很多研究工作,從所說的很明顯你還是有點缺乏經驗; 這可能會使這個答案變得非常復雜,但那是因為你問的並不簡單。

雖然實現它的要求並非不可能,但這並不容易。
此外,您不能直接在設計器中執行此操作。

主要問題是Qt的項目視圖使用QHeaderView ,它使用一維結構; 添加另一個“維度”層會使事情變得更加困難。

因此,您需要考慮的第一個方面是表格小部件需要為水平 header 設置一個新的自定義 QHeaderView,因此您顯然需要將 QHeaderView 子類化; 但為了使事情正常進行,您還需要繼承 QTableWidget 。

由於 header 的“單維性”(其數據僅使用單個坐標),您需要“展平”結構並創建抽象層才能訪問它。

為了實現這一點,我創建了一個Structure class,其功能允許以某種樹模型的形式訪問它:

class Section(object):
    def __init__(self, label='', children=None, isRoot=False):
        self.label = label
        self._children = []
        if children:
            self._children = []
            for child in children:
                child.parent = self
                self._children.append(child)
        self._isRoot = isRoot
        self.parent = None

    def children(self):
        return self._children

    def isRoot(self):
        return self._isRoot

    def iterate(self):
        # an iterator that cycles through *all* items recursively
        if not self._isRoot:
            yield self
        items = []
        for child in self._children:
            items.extend([i for i in child.iterate()])
        for item in items:
           yield item 

    def sectionForColumn(self, column):
        # get the first (child) item for the given column
        if not self._isRoot:
            return self.root().sectionForColumn(column)
        for child in self.iterate():
            if not child._children:
                if child.column() == column:
                    return child

    def root(self):
        if self._isRoot:
            return self
        return self.parent.root()

    def level(self):
        # while levels should start from -1 (root), we're using levels starting
        # from 0 (which is root); this is done for simplicity and performance
        if self._isRoot:
            return 0
        parent = self.parent
        level = 0
        while parent:
            level += 1
            parent = parent.parent
        return level

    def column(self):
        # root column should be -1; see comment on level()
        if self._isRoot:
            return 0
        parentColIndex = self.parent._children.index(self)
        column = self.parent.column()
        for c in self.parent._children[:parentColIndex]:
            column += c.columnCount()
        return column

    def columnCount(self):
        # return the column (child) count for this section
        if not self._children:
            return 1
        columns = 0
        for child in self._children:
            columns += child.columnCount()
        return columns

    def subLevels(self):
        if not self._children:
            return 0
        levels = 0
        for child in self._children:
            levels = max(levels, child.subLevels())
        return 1 + levels


class Structure(Section):
    # a "root" class created just for commodity
    def __init__(self, label='', children=None):
        super().__init__(label, children, isRoot=True)

使用此 class,您可以創建自己的 header 結構,如下所示:

structure = Structure('Root item', (
    Section('First parent, two sub levels', (
        Section('First child, no children'), 
        Section('Second child, two children', (
            Section('First subchild'), 
            Section('Second subchild')
            )
        )
    )), 
    # column index = 3
    Section('Second parent', (
        Section('First child'), 
        Section('Second child')
        )), 
    # column index = 5
    Section('Third parent, no children'), 
    # ...
))

這里是 QHeaderView 和 QTableWidget 子類,具有最少的可重現代碼:

class AdvancedHeader(QtWidgets.QHeaderView):
    _resizing = False
    _resizeToColumnLock = False

    def __init__(self, view, structure=None):
        super().__init__(QtCore.Qt.Horizontal, view)
        self.structure = structure or Structure()
        self.sectionResized.connect(self.updateSections)
        self.sectionHandleDoubleClicked.connect(self.emitHandleDoubleClicked)

    def setStructure(self, structure):
        if structure == self.structure:
            return
        self.structure = structure
        self.updateGeometries()

    def updateSections(self, index=0):
        # ensure that the parent section is always updated
        if not self.structure.children():
            return
        section = self.structure.sectionForColumn(index)
        while not section.parent.isRoot():
            section = section.parent
        leftColumn = section.column()
        left = self.sectionPosition(leftColumn)
        width = sum(self.sectionSize(leftColumn + c) for c in range(section.columnCount()))
        self.viewport().update(left - self.offset(), 0, width, self.height())

    def sectionRect(self, section):
        if not self.structure.children():
            return
        column = section.column()
        left = 0
        for c in range(column):
            left += self.sectionSize(c)

        bottom = self.height()
        rowHeight = bottom / self.structure.subLevels()
        if section.parent.isRoot():
            top = 0
        else:
            top = (section.level() - 1) * rowHeight

        width = sum(self.sectionSize(column + c) for c in range(section.columnCount()))

        if section.children():
            height = rowHeight
        else:
            root = section.root()
            rowCount = root.subLevels()
            parent = section.parent
            while parent.parent:
                rowCount -= 1
                parent = parent.parent
            height = rowHeight * rowCount
        return QtCore.QRect(left, top, width, height)

    def paintSubSection(self, painter, section, level, rowHeight):
        sectionRect = self.sectionRect(section).adjusted(0, 0, -1, -1)
        painter.drawRect(sectionRect)

        painter.save()
        font = painter.font()
        selection = self.selectionModel()
        column = section.column()
        sectionColumns = set([column + c for c in range(section.columnCount())])
        selectedColumns = set([i.column() for i in selection.selectedColumns()])
        if ((section.children() and selectedColumns & sectionColumns == sectionColumns) or
            (not section.children() and column in selectedColumns)):
                font.setBold(True)
                painter.setFont(font)

        painter.drawText(sectionRect, QtCore.Qt.AlignCenter, section.label)
        painter.restore()

        for child in section.children():
            self.paintSubSection(painter, child, child.level(), rowHeight)

    def sectionHandleAt(self, pos):
        x = pos.x() + self.offset()
        visual = self.visualIndexAt(x)
        if visual < 0:
            return visual

        for section in self.structure.iterate():
            rect = self.sectionRect(section)
            if pos in rect:
                break
        else:
            return -1
        grip = self.style().pixelMetric(QtWidgets.QStyle.PM_HeaderGripMargin, None, self)
        if x < rect.x() + grip:
            return section.column() - 1
        elif x > rect.x() + rect.width() - grip:
            return section.column() + section.columnCount() - 1
        return -1

        logical = self.logicalIndex(visual)
        position = self.sectionViewportPosition(logical)

        atLeft = x < (position + grip)
        atRight = x > (position + self.sectionSize(logical) - grip)
        if self.orientation() == QtCore.Qt.Horizontal and self.isRightToLeft():
            atLeft, atRight = atRight, atLeft

        if atLeft:
            while visual >= 0:
                visual -= 1
                logical = self.logicalIndex(visual)
                if not self.isSectionHidden(logical):
                    break
            else:
                logical = -1
        elif not atRight:
            logical = -1
        return logical

    def emitHandleDoubleClicked(self, index):
        if self._resizeToColumnLock:
            # avoid recursion
            return
        pos = self.viewport().mapFromGlobal(QtGui.QCursor.pos())
        handle = self.sectionHandleAt(pos)
        if handle != index:
            return
        self._resizeToColumnLock = True
        for section in self.structure.iterate():
            if index in range(section.column(), section.column() + section.columnCount()):
                rect = self.sectionRect(section)
                if rect.y() <= pos.y() <= rect.y() + rect.height():
                    sectCol = section.column()
                    for col in range(sectCol, sectCol + section.columnCount()):
                        if col == index:
                            continue
                        self.sectionHandleDoubleClicked.emit(col)
                    break
        self._resizeToColumnLock = False

    # -------- base class reimplementations -------- #

    def sizeHint(self):
        hint = super().sizeHint()
        hint.setHeight(hint.height() * self.structure.subLevels())
        return hint

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        if event.button() != QtCore.Qt.LeftButton:
            return
        handle = self.sectionHandleAt(event.pos())
        if handle >= 0:
            self._resizing = True
        else:
            # if the clicked section has children, select all of its columns
            cols = []
            for section in self.structure.iterate():
                sectionRect = self.sectionRect(section)
                if event.pos() in sectionRect:
                    firstColumn = section.column()
                    columnCount = section.columnCount()
                    for column in range(firstColumn, firstColumn + columnCount):
                        cols.append(column)
                    break
            self.sectionPressed.emit(cols[0])
            for col in cols[1:]:
                self.sectionEntered.emit(col)

    def mouseMoveEvent(self, event):
        super().mouseMoveEvent(event)
        handle = self.sectionHandleAt(event.pos())
        if not event.buttons():
            if handle < 0:
                self.unsetCursor()
        elif handle < 0 and not self._resizing:
            # update sections when click/dragging (required if highlight is enabled)
            pos = event.pos()
            pos.setX(pos.x() + self.offset())
            for section in self.structure.iterate():
                if pos in self.sectionRect(section):
                    self.updateSections(section.column())
                    break
            # unset the cursor, in case it was set for a section handle
            self.unsetCursor()

    def mouseReleaseEvent(self, event):
        self._resizing = False
        super().mouseReleaseEvent(event)

    def paintEvent(self, event):
        qp = QtGui.QPainter(self.viewport())
        qp.setRenderHints(qp.Antialiasing)
        qp.translate(.5, .5)
        height = self.height()
        rowHeight = height / self.structure.subLevels()
        qp.translate(-self.horizontalOffset(), 0)
        column = 0
        for parent in self.structure.children():
            self.paintSubSection(qp, parent, 0, rowHeight)
            column += 1


class CustomHeaderTableWidget(QtWidgets.QTableWidget):
    structure = None
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        customHeader = AdvancedHeader(self)
        self.setHorizontalHeader(customHeader)
        customHeader.setSectionsClickable(True)
        customHeader.setHighlightSections(True)

        self.cornerHeader = QtWidgets.QLabel(self)
        self.cornerHeader.setAlignment(QtCore.Qt.AlignCenter)
        self.cornerHeader.setStyleSheet('border: 1px solid black;')
        self.cornerHeader.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
        self.verticalHeader().setMinimumWidth(
            self.cornerHeader.minimumSizeHint().width() + self.fontMetrics().width(' '))
        self._cornerButton = self.findChild(QtWidgets.QAbstractButton)

        self.setStructure(kwargs.get('structure') or Section('ROOT', isRoot=True))

        self.selectionModel().selectionChanged.connect(self.selectionModelSelChanged)

    def setStructure(self, structure):
        if structure == self.structure:
            return
        self.structure = structure
        if not structure:
            super().setColumnCount(0)
            self.cornerHeader.setText('')
        else:
            super().setColumnCount(structure.columnCount())
            self.cornerHeader.setText(structure.label)
        self.horizontalHeader().setStructure(structure)
        self.updateGeometries()

    def selectionModelSelChanged(self):
        # update the corner widget
        selected = len(self.selectionModel().selectedIndexes())
        count = self.model().rowCount() * self.model().columnCount()
        font = self.cornerHeader.font()
        font.setBold(selected == count)
        self.cornerHeader.setFont(font)

    def updateGeometries(self):
        super().updateGeometries()
        vHeader = self.verticalHeader()
        if not vHeader.isVisible():
            return
        style = self.verticalHeader().style()
        opt = QtWidgets.QStyleOptionHeader()
        opt.initFrom(vHeader)
        margin = style.pixelMetric(style.PM_HeaderMargin, opt, vHeader)
        width = self.cornerHeader.minimumSizeHint().width() + margin * 2
        
        vHeader.setMinimumWidth(width)
        self.cornerHeader.setGeometry(self._cornerButton.geometry())

    def setColumnCount(self, count):
        # ignore column count, as we're using setStructure() instead
        pass


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)

    structure = Structure('UNITE', (
        Section('Hrs de marche', (
            Section('Expl'), 
            Section('Indi', (
                Section('Prev'), 
                Section('Accid')
            ))
        )), 
        Section('Dem', (
            Section('TST'), 
            Section('Epl')
        )), 
        Section('Decle'), 
        Section('a'), 
        Section('Consom'), 
        Section('Huile'), 
    ))

    tableWidget = CustomHeaderTableWidget()
    tableWidget.setStructure(structure)

    tableWidget.setRowCount(2)
    tableWidget.setVerticalHeaderLabels(
        ['Row {}'.format(r + 1) for r in range(tableWidget.rowCount())])

    tableWidget.show()
    sys.exit(app.exec())

一些考慮,因為上面的例子並不完美:

  • 部分不可移動(如果您嘗試設置setSectionsMovable並嘗試拖動部分,它可能會在某些時候崩潰);
  • 雖然我試圖避免調整“父”部分的大小(未顯示調整大小 cursor),但仍然可以從父矩形調整子部分的大小;
  • 改變 model 的水平結構可能會產生意想不到的結果(我只實現了基本操作);
  • Structure是一個標准的 python object子類,它與 QTableWidget 完全斷開;
  • 考慮到上述情況,使用諸如 Horizo horizontalHeaderItemsetHorizontalHeaderItemsetHorizontalHeaderLabels類的函數可能無法按預期工作;

現在,如何在設計器中使用它? 您需要使用提升的小部件
添加 QTableWidget,右鍵單擊它並 select Promote to... ,確保在“Base class name”組合中選擇“QTableWidget”,在“Promoted ZA2F2ED4F8EBC2CBB4DZ name”字段中輸入“CustomHeaderTableWidget”,然后在“Promoted ZA2F2ED4F8EBC2CBB4DZ name”字段中輸入“CustomHeaderTableWidget”和DC40AB文件名包含“頭文件”字段中的子類(請注意,它被視為 python 模塊名稱,因此它必須沒有.py文件擴展名); 點擊“添加”,點擊“推廣”並保存。 考慮到,從那里開始,您仍然必須提供自定義Structure ,並且如果您在 Designer 中添加了任何行和列,它必須反映結構列數。


最后,由於這件事很有趣,我可能會在將來重新討論它並最終更新代碼。

同時,我強烈建議您仔細研究代碼,探索 QHeaderView 的所有重新實現(請參閱下面的base class reimplementations評論)以及原始方法實際上做了什么,請閱讀QHeaderView文檔。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM