[英]PyQt5: using loadUi with promoted widgets from Qt Designer
[英]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
並嘗試拖動部分,它可能會在某些時候崩潰);Structure
是一個標准的 python object
子類,它與 QTableWidget 完全斷開;horizontalHeaderItem
、 setHorizontalHeaderItem
或setHorizontalHeaderLabels
類的函數可能無法按預期工作; 現在,如何在設計器中使用它? 您需要使用提升的小部件。
添加 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.