这是一个展示问题的最小示例:
import sys
from PySide6.QtGui import QTextCursor
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QPlainTextEdit,
)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 600, 400)
self.textEdit = QPlainTextEdit(self)
self.textEdit.setPlainText(
f"Line 1 {'aaa ' * 1000}\n"
"Line 2\n"
"Line 3\n"
"Line 4\n"
"Line 5\n"
f"Line 6 {'aaa ' * 1000}\n"
)
cursor = self.textEdit.textCursor()
cursor.movePosition(QTextCursor.Start)
# Move cursor to line 3
cursor.movePosition(QTextCursor.Down, QTextCursor.MoveAnchor, 3 - 1)
self.textEdit.setTextCursor(cursor)
# scrollbarPosition = self.textEdit.verticalScrollBar().value()
# self.textEdit.verticalScrollBar().setValue(0)
# self.textEdit.verticalScrollBar().setValue(scrollbarPosition)
self.setCentralWidget(self.textEdit)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
当我在 Windows 10 上运行此程序时,我得到:
如您所见,光标按预期从第 3 行开始。但现在,如果我使用鼠标向上滚动一点点,就会发生以下情况:
如您所见,滚动条变短了,滚动视图一直跳到文本字段的顶部。就好像 Qt 不知道第 1 行和第 2 行在那里(或者更准确地说,它知道这些行在那里,但没想到第 1 行这么长),当我向上滚动时,它发现有第3行上面的文本,然后加载它们,然后一路跳到顶部。然而,光标仍停留在第 3 行(这很好)。
如果我只取消注释该行
self.textEdit.verticalScrollBar().setValue(0)
从上面看,那么滚动条的长度是正确的,但是滚动视图当然是从顶部开始的,而不是从第 3 行开始。
如果我取消注释所有三个:
scrollbarPosition = self.textEdit.verticalScrollBar().value()
self.textEdit.verticalScrollBar().setValue(0)
self.textEdit.verticalScrollBar().setValue(scrollbarPosition)
要尝试记住初始滚动条位置,请转到顶部(希望加载顶部的内容),然后向下滚动回第3行,然后结果与初始程序中完全相同——滚动条的长度现在是错误的!
如何才能让滚动条的长度在滚动时不改变,并且程序从第3行开始,如果我向上滚动一点,它只显示第2行和第1行的最后一位(而不是跳转一直到文本字段的开头)?
问题是由于您在小部件尚未显示时移动光标而引起的。
虽然在正常情况下,这可能工作得很好,但 QPlainTextEdit 有自己的方式来管理文本布局,从而管理滚动条行为。这不适用于事件队列,因为滚动区域在最终显示和“滚动”其内容之前需要一些处理,而 QPlainTextEdit 的特殊行为对此不能很好地工作。
如果您的问题是将光标移动到顶部,显示特定行,那么您必须这样做的唯一方法是确保仅在第一次最终映射(“显示”)小部件时完成它,所以Qt 有足够的“时间”来正确调整其内容大小并最终更新光标和滚动条。
此外,请注意使用
QTextCursor.Down
是不合适的:您显然想要移动到(文本的)下一个 line,因此更正确的枚举是 QTextCursor.NextBlock
。
showEvent()
内进行检查,以便所需的任何操作仅完成 once。
class MainWindow(QMainWindow):
_firstShown = False
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 600, 400)
self.textEdit = QPlainTextEdit(self)
self.textEdit.setPlainText(
f"Line 1 {'aaa ' * 1000}\n"
"Line 2\n"
"Line 3\n"
"Line 4\n"
"Line 5\n"
f"Line 6 {'aaa ' * 1000}\n"
)
# everything is as above, but I removed the QTextCursor stuff
self.setCentralWidget(self.textEdit)
self._targetLine = 3
def showEvent(self, event):
super().showEvent(event)
if not self._firstShown:
# reset the flag
self._firstShown = True
# scroll to the bottom, so that the cursor will eventually be
# shown on top when moved
self.textEdit.verticalScrollBar().setValue(
self.textEdit.verticalScrollBar().maximum())
cursor = self.textEdit.textCursor()
# going to the third line means moving to the next block twice
for i in range(self._targetLine - 1):
cursor.movePosition(QTextCursor.NextBlock)
self.textEdit.setTextCursor(cursor)
更合适的实现可能应该使用 QPlainTextEdit 子类来接受或使用该
_targetLine
值并实现自己的 showEvent()
。
最后,请注意,由于上面解释的 QPlainTextEdit(及其 QPlainTextDocumentLayout)的特殊实现,当行很长时,调整大小和滚动在某种程度上总是不可靠。这是其实现的一个不幸的缺点,它的目标更多的是 UI 性能(尽可能防止冻结)而不是 UI 可靠性。