我正在尝试设置一个纵横比固定的QOpenGLWidget。问题在于此小部件位于QDock内,因此resizeEvent方法会干扰渲染我不知道为什么。我该怎么办?
MRE:
# -*- coding: utf-8 -*-
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QOpenGLWidget, QMainWindow, QApplication, QDockWidget, QLabel
class Renderizador(QOpenGLWidget):
def __init__(self):
QOpenGLWidget.__init__(self)
# policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
# policy.setHeightForWidth(True)
# self.setSizePolicy(policy)
# size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
# size.setHeightForWidth(True)
# size.setWidthForHeight(True)
# self.setSizePolicy(size)
# def heightForWidth(self, width):
# return width
# def sizeHint(self):
# return QSize(400, 400)
# def resizeEvent(self, event):
# event.accept()
# self.resize(event.size().width(), event.size().height())
class Ventana(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
widget_central = QLabel("Hi")
widget_central.setGeometry(10, 10, 100, 100)
self.setCentralWidget(widget_central)
self.renderizador = Renderizador()
dock_renderizador = QDockWidget("Render")
dock_renderizador.setFeatures(QDockWidget.DockWidgetMovable)
self.addDockWidget(Qt.LeftDockWidgetArea, dock_renderizador)
dock_renderizador.setWidget(self.renderizador)
if __name__ == "__main__":
MainEvent = QApplication([])
main_app = Ventana()
main_app.show()
MainEvent.exec()
固定小部件的长宽比真的很难实现。
虽然有一个可以解决的“单个窗口小部件”(顶部窗口),但大多数情况下,人们希望窗口中具有布局的窗口小部件具有固定的宽高比,该窗口可能包含other小部件。使用QDockWidgets,情况甚至更加复杂。
必须考虑多个方面:
此外,在您的情况下,更多的问题将发挥作用;最重要的是,QQMainWindow具有its own private layout:
Qt使用对每个子小部件的调用的复杂递归系统在内部计算所有大小,最后根据其大小提示,大小策略和小部件角色(QMainWindows的“中央小部件”)对所有小部件进行布局并且其内容优先于最小大小要求,但还会考虑menuBar和statusBar大小提示)。
此外,无论QDockWidget是否浮动,其行为都可能有所不同,如果是,则必须考虑所选Dock小部件区域中的现有Dock小部件,如果该窗口支持选项卡式Dock(并且垂直选项卡支持)或嵌套基座(同一区域内的多行或多列)。最后,两个重要方面:
在下面的示例中,我尝试解决了大多数问题,但是请记住,这是一个非常非常[
但是,一旦移动了底座拆分器或将窗口的大小调整为小于(dockWidgetMaximumSizeForSquare + minimumWidthOfWidgets)
的宽度,小部件将始终获得垂直边距,因为没有水平空间了。
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_())
非常感谢您的回答,musicamante。我发现了如何重新实现resizeEvent来伪装效果: