作为我维护的 Qt 应用程序之一的一部分,在最近更新代码后开始出现奇怪的布局问题。
我们的对话框有多个小部件,包括一些文本和滚动区域,一旦我们将其中一个 QLabel 设置为 wordWrap,就会完全忽略主小部件的大小提示。
我成功地用很少的代码重现了 2 个案例:
案例1:QLabel + AddStretch
import sys
from PySide2 import QtCore, QtWidgets # Same results with PySide6
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent=parent)
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel('This is a fairly long sentence, although not that long')
self.label.setWordWrap(True) # Comment out for expected results
layout.addWidget(self.label)
layout.addStretch()
self.setLayout(layout)
def sizeHint(self):
return QtCore.QSize(500, 500)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QDialog()
layout = QtWidgets.QVBoxLayout()
window.setLayout(layout)
notes_panel = MyWidget()
layout.addWidget(notes_panel)
window.show()
sys.exit(app.exec_())
预期结果: 500x500,根据尺寸提示
设置 wordWrap 时的结果:
从我所做的调试来看,我的布局现在有“hasHeightForWidth”,这导致它运行相当于
heightForWidth(sizeHint.width())
,并且显示错误。
案例2:标签和QScrollArea
更接近我的实际应用程序,我们有一个 QScrollArea。我们通常使用一个 ScrollArea 帮助器,因为它通常可以给我们带来更好的结果,我将其包含在这里。
class MyWidget2(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget2, self).__init__(parent=parent)
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel('This is a fairly long sentence, although not that long')
self.label.setWordWrap(True)
layout.addWidget(self.label)
# scroll area
scroll = ScrollArea()
layout.addWidget(scroll)
for i in range(75):
scroll.addWidget(QtWidgets.QCheckBox(str(i)))
self.setLayout(layout)
def sizeHint(self):
return QtCore.QSize(500, 500)
class ScrollArea(QtWidgets.QScrollArea):
""" Convenience class for setting up Scroll Areas"""
def __init__(self, direction=QtCore.Qt.Vertical, parent=None):
"""
Parameters
----------
direction: QtCore.Qt.Vertical or QtCore.Qt.Horizontal, optional
parent: QtWidgets.QWidget, optional
"""
super(ScrollArea, self).__init__(parent=parent)
layout_class = QtWidgets.QVBoxLayout if direction == QtCore.Qt.Vertical else QtWidgets.QHBoxLayout
# Create components
widget = QtWidgets.QWidget()
widget_layout = layout_class()
self.contents_layout = layout_class()
# Assign components
widget_layout.addLayout(self.contents_layout)
widget_layout.addStretch()
widget.setLayout(widget_layout)
self.setWidget(widget)
self.setWidgetResizable(True)
def addWidget(self, widget):
""" Add a widget to the scroll area """
self.contents_layout.addWidget(widget)
def addWidgets(self, widgets):
""" Add multiple widgets to the scroll area"""
for widget in widgets:
self.addWidget(widget)
def sizeHint(self):
""" Overriden sizeHint to return the hint based on content, plus some margins to accommodate scroll bars. """
return self.contents_layout.sizeHint() + QtCore.QSize(30, 20)
预计:
setWordWrap(True)时的结果:
这特别糟糕,因为我无法将此对话框调整得更小,它的行为就好像 ScrollArea 的 sizeHint 是最小尺寸一样。这就是我们的应用程序中发生的情况。
注意: 它在 PySide6 中表现更好,它的表现不像是最小大小,但初始大小仍然显示大于所需的 sizeHint。
我已经找到了几种方法来绕过滚动区域的 sizeHint 作为最小尺寸,但我还没有找到任何方法来同时拥有自动换行标签并尊重我的小部件的 sizeHint。
我知道文档提到 wordWrapped 标签可能会导致问题 https://doc.qt.io/qt-6/layout.html#layout-issues 并且我发现了其他具有相关主题的堆栈溢出帖子,但它们要么没有答案,要么答案无助于找到解决方法。
启用自动换行时,qlabel 有错误的 sizeHint()
我的问题是:
感谢@musicamante 的评论,我找到了一个有效的解决方案。
看来我重新实现的
sizeHint
被忽略了,因为小部件具有宽度的高度,因此重新实现hasSizeForWidth()
也可以防止父布局重新计算高度并正确使用我的尺寸提示:
class MyWidget2(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget2, self).__init__(parent=parent)
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel('This is a fairly long sentence, although not that long')
self.label.setWordWrap(True)
layout.addWidget(self.label)
# scroll area
scroll = ScrollArea()
layout.addWidget(scroll)
for i in range(75):
scroll.addWidget(QtWidgets.QCheckBox(str(i)))
self.setLayout(layout)
def sizeHint(self):
return QtCore.QSize(500, 500)
# NEW METHOD
def hasHeightForWidth(self):
return False