QDock上具有固定长宽比的QOpenGLWidget

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

我正在尝试设置一个纵横比固定的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()

python user-interface pyqt5
2个回答
2
投票

固定小部件的长宽比真的很难实现。

虽然有一个可以解决的“单个窗口小部件”(顶部窗口),但大多数情况下,人们希望窗口中具有布局的窗口小部件具有固定的宽高比,该窗口可能包含other小部件。使用QDockWidgets,情况甚至更加复杂。

必须考虑多个方面:

  • 每个小部件都有其自己的“提示”:它希望显示的方式,最小或最大宽度或高度
  • 窗口的布局(可能还有其子窗口小部件的子布局)
  • 系统可能有一些矛盾(最大窗口大小,当该窗口靠近另一个窗口的屏幕边缘时有特殊行为)
  • 跨平台问题严重限制了控制“容器”顶级窗口调整大小的可能性

此外,在您的情况下,更多的问题将发挥作用;最重要的是,QQMainWindow具有its own private layout

QMainWindow layout graph

Qt使用对每个子小部件的调用的复杂递归系统在内部计算所有大小,最后根据其大小提示,大小策略和小部件角色(QMainWindows的“中央小部件”)对所有小部件进行布局并且其内容优先于最小大小要求,但还会考虑menuBar和statusBar大小提示)。

此外,无论QDockWidget是否浮动,其行为都可能有所不同,如果是,则必须考虑所选Dock小部件区域中的现有Dock小部件,如果该窗口支持选项卡式Dock(并且垂直选项卡支持)或嵌套基座(同一区域内的多行或多列)。最后,两个重要方面:

    QOpenGLWidget调整大小的速度不快,通常在动态调整大小时可能会导致闪烁。
  • 使用QSplitter调整扩展栏的大小后,提示将被自动忽略;虽然这也可以从理论上解决(无论如何我也不知道如何),但这可能会使事情变得更加困难。
  • 长话短说:始终
  • 不建议将要嵌入到[[any
布局中的任何小部件的固定长宽比。

在下面的示例中,我尝试解决了大多数问题,但是请记住,这是一个非常非常[的实现,存在很多问题(最重要的是,上述闪烁:使用标准QWidget后代会产生影响被最小化,但仍然存在),并且,真诚的,我完全不建议使用它。

如您所见,乍一看似乎一切正常:dock widget looking good

但是,一旦移动了底座拆分器或将窗口的大小调整为小于(dockWidgetMaximumSizeForSquare + minimumWidthOfWidgets)的宽度,小部件将始终获得垂直边距,因为没有水平空间了。

dock widget looking bad

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来伪装效果:

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