PyQt6 auto-resize QTextEdit to fit content 问题
PyQt6 auto-resize QTextEdit to fit content problem
我在 QScrollArea 的 QVBoxLayout 中有一堆 QTextEdit。
文本通常会变得很长,水平 space 受设计限制,QTextEdit 自动将文本换行成多行,这很好。
我想自动调整 QTextEdit 的大小以适合换行的文本,文本本身总是在一行中,而换行的文本可以有多行,我希望 QTextEdits 适合换行的高度.
经过几个小时 Google 的搜索,我找到了一个解决方案,但它没有按预期工作,有时底部会多出一行,我会 post下面的示例代码:
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
font = QFont('Noto Serif', 9)
class Editor(QTextEdit):
doubleClicked = pyqtSignal(QTextEdit)
def __init__(self):
super().__init__()
self.setReadOnly(True)
self.setFont(font)
self.textChanged.connect(self.autoResize)
self.margins = self.contentsMargins()
self.setAttribute(Qt.WidgetAttribute.WA_DontShowOnScreen)
@pyqtSlot(QMouseEvent)
def mouseDoubleClickEvent(self, e: QMouseEvent) -> None:
self.doubleClicked.emit(self)
def autoResize(self):
self.show()
height = int(self.document().size().height() + self.margins.top() + self.margins.bottom())
self.setFixedHeight(height)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.resize(405, 720)
frame = self.frameGeometry()
center = self.screen().availableGeometry().center()
frame.moveCenter(center)
self.move(frame.topLeft())
self.centralwidget = QWidget(self)
self.vbox = QVBoxLayout(self.centralwidget)
self.scrollArea = QScrollArea(self.centralwidget)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.verticalLayout = QVBoxLayout(self.scrollAreaWidgetContents)
self.verticalLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.scrollArea.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
self.vbox.addWidget(self.scrollArea)
self.setCentralWidget(self.centralwidget)
def addItems():
items = [
"L'Estro Armonico No. 9 in D Major\u2014III. Allegro",
"L'Estro Armonico No. 6 in A Minor\u2014III. Presto",
"L'Estro Armonico No. 6 in A Minor\u2014I. Allegro",
"L'Estro Armonico No. 1 in D major\u2014I. Allegro",
"12 Concertos Op.3 \u2014 L'estro Armonico \u2014 Concerto No. 6 In A Minor For Solo Violin RV 356\u2014Presto",
"Ultimate Mozart\u2014 The Essential Masterpieces",
"Serenade in G K.525 Eine kleine Nachtmusik\u20141. Allegro",
"Vivaldi\u2014 L'estro Armonico",
"Academy of St. Martin in the Fields",
"Are You With Me \u2014 Reality"
]
for i in items:
textbox = Editor()
textbox.setText(i)
window.verticalLayout.addWidget(textbox)
app = QApplication([])
window = Window()
window.show()
addItems()
app.exec()
请注意,您需要 Noto Serif 才能正确 运行(当然您可以替换它),当然您还需要 PyQt6。
示例中,前七个文本框底部都有一个额外的空行,后三个没有。
是什么导致了额外的线以及如何删除它?
更新:
我必须设置Qt.WidgetAttribute.WA_DontShowOnScreen,因为如果我不设置它,调用QTextEdit 的.show() 将导致QTextEdit 出现在屏幕中间并迅速消失,这很烦人。
我不得不调用.show() 因为调用document().size() 没有show(),值将全部为0,我不知道为什么会这样。
问题出在您尝试过早设置高度。事实上,如果您在 show()
之后添加一个 print(self.show())
,您会看到所有内容都将显示默认大小(可能是 256x192)。
这取决于两个方面:
- 当一个小部件第一次显示时,它还没有完全“映射”在OS window 管理中,因此它会根据许多方面使用默认大小;
- 您在将文本添加到布局之前设置文本,因此 QTextEdit 将不知道父级所需的大小;
然后又出现了一个问题:如果window调整了大小,内容将不会适应,直到文本被改变。
为了根据内容正确设置垂直高度,您应该设置文档的 textWidth
,并在每次调整小部件大小时调用 autoResize
。
class Editor(QTextEdit):
def __init__(self):
super().__init__()
self.setReadOnly(True)
self.setFont(font)
self.textChanged.connect(self.autoResize)
def autoResize(self):
self.document().setTextWidth(self.viewport().width())
margins = self.contentsMargins()
height = int(self.document().size().height() + margins.top() + margins.bottom())
self.setFixedHeight(height)
def resizeEvent(self, event):
self.autoResize()
注意:
- 边距应该动态访问,而不是存储在
__init__
;
mouseDoubleClickEvent
是一个在鼠标事件上调用的函数,它不是(也不应该)是一个槽,所以使用 pyqtSlot
装饰器是没有意义的;
- 虽然对于“主”布局(如滚动区域内容的布局)在概念上没有问题,但设置布局的对齐方式不会设置其项目的对齐方式,而只会设置布局的对齐方式;虽然结果 经常 相同,但在实践中却大不相同(因此结果并不 总是 相同,尤其是如果将更多布局添加到相同的父布局);
- 双击文本字段非常常用于高级 selection(通常,select 光标下的单词),并选择阻止此类操作(从而更改已知 UI 惯例) 应该仔细考虑;
我在 QScrollArea 的 QVBoxLayout 中有一堆 QTextEdit。
文本通常会变得很长,水平 space 受设计限制,QTextEdit 自动将文本换行成多行,这很好。
我想自动调整 QTextEdit 的大小以适合换行的文本,文本本身总是在一行中,而换行的文本可以有多行,我希望 QTextEdits 适合换行的高度.
经过几个小时 Google 的搜索,我找到了一个解决方案,但它没有按预期工作,有时底部会多出一行,我会 post下面的示例代码:
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
font = QFont('Noto Serif', 9)
class Editor(QTextEdit):
doubleClicked = pyqtSignal(QTextEdit)
def __init__(self):
super().__init__()
self.setReadOnly(True)
self.setFont(font)
self.textChanged.connect(self.autoResize)
self.margins = self.contentsMargins()
self.setAttribute(Qt.WidgetAttribute.WA_DontShowOnScreen)
@pyqtSlot(QMouseEvent)
def mouseDoubleClickEvent(self, e: QMouseEvent) -> None:
self.doubleClicked.emit(self)
def autoResize(self):
self.show()
height = int(self.document().size().height() + self.margins.top() + self.margins.bottom())
self.setFixedHeight(height)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.resize(405, 720)
frame = self.frameGeometry()
center = self.screen().availableGeometry().center()
frame.moveCenter(center)
self.move(frame.topLeft())
self.centralwidget = QWidget(self)
self.vbox = QVBoxLayout(self.centralwidget)
self.scrollArea = QScrollArea(self.centralwidget)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.verticalLayout = QVBoxLayout(self.scrollAreaWidgetContents)
self.verticalLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.scrollArea.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
self.vbox.addWidget(self.scrollArea)
self.setCentralWidget(self.centralwidget)
def addItems():
items = [
"L'Estro Armonico No. 9 in D Major\u2014III. Allegro",
"L'Estro Armonico No. 6 in A Minor\u2014III. Presto",
"L'Estro Armonico No. 6 in A Minor\u2014I. Allegro",
"L'Estro Armonico No. 1 in D major\u2014I. Allegro",
"12 Concertos Op.3 \u2014 L'estro Armonico \u2014 Concerto No. 6 In A Minor For Solo Violin RV 356\u2014Presto",
"Ultimate Mozart\u2014 The Essential Masterpieces",
"Serenade in G K.525 Eine kleine Nachtmusik\u20141. Allegro",
"Vivaldi\u2014 L'estro Armonico",
"Academy of St. Martin in the Fields",
"Are You With Me \u2014 Reality"
]
for i in items:
textbox = Editor()
textbox.setText(i)
window.verticalLayout.addWidget(textbox)
app = QApplication([])
window = Window()
window.show()
addItems()
app.exec()
请注意,您需要 Noto Serif 才能正确 运行(当然您可以替换它),当然您还需要 PyQt6。
示例中,前七个文本框底部都有一个额外的空行,后三个没有。
是什么导致了额外的线以及如何删除它?
更新:
我必须设置Qt.WidgetAttribute.WA_DontShowOnScreen,因为如果我不设置它,调用QTextEdit 的.show() 将导致QTextEdit 出现在屏幕中间并迅速消失,这很烦人。
我不得不调用.show() 因为调用document().size() 没有show(),值将全部为0,我不知道为什么会这样。
问题出在您尝试过早设置高度。事实上,如果您在 show()
之后添加一个 print(self.show())
,您会看到所有内容都将显示默认大小(可能是 256x192)。
这取决于两个方面:
- 当一个小部件第一次显示时,它还没有完全“映射”在OS window 管理中,因此它会根据许多方面使用默认大小;
- 您在将文本添加到布局之前设置文本,因此 QTextEdit 将不知道父级所需的大小;
然后又出现了一个问题:如果window调整了大小,内容将不会适应,直到文本被改变。
为了根据内容正确设置垂直高度,您应该设置文档的 textWidth
,并在每次调整小部件大小时调用 autoResize
。
class Editor(QTextEdit):
def __init__(self):
super().__init__()
self.setReadOnly(True)
self.setFont(font)
self.textChanged.connect(self.autoResize)
def autoResize(self):
self.document().setTextWidth(self.viewport().width())
margins = self.contentsMargins()
height = int(self.document().size().height() + margins.top() + margins.bottom())
self.setFixedHeight(height)
def resizeEvent(self, event):
self.autoResize()
注意:
- 边距应该动态访问,而不是存储在
__init__
; mouseDoubleClickEvent
是一个在鼠标事件上调用的函数,它不是(也不应该)是一个槽,所以使用pyqtSlot
装饰器是没有意义的;- 虽然对于“主”布局(如滚动区域内容的布局)在概念上没有问题,但设置布局的对齐方式不会设置其项目的对齐方式,而只会设置布局的对齐方式;虽然结果 经常 相同,但在实践中却大不相同(因此结果并不 总是 相同,尤其是如果将更多布局添加到相同的父布局);
- 双击文本字段非常常用于高级 selection(通常,select 光标下的单词),并选择阻止此类操作(从而更改已知 UI 惯例) 应该仔细考虑;