这是 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
,以便后者的高度恰好适合其包含的文本......
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
用文本填充它并设置其宽度......以便它告诉我包装文本的高度......