PySide2 QTextEdit 在使用换行时不会调整到它自己的内容。 (正在聊天 window)
PySide2 QTextEdit doesn't adjust to it's own content when using wrapping. (Making chat window)
当放置文本时,PySide2 QTextEdit 不会改变它自己的大小。
我正在尝试创建类似于聊天 window 的内容,其中每条消息 - QTextEdit 都处于 OnlyRead 模式。所有'messages'都放在QScrollArea中。主要目标是让消息框(下面屏幕上的消息框)根据内容调整大小。
wrong working example
我试过这段代码
https://ru.whosebug.com/questions/1408239/Как-сделать-двухсторонний-чат-в-qt-pyqt/1408264#1408264
已被复制粘贴多次。但它没有做我想要的。它创建一个固定的、不可调整大小的 QTextEdit 消息框。
举个例子,我的实际意思是,如果我们有一个单字消息,QTextEdit 小部件必须变成一个单笔划框,具有消息的宽度。如果我们有一个多句消息,QTextEdit 小部件必须成为一个多笔划框(高度已经展开,不需要在里面滚动),最大恒定长度(我会选择)。
接下来是显示正确信息的例子
(good example)
为了实现 self-adjusting 消息,需要采取一些预防措施。
正如我在 的回答和评论中所解释的那样,您必须考虑布局尺寸与其要求之间复杂而微妙的关系,这变得更加复杂 文本布局 没有固定比例。
根据文本设置宽度的主要问题是它可以更改显示它所需的高度,这会导致递归,并且由于大小是基于 当前视口大小,结果是minimumSizeHint()
在一定数量的递归调用后总是return可能的最小尺寸。
考虑到上述情况,我们必须对我的原始代码进行以下更改:
- 每当调整视图大小时,滚动区域必须始终为其消息小部件设置最大宽度,可能具有指定的边距(用于 sent/received 区别);
- 必须使用 alignment 参数将小部件添加到布局;
minimumSizeHint()
必须改为:
- 计算 首选 文本宽度(
idealWidth()
基于小部件的最大尺寸;
- 获取该文本宽度的参考高度;
- 将文本宽度设置为当前宽度;
- 将新的文档高度与之前的文档高度进行比较,如果相同则表示我们可以使用新的宽度作为提示的最大宽度(文本可以更短),否则我们使用初始文本宽度基于最大尺寸;
请注意,与您的 link 修改后的代码有几点不同:最重要的是,重写样式表没有多大意义,并且设置边距会产生值问题returned by frameWidth()
(这就是他们从文档高度中减去 100 的原因);这当然不是一个好的选择,因为边距应该在布局内设置。
class WrapLabel(QtWidgets.QTextEdit):
def __init__(self, text=''):
super().__init__(text)
self.setReadOnly(True)
self.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Maximum)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.textChanged.connect(self.updateGeometry)
def minimumSizeHint(self):
margin = self.frameWidth() * 2
doc = self.document().clone()
doc.setTextWidth(self.maximumWidth())
idealWidth = doc.idealWidth()
idealHeight = doc.size().height()
doc.setTextWidth(self.viewport().width())
if doc.size().height() == idealHeight:
idealWidth = doc.idealWidth()
return QtCore.QSize(
max(50, idealWidth + margin),
doc.size().height() + margin)
def sizeHint(self):
return self.minimumSizeHint()
def resizeEvent(self, event):
super().resizeEvent(event)
self.updateGeometry()
class ChatTest(QtWidgets.QScrollArea):
def __init__(self):
super().__init__()
self.margin = 100
self.marginRatio = .8
self.messages = []
container = QtWidgets.QWidget()
self.setWidget(container)
self.setWidgetResizable(True)
layout = QtWidgets.QVBoxLayout(container)
layout.addStretch()
self.resize(480, 360)
letters = 'abcdefghijklmnopqrstuvwxyz '
for i in range(1, 11):
msg = ''.join(choice(letters) for i in range(randrange(10, 250)))
QtCore.QTimer.singleShot(500 * i, lambda msg=msg, i=i:
self.addMessage(msg, i & 1))
def addMessage(self, text, sent=False):
message = WrapLabel(text)
message.setStyleSheet('''
WrapLabel {{
border: 1px outset palette(dark);
border-radius: 8px;
background: {};
}}
'''.format(
'#fff8c7' if sent else '#ceffbd')
)
self.messages.append(message)
self.widget().layout().addWidget(message,
alignment=QtCore.Qt.AlignRight if sent else QtCore.Qt.AlignLeft)
QtCore.QTimer.singleShot(0, self.scrollToBottom)
def scrollToBottom(self):
QtWidgets.QApplication.processEvents()
self.verticalScrollBar().setValue(
self.verticalScrollBar().maximum())
def resizeEvent(self, event):
sb = self.verticalScrollBar()
atMaximum = sb.value() == sb.maximum()
maxWidth = max(self.width() * self.marginRatio,
self.width() - self.margin) - sb.sizeHint().width()
for message in self.messages:
message.setMaximumWidth(maxWidth)
super().resizeEvent(event)
if atMaximum:
sb.setValue(sb.maximum())
当放置文本时,PySide2 QTextEdit 不会改变它自己的大小。
我正在尝试创建类似于聊天 window 的内容,其中每条消息 - QTextEdit 都处于 OnlyRead 模式。所有'messages'都放在QScrollArea中。主要目标是让消息框(下面屏幕上的消息框)根据内容调整大小。 wrong working example
我试过这段代码 https://ru.whosebug.com/questions/1408239/Как-сделать-двухсторонний-чат-в-qt-pyqt/1408264#1408264
已被复制粘贴多次。但它没有做我想要的。它创建一个固定的、不可调整大小的 QTextEdit 消息框。
举个例子,我的实际意思是,如果我们有一个单字消息,QTextEdit 小部件必须变成一个单笔划框,具有消息的宽度。如果我们有一个多句消息,QTextEdit 小部件必须成为一个多笔划框(高度已经展开,不需要在里面滚动),最大恒定长度(我会选择)。
接下来是显示正确信息的例子 (good example)
为了实现 self-adjusting 消息,需要采取一些预防措施。
正如我在
根据文本设置宽度的主要问题是它可以更改显示它所需的高度,这会导致递归,并且由于大小是基于 当前视口大小,结果是minimumSizeHint()
在一定数量的递归调用后总是return可能的最小尺寸。
考虑到上述情况,我们必须对我的原始代码进行以下更改:
- 每当调整视图大小时,滚动区域必须始终为其消息小部件设置最大宽度,可能具有指定的边距(用于 sent/received 区别);
- 必须使用 alignment 参数将小部件添加到布局;
minimumSizeHint()
必须改为:- 计算 首选 文本宽度(
idealWidth()
基于小部件的最大尺寸; - 获取该文本宽度的参考高度;
- 将文本宽度设置为当前宽度;
- 将新的文档高度与之前的文档高度进行比较,如果相同则表示我们可以使用新的宽度作为提示的最大宽度(文本可以更短),否则我们使用初始文本宽度基于最大尺寸;
- 计算 首选 文本宽度(
请注意,与您的 link 修改后的代码有几点不同:最重要的是,重写样式表没有多大意义,并且设置边距会产生值问题returned by frameWidth()
(这就是他们从文档高度中减去 100 的原因);这当然不是一个好的选择,因为边距应该在布局内设置。
class WrapLabel(QtWidgets.QTextEdit):
def __init__(self, text=''):
super().__init__(text)
self.setReadOnly(True)
self.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Maximum)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.textChanged.connect(self.updateGeometry)
def minimumSizeHint(self):
margin = self.frameWidth() * 2
doc = self.document().clone()
doc.setTextWidth(self.maximumWidth())
idealWidth = doc.idealWidth()
idealHeight = doc.size().height()
doc.setTextWidth(self.viewport().width())
if doc.size().height() == idealHeight:
idealWidth = doc.idealWidth()
return QtCore.QSize(
max(50, idealWidth + margin),
doc.size().height() + margin)
def sizeHint(self):
return self.minimumSizeHint()
def resizeEvent(self, event):
super().resizeEvent(event)
self.updateGeometry()
class ChatTest(QtWidgets.QScrollArea):
def __init__(self):
super().__init__()
self.margin = 100
self.marginRatio = .8
self.messages = []
container = QtWidgets.QWidget()
self.setWidget(container)
self.setWidgetResizable(True)
layout = QtWidgets.QVBoxLayout(container)
layout.addStretch()
self.resize(480, 360)
letters = 'abcdefghijklmnopqrstuvwxyz '
for i in range(1, 11):
msg = ''.join(choice(letters) for i in range(randrange(10, 250)))
QtCore.QTimer.singleShot(500 * i, lambda msg=msg, i=i:
self.addMessage(msg, i & 1))
def addMessage(self, text, sent=False):
message = WrapLabel(text)
message.setStyleSheet('''
WrapLabel {{
border: 1px outset palette(dark);
border-radius: 8px;
background: {};
}}
'''.format(
'#fff8c7' if sent else '#ceffbd')
)
self.messages.append(message)
self.widget().layout().addWidget(message,
alignment=QtCore.Qt.AlignRight if sent else QtCore.Qt.AlignLeft)
QtCore.QTimer.singleShot(0, self.scrollToBottom)
def scrollToBottom(self):
QtWidgets.QApplication.processEvents()
self.verticalScrollBar().setValue(
self.verticalScrollBar().maximum())
def resizeEvent(self, event):
sb = self.verticalScrollBar()
atMaximum = sb.value() == sb.maximum()
maxWidth = max(self.width() * self.marginRatio,
self.width() - self.margin) - sb.sizeHint().width()
for message in self.messages:
message.setMaximumWidth(maxWidth)
super().resizeEvent(event)
if atMaximum:
sb.setValue(sb.maximum())