PySide6 Qt:在 QPlainTextEdit 中移动光标后滚动条长度错误

问题描述 投票:0回答:1

这是一个展示问题的最小示例:

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 上运行此程序时,我得到:

image 1

如您所见,光标按预期从第 3 行开始。但现在,如果我使用鼠标向上滚动一点点,就会发生以下情况:

image 2

如您所见,滚动条变短了,滚动视图一直跳到文本字段的顶部。就好像 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行的最后一位(而不是跳转一直到文本字段的开头)?

qt pyside pyside6
1个回答
0
投票

问题是由于您在小部件尚未显示时移动光标而引起的。

虽然在正常情况下,这可能工作得很好,但 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 可靠性。

© www.soinside.com 2019 - 2024. All rights reserved.