QD上具有固定长宽比的QOpenGLWidget

2024-09-25 10:29:23 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图建立一个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()


Tags: selfeventtruesizeinitdefpolicywidth
2条回答

非常感谢你的回答,音乐大师。 我找到了如何重新实现resizeEvent来伪造效果:

def resizeEvent(self, event):
    QOpenGLWidget.resizeEvent(self, event)
    if self.width() > self.height():
        self.resize(self.height(), self.height())
    elif self.height() > self.width():
        self.resize(self.width(), self.width())

小部件的固定纵横比是很难实现的。你知道吗

虽然有一个“单个小部件”(顶部窗口)可以使用,但大多数情况下,人们希望小部件的长宽比是固定的,这些小部件位于具有布局的窗口中,其中可能包含其他小部件。有了Qdock,情况就更复杂了。你知道吗

必须考虑多个方面:

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

此外,在您的例子中,更多的问题会出现;最重要的是,QQMainWindow有its own private layout

QMainWindow layout graph

Qt通过调用每个子窗口小部件的复杂递归系统在内部计算所有大小,最后根据其大小提示、大小策略和窗口小部件角色布置所有窗口小部件(QMainWindows的“中心窗口小部件”及其内容优先于最小大小要求,但是菜单栏和状态栏大小提示也被考虑在内)。你知道吗

另外,QDockWidget可以有不同的行为,不管它是浮动的还是不浮动的,如果它是浮动的,它必须考虑所选dock widget区域中的其他现有dock widget,如果窗口支持选项卡式停靠(并且如果支持垂直选项卡)或嵌套停靠(同一区域中的多行或列)。你知道吗

最后,有两个重要方面:

  • QOpenGLWidget在调整大小方面并不是那么快,这通常会导致动态调整大小时出现一些闪烁。

  • 一旦你用它的qspliter调整dockbar的大小,这个提示就会被自动忽略;虽然这在理论上也是可以解决的(我不知道怎么做),但这可能会让事情变得更加困难。

长话短说:任何要嵌入任何布局的小部件的固定纵横比都是不鼓励的。你知道吗

在下面的示例中,我试图解决大多数问题,但请记住,这是一个非常原始的实现,有很多问题(最重要的是,前面提到的闪烁:使用标准的QWidget子代,效果最小化,但仍然存在),而且,真诚地说,我不建议使用它。你知道吗

如你所见,乍一看似乎一切正常:

dock widget looking good

但是,只要移动dock splitter或者将窗口调整到小于(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_())

相关问题 更多 >