使用pyqt5设计器制作指定的表 [英] Making a specified table using pyqt5 designer

查看:155
本文介绍了使用pyqt5设计器制作指定的表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用pyqt设计器制作一个特定的表格,如下图所示,我无法取得很好的结果.我想在一个窗口中创建此表,并包含相同的元素和相同的尺寸.我试图通过LineEdits和Qlabels使用布局,但我也做不到.谢谢.

I want to make a specific table like shown in the picture below using pyqt designer and I coulnd't make a good result. I want to make this table in a window and contains the same elements and same dimensions. I tried to use layouts using LineEdits and Qlabels but I couldnt make it too . Thank you.

推荐答案

前提:您的问题并未显示出大量的研究成果,从上面所说的很明显,您仍然没有经验.这可能会使答案非常复杂,但这是因为您要求的不是 简单.

Premise: your question didn't show lots of research efforts, and from what was said it's quite clear that you're still a bit inexperienced; this will probably make this answer very complicated, but that's because what you asked is not simple.

实现要求的目标并非不可能,但并非易事.
另外,您不能直接在设计器中执行此操作.

While achieving what it is asked is not impossible, it is not easy.
Also, you cannot do it directly in designer.

主要问题是Qt的项目视图使用QHeaderView,后者使用一维结构;添加另一个维度"层使事情变得更加困难.

The main problem is that Qt's item views use QHeaderView, which uses a monodimensional structure; adding another "dimension" layer makes things much more difficult.

因此,您需要考虑的第一个方面是表窗口小部件需要为水平标题设置新的自定义QHeaderView,因此您显然需要将QHeaderView子类化.但是为了使事情正常进行,您还需要将QTableWidget子类化.

So, the first aspect you need to consider is that the table widget needs to have a new, custom QHeaderView set for the horizontal header, so you'll obviously need to subclass QHeaderView; but in order to make things work you'll also need to subclass QTableWidget too.

由于一维性",在标头(仅使用其数据的单个坐标)的标头"中,您需要平整"标头.结构并创建一个抽象层以便对其进行访问.

Due to the "monodimensionality" of the header (which only uses a single coordinate for its data), you need to "flatten" the structure and create an abstraction layer in order to access it.

为了实现这一点,我创建了一个 Structure 类,该类具有允许以某种树模型访问它的函数:

In order to achieve that, I created a Structure class, with functions that allow access to it as some sort of tree-model:

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)

使用此类,您可以创建自己的标题结构,如下所示:

With this class, you can create your own header structure like this:

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子类,它们具有最少的可复制代码:

And here are the QHeaderView and QTableWidget subclasses, with a minimal reproducible code:

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())

一些注意事项,因为上面的示例不是是完美的:

Some considerations, since the above example is not perfect:

  • 节是不可移动的(如果您尝试设置 setSectionsMovable 并尝试拖动节,则可能会在某个时刻崩溃);
  • 在我尝试避免调整父"的大小时,部分(未显示调整大小的光标),仍然可以从父矩形调整子部分的大小;
  • 改变模型的水平结构可能会产生意想不到的结果(我只实现了基本操作);
  • Structure 是一个标准的python object 子类,它与QTableWidget完全无关;
  • 考虑到以上情况,使用 horizo​​ntalHeaderItem setHorizo​​ntalHeaderItem setHorizo​​ntalHeaderLabels 之类的功能可能无法按预期工作;
  • sections are not movable (if you try to set setSectionsMovable and try to drag a section, it would probably crash at some point);
  • while I tried to avoid resizing of a "parent" section (the resize cursor is not shown), it is still possible to resize a child section from the parent rectangle;
  • changing the horizontal structure of the model might give unexpected results (I only implemented basic operations);
  • Structure is a standard python object subclass, and it's completely unlinked from the QTableWidget;
  • considering the above, using functions like horizontalHeaderItem, setHorizontalHeaderItem or setHorizontalHeaderLabels might not work as expected;

现在,如何在设计器中使用它?您需要使用 升级的小部件 .
添加QTableWidget,右键单击它并选择 Promote to ... ,确保"QTableWidget"在基本类别名称"中选择否".组合,键入"CustomHeaderTableWidget"在晋升班级名称"中字段,然后是包含"Header file"中的子类的文件名.字段(请注意,它被视为python模块名称,因此必须不带有 .py 文件扩展名);点击添加",点击升级"并保存.考虑到从那里开始,您仍然必须提供自定义的 Structure ,并且如果在Designer中添加了任何行和列,则它必须反映结构的列数.

Now, how to use it in designer? You need to use a promoted widget.
Add the QTableWidget, right click on it and select Promote to..., ensure that "QTableWidget" is selected in the "Base class name" combo, type "CustomHeaderTableWidget" in the "Promoted class name" field and then the file name that contains the subclass in the "Header file" field (note that it's treated like a python module name, so it has to be without the .py file extension); click "Add", click "Promote" and save it. Consider that, from there, you must still provide the custom Structure, and if you added any row and column in Designer it must reflect the structure column count.

最后,由于此事很有趣,我可能会在将来返回并最终更新代码.

Finally, since the matter is interesting, I might return on it in the future and update the code, eventually.

同时,我强烈建议您仔细研究代码,探索QHeaderView的所有重新实现(请参见基类重新实现注释的内容),以及原始方法通过阅读 QHeaderView 文档.

In the meantime, I strongly suggest you to carefully study the code, explore all the reimplementations of QHeaderView (see what's below base class reimplementations comment) and what the original methods actually do by reading the QHeaderView documentation.

这篇关于使用pyqt5设计器制作指定的表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆