我正在尝试在 PyQt5 中创建一个自定义
QTextEdit
小部件,该小部件会根据其内容自动扩展和收缩。当用户输入新行时,小部件的高度应增加;当行被删除时,小部件的高度应缩小。我有一些代码,但是当调整 TextEdit 大小时,窗口大小保持不变,因此 TextEdit 上方的小部件(应该具有动态大小)的大小会增加。我的MRE如下:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class App(QWidget):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.init_ui()
def init_ui(self):
### ChatWidget ###
chat_widget = QWidget(self)
chat_widget.setObjectName("chatbox")
chat_widget.setStyleSheet("#chatbox {background-color: black; border: 0;}")
chat_widget.scroll_area = QScrollArea(self)
chat_widget.scroll_area.setWidgetResizable(True)
chat_widget.scroll_area.setWidget(chat_widget)
chat_widget.scroll_area.setStyleSheet("""
QScrollArea {
}
QScrollBar:vertical {
background-color: black;
color: white;
}
""")
chat_widget.setMinimumHeight(449)
chat_widget.setMinimumWidth(300)
chat_widget.scroll_area.setMinimumHeight(453)
chat_widget.scroll_area.setMinimumWidth(333)
self.layout.addWidget(chat_widget.scroll_area)
###
### CustomTextEdit ###
self.text_edit = CustomTextEdit(5, self)
self.layout.addWidget(self.text_edit)
self.text_edit.setStyleSheet("""
font-size: 12pt;
""")
QTimer().singleShot(5, self.text_edit.update_height)
###
class CustomTextEdit(QTextEdit):
def __init__(self, max_lines=5, parent=None):
super().__init__(parent)
self.max_lines = max_lines
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
self.document().contentsChanged.connect(self.update_height)
self.setFixedHeight(self.fontMetrics().height())
def update_height(self):
document_height = self.document().size().height()
line_height = self.fontMetrics().lineSpacing()
text_layout = self.document().documentLayout()
block = self.document().begin()
num_lines = 0
while block.isValid():
layout = block.layout()
num_lines += layout.lineCount()
block = block.next()
desired_height = document_height + self.contentsMargins().top() + self.contentsMargins().bottom()
if num_lines > self.max_lines:
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
else:
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setFixedHeight(int(desired_height))
def scroll_to_bottom(self):
scrollbar = self.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
if __name__ == '__main__':
app = QApplication(sys.argv)
app_widget = App()
app_widget.show()
sys.exit(app.exec_())
如果有人能提供帮助,我将不胜感激,谢谢!
尝试/目标/期望:我尝试使 TextEdit 随着行数的增加而调整大小。 问题:其他小部件正在调整大小/窗口未调整大小。
当子窗口小部件需要比以前更多的空间时,如果其他窗口小部件没有留下可用空间,通常会导致父窗口部件相应地增加其大小,最终上升到顶层窗口。这是为了确保所有小部件始终可见且可用。
当更改反而降低了尺寸要求时,这种情况不会发生,因为假设可以更好地利用空间,而不是试图任意减小总体尺寸。通常不会这样做,因为内部小部件和容器无法正确计算大小的减小,因为它们不知道导致减小的原因,它们只知道发生了调整大小:这可能是由用户调整大小引起的窗口,或通过另一个小部件更改其大小限制。
为了尝试将大小“重置”为最小值,我们最终可以尝试获得窗口的最小首选大小要求(
sizeHint()
),但只有在之后更改已被正确处理布局管理器(这就是调用 activate()
的原因)。
def update_height(self):
...
self.window().layout.activate()
self.window().resize(
self.window().width(),
self.window().sizeHint().height()
)
但请注意,通常不鼓励这种大小更改。虽然尺寸的增加可能是合理的(UI must 能够显示其内容),但这两项更改实际上都应该考虑当前尺寸,以防用户手动调整窗口大小。
即使在原始代码中,执行所有这些操作的缺点也清晰可见:尝试使窗口比启动时稍高(几个像素),然后添加足够的文本以显示多行;窗户只会稍微增加其高度。现在,如果清除文本,大小将不会恢复到以前的大小。
如果您对新添加的内容执行相同的操作,则无论用户最终如何尝试调整窗口大小,窗口都将始终恢复到其最小大小。
这是仅在必要时且在特定条件下才应更改窗口大小的众多原因之一(内容中没有完全动态的更改)。虽然从技术上来说,以正确的方式实现你想要的并不是不可能的,但它总是需要遵循非常微妙的程序(检查父和顶级窗口、大小限制、同级小部件等),这可能很难实现。此外,它通常被认为对用户来说很烦人,尤其是对于输入字段,并且实际上从用户体验的角度来看它没有提供任何真正的好处。 更合适的方法应该正确使用大小提示,特别是
和
sizeHint()
,它们分别返回小部件的最小可能大小和首选大小:子小部件最终应提供 preferred大小如果其他小部件需要更多空间,最终可能会缩小。 窗口不应该因用户输入而调整大小,除非为了正确显示其所有内容是绝对必要的。
在您的情况下,这应该作为 QScrollArea 子类实现:
class ScrollArea(QScrollArea):
def minimumSizeHint(self):
return QSize(300, 350)
def sizeHint(self):
return QSize(300, 450)
class App(QWidget):
...
def init_ui(self):
...
chat_widget.scroll_area = ScrollArea(self)
self.setMinimumHeight(self.sizeHint().height())
上面将给出与您类似的初始结果,其好处是,如果输入区域需要更多空间,“消息区域”最终会缩小,然后如果需要较少的垂直空间,所有内容都会相应恢复。进一步说明:
如果您使用
setFixedHeight()
chat_widget
scroll_area
作为其成员在概念上是错误的,因为它是相反的;
layout()
不可调用(在第一个示例中,
self.window().layout()
会更合适,它对任何级别的任何父母都有效,但这是不可能的,因为你把它变成了一个不可调用的参考);
始终使用正确的父参数:大多数时候,如果小部件在构造后立即添加到布局管理器中,则不需要它,但如果您do
chat_widget = QWidget(self)
是不合适的,因为它将被重新设置为滚动区域的子级;