如何实现响应式 QPlainTextEdit?

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

这是 MRE:

import sys, random
from PyQt5 import QtWidgets, QtCore, QtGui

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        main_splitter = MainSplitter(self)
        self.setCentralWidget(main_splitter)
        main_splitter.setOrientation(QtCore.Qt.Vertical)
        main_splitter.setStyleSheet('background-color: red; border: 1px solid pink;');
        
        # top component of vertical splitter: a QFrame to hold horizontal splitter and breadcrumbs QPlainTextEdit
        top_frame = QtWidgets.QFrame()
        top_frame_layout = QtWidgets.QVBoxLayout()
        top_frame.setLayout(top_frame_layout)
        top_frame.setStyleSheet('background-color: green; border: 1px solid green;');
        main_splitter.addWidget(top_frame)
        
        # top component of top_frame: horizontal splitter
        h_splitter = HorizontalSplitter()
        h_splitter.setStyleSheet('background-color: cyan; border: 1px solid orange;')
        top_frame_layout.addWidget(h_splitter)

        # bottom component of top_frame: QPlainTextEdit (for "breadcrumbs")
        self.breadcrumbs_pte = BreadcrumbsPTE(top_frame)
        top_frame_layout.addWidget(self.breadcrumbs_pte)
        self.text = 'some plain text '
        self.breadcrumbs_pte.setPlainText(self.text * 50)
        self.breadcrumbs_pte.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
        self.breadcrumbs_pte.setStyleSheet('background-color: orange; border: 1px solid blue;');
        self.breadcrumbs_pte.setMinimumWidth(300)
        self.breadcrumbs_pte.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)

        # bottom component of vertical splitter: a QFrame
        bottom_panel = BottomPanel(main_splitter)
        bottom_panel.setStyleSheet('background-color: magenta; border: 1px solid cyan;')
        main_splitter.addWidget(bottom_panel)

    def resizeEvent(self, *args):
        print('resize event...')
        n_repeat = random.randint(10, 50)
        self.breadcrumbs_pte.setPlainText(self.text * n_repeat)
        super().resizeEvent(*args)

class BreadcrumbsPTE(QtWidgets.QPlainTextEdit):
    def sizeHint(self):
        return QtCore.QSize(500, 100)

class MainSplitter(QtWidgets.QSplitter):
    def sizeHint(self):
        return QtCore.QSize(40, 150)

class HorizontalSplitter(QtWidgets.QSplitter):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setOrientation(QtCore.Qt.Horizontal)

        self.left_panel = LeftPanel()
        self.left_panel.setStyleSheet('background-color: yellow; border: 1px solid black;');
        self.addWidget(self.left_panel)
        self.left_panel.setMinimumHeight(150)

        right_panel = QtWidgets.QFrame()
        right_panel.setStyleSheet('background-color: black; border: 1px solid blue;');
        self.addWidget(right_panel)

        # to achieve 66%-33% widths ratio
        self.setStretchFactor(0, 20)
        self.setStretchFactor(1, 10)
         
    def sizeHint(self):
        return self.left_panel.sizeHint()
    
class LeftPanel(QtWidgets.QFrame):
    def sizeHint(self):
        return QtCore.QSize(150, 250)

class BottomPanel(QtWidgets.QFrame):
    def sizeHint(self):
        return QtCore.QSize(30, 180)

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

如果你运行它,你会发现如果你调整窗口的大小,

QPlainTextEdit
中的文本每次都会改变。

我想要实现的目标:我希望

QPlainTextEdit
中的文本调整该组件及其上方组件(
HorizontalSplitter
)的大小,以便
QPlainTextEdit
仅包含完美的文本,而不是底部留有空间。

我希望这种情况发生,这样就不会调整主窗口的大小(显然,如果发生这种情况,在编写代码时,当前会导致无限触发

MainWindow.resizeEvent()
)。

Qt/PyQt 的教程似乎没有对所有各种机制如何工作以及它们如何交互给出全面的技术解释。例如,我知道

sizeHint
在大小和布局方面起着至关重要的作用,但是,由于缺乏深入研究源代码的能力,我不知道如何提高我的理解。

例如,我尝试了在此处的各个类上注释掉

sizeHint
的无限排列,包括将它们全部注释掉:但是
self.top_pte.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
似乎从来没有起作用(即,正如我想要的那样!)。

注意,如有必要,

BottomPanel
(即垂直
QSplitter
的第二个子元素)可以设置最小和最大高度(即固定高度)以使事情变得更简单。主要目的是调整水平
QSplitter
QPlainTextEdit
,以便后者的高度恰好适合其包含的文本......

python pyqt resize
1个回答
0
投票
经过几个小时的实验,我找到了答案。我不知道其中一些技术是否会被认为是有争议的(或者只是不好)。如果 musicamante 看看这个,我会对你的观点感兴趣。

import sys, random from PyQt5 import QtWidgets, QtCore, QtGui class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) main_splitter = MainSplitter(self) self.setCentralWidget(main_splitter) main_splitter.setOrientation(QtCore.Qt.Vertical) main_splitter.setStyleSheet('background-color: red; border: 1px solid pink;'); # top component of vertical splitter: a QFrame to hold horizontal splitter and breadcrumbs QPlainTextEdit top_frame = TopFrame(self) top_frame_layout = QtWidgets.QVBoxLayout() top_frame.setLayout(top_frame_layout) top_frame.setStyleSheet('background-color: green; border: 1px solid green;'); main_splitter.addWidget(top_frame) # top component of top_frame: horizontal splitter h_splitter = HorizontalSplitter() h_splitter.setStyleSheet('background-color: cyan; border: 1px solid orange;') top_frame_layout.addWidget(h_splitter, stretch=1) # bottom component of top_frame: QPlainTextEdit (for "breadcrumbs") self.breadcrumbs_pte = BreadcrumbsPTE(top_frame) top_frame_layout.addWidget(self.breadcrumbs_pte) self.text = 'some plain text ' self.breadcrumbs_pte.setPlainText(self.text * 50 + '... END') self.breadcrumbs_pte.setStyleSheet('background-color: orange; border: 1px solid blue;'); self.breadcrumbs_pte.setMinimumWidth(300) # bottom component of vertical splitter: a QFrame bottom_panel = BottomPanel(main_splitter) bottom_panel.setStyleSheet('background-color: magenta; border: 1px solid cyan;') main_splitter.addWidget(bottom_panel) def resizeEvent(self, *args): print('WINDOW resize event...') n_repeat = random.randint(10, 50) self.breadcrumbs_pte.setPlainText(self.text * n_repeat + '... END') super().resizeEvent(*args) # fortunately this sort of technique seemed possible to avoid: # QtCore.QTimer.singleShot(0, self.updateGeometry) self.breadcrumbs_pte.resizeEvent(*args) class TopFrame(QtWidgets.QFrame): pass class BreadcrumbsPTE(QtWidgets.QPlainTextEdit): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) # print(f'+++ self.wordWrapMode() {self.wordWrapMode()}') # 4 (QTextOption::WrapAtWordBoundaryOrAnywhere) def resizeEvent(self, *args): print('BPTE resize event...') plain_text = self.document().toPlainText() print(f'+++ RESIZE len(plain_text) {len(plain_text)}') super().resizeEvent(*args) # QtCore.QTimer.singleShot(0, self.updateGeometry) self.updateGeometry() def sizeHint(self): super_hint = super().sizeHint() actual_size = self.size() print(f'+++ super_hint {super_hint} actual_size {actual_size}') # document = QtGui.QTextDocument() plain_text = self.document().toPlainText() # print(f'+++ len(plain_text) {len(plain_text)}') document.setPlainText(plain_text) document.setTextWidth(float(actual_size.width())) document_float_size = document.size() document_int_size = QtCore.QSize(int(document_float_size.width()), int(document_float_size.height())) # print(f'+++ document_int_size {document_int_size}') self.updateGeometry() return document_int_size def minimumSizeHint(self): return self.sizeHint() class MainSplitter(QtWidgets.QSplitter): pass class HorizontalSplitter(QtWidgets.QSplitter): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setOrientation(QtCore.Qt.Horizontal) self.left_panel = LeftPanel() self.left_panel.setStyleSheet('background-color: yellow; border: 1px solid black;'); self.addWidget(self.left_panel) self.left_panel.setMinimumHeight(150) right_panel = QtWidgets.QFrame() right_panel.setStyleSheet('background-color: black; border: 1px solid blue;'); self.addWidget(right_panel) # to achieve 66%-33% widths ratio self.setStretchFactor(0, 20) self.setStretchFactor(1, 10) def sizeHint(self): return self.left_panel.sizeHint() class LeftPanel(QtWidgets.QFrame): def sizeHint(self): return QtCore.QSize(150, 500) class BottomPanel(QtWidgets.QFrame): def sizeHint(self): return QtCore.QSize(30, 180) app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() app.exec_()

我注意到你经常会得到一个主窗口调整大小事件,而没有其他任何事情发生(例如其他组件上的
sizeHint
)...所以认为最好的办法是将这个

resizeEvent

 直接转发到 
BreadcrumbsPTE
。不知道是否有更好的方法强制后代widget触发
resizeEvent
sizeHint

会触发

updateGeometry

,而 
minimumSizeHint
 显然只是运行 
sizeHint
,所以它也会这样做。但 
BreadcrumbsPTE.resizeEvent
 似乎也需要触发 
updateGeometry
 才能正常工作。
我对 
QTextDocument

BreadcrumbsPTE.document

 提供的 
BreadcrumbsPTE.sizeHint
 感到困惑:我似乎无法让它改变大小:也许它受到其父级(或容器)的约束,即 
BreadcrumbsPTE
:为此原因我似乎必须制作一个单独的 
QTextDocument
 用文本填充它并设置其宽度......以便它告诉我包装文本的高度......
	

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