<p>小部件的固定纵横比是很难实现的。你知道吗</p>
<p>虽然有一个“单个小部件”(顶部窗口)可以使用,但大多数情况下,人们希望小部件的长宽比是固定的,这些小部件位于具有布局的窗口中,其中可能包含<em>其他</em>小部件。有了Qdock,情况就更复杂了。你知道吗</p>
<p>必须考虑多个方面:</p>
<ul>
<li>每个小部件都有自己的“提示”:它喜欢显示的方式,它的最小或最大宽度或高度</li>
<li>窗口的布局(可能还有子窗口小部件的子布局)</li>
<li>系统可能有一些限制(最大窗口大小,当窗口靠近另一个窗口的屏幕边缘时的特殊行为)</li>
<li>跨平台问题严重限制了控制调整“容器”顶层窗口大小的可能性</li>
</ul>
<p>此外,在您的例子中,更多的问题会出现;最重要的是,QQMainWindow有<a href="https://doc.qt.io/qt-5/qmainwindow.html#qt-main-window-framework" rel="nofollow noreferrer">its own private layout</a>:</p>
<p><a href="https://doc.qt.io/qt-5/images/mainwindowlayout.png" rel="nofollow noreferrer"><img src="https://doc.qt.io/qt-5/images/mainwindowlayout.png" alt="QMainWindow layout graph"/></a></p>
<p>Qt通过调用每个子窗口小部件的复杂递归系统在内部计算所有大小,最后根据其大小提示、大小策略和窗口小部件角色布置所有窗口小部件(QMainWindows的“中心窗口小部件”及其内容优先于最小大小要求,但是菜单栏和状态栏大小提示也被考虑在内)。你知道吗</p>
<p>另外,QDockWidget可以有不同的行为,不管它是浮动的还是不浮动的,如果它是浮动的,它必须考虑所选dock widget区域中的<em>其他</em>现有dock widget,如果窗口支持选项卡式停靠(并且如果支持垂直选项卡)或嵌套停靠(同一区域中的多行或列)。你知道吗</p>
<p>最后,有两个重要方面:</p>
<ul>
<li><p>QOpenGLWidget在调整大小方面并不是<em>那么快,这通常会导致动态调整大小时出现一些闪烁。</p></li>
<li><p>一旦你用它的qspliter调整dockbar的大小,这个提示就会被自动忽略;虽然这在理论上也是可以解决的(我不知道怎么做),但这可能会让事情变得更加困难。</p></li>
</ul>
<p>长话短说:任何要嵌入<em>任何</em>布局的小部件的固定纵横比都是<strong>不鼓励的。你知道吗</p>
<p>在下面的示例中,我试图解决大多数问题,但请记住,这是一个非常原始的实现,有很多问题(最重要的是,前面提到的闪烁:使用标准的QWidget子代,效果最小化,但仍然存在),而且,真诚地说,我不建议使用它。你知道吗</p>
<p>如你所见,乍一看似乎一切正常:</p>
<p><a href="https://i.stack.imgur.com/5yDAz.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/5yDAz.png" alt="dock widget looking good"/></a></p>
<p>但是,只要移动dock splitter或者将窗口调整到小于<code>(dockWidgetMaximumSizeForSquare + minimumWidthOfWidgets)</code>的宽度,小部件就可以获得垂直边距,因为没有水平空间了。你知道吗</p>
<p><a href="https://i.stack.imgur.com/mYWWy.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/mYWWy.png" alt="dock widget looking bad"/></a></p>
<pre><code>from PyQt5 import QtCore, QtGui, QtWidgets
class Renderizador(QtWidgets.QOpenGLWidget):
# ...
class RenderizadorContainer(QtWidgets.QWidget):
def __init__(self, renderizador=None):
QtWidgets.QWidget.__init__(self)
self.setMinimumSize(20, 20)
self._currentSize = self.minimumSize()
self._dirty = False
self._shown = False
self._newSize = self.minimumSize()
self.renderizador = None
self.setRenderizador(renderizador)
def setRenderizador(self, renderizador):
if self.renderizador:
self.renderizador.setParent(None)
self.renderizador = renderizador
if self.renderizador:
# set the parent to this widget container; this will also "constraint"
# it to this widget, also limiting its paint area
self.renderizador.setParent(self)
self.updateGeometry()
def event(self, event):
if event.type() == QtCore.QEvent.Resize and event.size().isValid():
# capture the resize event before it's actually sent to resizeEvent;
# remember that, at this point, the resize has already happened!
self._dirty = True
# the widget has probably not been shown yet, use the minimum size
# to start with
self._newSize = event.size() if self._shown else self.minimumSize()
# notify the layout that the sizeHint has changed
self.updateGeometry()
return QtWidgets.QWidget.event(self, event)
def sizeHint(self):
if self._dirty:
self._dirty = False
# provide the layout a square size hint
maxSize = max(self._newSize.width(), self._newSize.height())
self._currentSize = QtCore.QSize(maxSize, maxSize)
return self._currentSize
return self._currentSize
def showEvent(self, event):
QtWidgets.QWidget.showEvent(self, event)
# the widget is being "mapped", keep track of it
self._shown = True
def resizeEvent(self, event):
if not self.renderizador:
return
# since the "renderizador" is not placed into a layout, its position is
# "free", and we can set its geometry as we like; let's move it to the
# center of this widget
minSize = min(self.width(), self.height())
rect = QtCore.QRect(0, 0, minSize, minSize)
rect.moveCenter(self.rect().center())
self.renderizador.setGeometry(rect)
class RenderizadorDockWidget(QtWidgets.QDockWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QDockWidget.__init__(self, *args, **kwargs)
self.setWindowTitle('Dock test')
self.newSize = None
self.topLevelChanged.connect(self.checkNewSize)
# resizing should *never* happen within a resizeEvent, but in some cases
# it can be done *after* it; this will minimize flickering, but it will
# *never* be optimal.
self.resizeTimer = QtCore.QTimer(
singleShot=True, interval=0, timeout=self.delayedResize)
def checkNewSize(self, topLevel):
if topLevel:
# whenever the widget is "undocked", it will be resized; since the
# dock widget has a titlebar, we have to take it into account
opt = QtWidgets.QStyleOptionDockWidget()
self.initStyleOption(opt)
titleHeight = self.style().subElementRect(
QtWidgets.QStyle.SE_DockWidgetTitleBarText, opt, self).height()
minSize = min(self.width(), self.height() - titleHeight)
self.resize(minSize, minSize)
if self.widget().renderizador:
self.widget().renderizador.update()
elif self.widget().renderizador:
# the "renderizador" might not be updated instantly after "redocking"
# the widget; schedule an update to force its repainting
self.widget().renderizador.update()
def delayedResize(self):
if self.newSize is not None and self.isFloating():
size, delta = self.newSize
self.newSize = None
self.resize(size, size + delta)
def resizeEvent(self, event):
if self.isFloating() and event.size() != event.oldSize():
# as for the topLevelChanged, compute the title bar height
opt = QtWidgets.QStyleOptionDockWidget()
self.initStyleOption(opt)
titleHeight = self.style().subElementRect(
QtWidgets.QStyle.SE_DockWidgetTitleBarText, opt, self).height()
newWidth = event.size().width()
newHeight = event.size().height()
oldWidth = event.oldSize().width()
oldHeight = event.oldSize().height()
if newWidth == oldWidth:
# width is the same, the reference is the title height
self.newSize = event.size().height() - titleHeight, titleHeight
elif newHeight == oldHeight:
# height is the same, the reference is the width
self.newSize = event.size().width(), titleHeight
else:
# resizing cannot be based on minimum or maximum, while there are
# various approach for this, I believe that the optimal "hint" is
# an average value between the difference between width and height
# of the new size; other single fixed-aspect-ratio-widget based
# programs use similar methods, like mpv/mplayer
if not self.newSize:
if newWidth != oldWidth:
newSize = newWidth - (newWidth - newHeight + titleHeight) / 2
else:
newSize = newHeight - (newHeight - newWidth) / 2 - titleHeight
self.newSize = newSize, titleHeight
else:
oldWidth = self.newSize[0]
oldHeight = oldWidth + titleHeight
if newWidth != oldWidth:
newSize = newWidth - (newWidth - newHeight + titleHeight) // 2
else:
newSize = newHeight - (newHeight - newWidth) // 2 - titleHeight
self.newSize = newSize, titleHeight
# schedule a resize based on the new size
self.resizeTimer.start()
class DockTest(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
# some random widgets to better show the overall behavior with an existing
# layout and some minimum size limitations for the window
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QGridLayout(central)
layout.addWidget(QtWidgets.QLabel('Test label'), 0, 0)
layout.addWidget(QtWidgets.QLineEdit(), 0, 1)
groupBox = QtWidgets.QGroupBox(title='Group box')
layout.addWidget(groupBox, 1, 0, 1, 2)
groupLayout = QtWidgets.QVBoxLayout(groupBox)
groupLayout.addWidget(QtWidgets.QCheckBox('Checkbox'))
groupLayout.addWidget(QtWidgets.QPushButton('Button'))
layout.addWidget(QtWidgets.QTableView(), 2, 0, 1, 2)
self.renderizadorDock = RenderizadorDockWidget()
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.renderizadorDock)
self.renderizador = Renderizador()
self.renderizadorDock.setWidget(RenderizadorContainer(self.renderizador))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
dockTest = DockTest()
dockTest.show()
sys.exit(app.exec_())
</code></pre>