QWidget 没有显示

QtWidget is not showing

我正在尝试使用 PyQt5 来显示两个小部件,第一个是 sin、cos 和 tan 函数的绘图。我正在使用 pyqtgraph 并使用了在此 . I am also using another widget that draws a cube using PyOpenGL, by taking the example found in this link 的答案中找到的代码。我试图在一个主要的小部件中显示这两个小部件,这是主要的 window。我的做法如下

  1. 拿一个主部件。
  2. 在主小部件中,使用 QVBoxLayout()
  3. 在QVBoxLayout中,在上面提到的两个widgets处

但是当我 运行 代码时,只显示使用 pyqtgraph 的图,而不显示使用 PyOpenGL 绘制的立方体。经过一些调试,我发现立方体小部件的高度默认设置为 0。我不确定为什么会这样。我试着打电话给 glWidget.resize(640,480)。但它没有用。我刚开始使用 PyQt 和 PyOpenGL。如果我的假设是正确的,我想我遗漏了一些允许 glWidget 的高度大于 0 的细节。我也不确定这是否真的可行。我现在的代码如下,有点乱

import sys
from OpenGL.GL.images import asWrapper

from PyQt5.QtWidgets import QApplication, QGridLayout
from PyQt5 import QtWidgets
import pyqtgraph as pg
from OpenGL.GL import *
from OpenGL.GLU import *
from PyQt5 import QtGui
from PyQt5.QtOpenGL import *
from PyQt5 import QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np
from PyQt5 import QtOpenGL
import OpenGL.GL as gl
from OpenGL import GLU  
from OpenGL.arrays import vbo

class TimeLine(QtCore.QObject):
    frameChanged = QtCore.pyqtSignal(int)

    def __init__(self, interval=60, loopCount=1, parent=None):
        super(TimeLine, self).__init__(parent)
        self._startFrame = 0
        self._endFrame = 0
        self._loopCount = loopCount
        self._timer = QtCore.QTimer(self, timeout=self.on_timeout)
        self._counter = 0
        self._loop_counter = 0
        self.setInterval(interval)

    def on_timeout(self):
        if self._startFrame <= self._counter < self._endFrame:
            self.frameChanged.emit(self._counter)
            self._counter += 1
        else:
            self._counter = 0
            self._loop_counter += 1
        
        if self._loopCount > 0:
            if self._loop_counter >= self.loopCount():
                self._timer.stop()
    
    def setLoopCount(self, loopCount):
        self._loopCount = loopCount
    
    def loopCount(self):
        return self._loopCounts
    interval = QtCore.pyqtProperty(int, fget=loopCount, fset=setLoopCount)

    def setInterval(self, interval):
        self._timer.setInterval(interval)
    
    def interval(self):
        return self._timer.interval()

    interval = QtCore.pyqtProperty(int, fget=interval, fset=setInterval)

    def setFrameRange(self, startFrame, endFrame):
        self._startFrame = startFrame
        self._endFrame = endFrame

    @QtCore.pyqtSlot()
    def start(self):
        self._counter = 0
        self._loop_counter = 0
        self._timer.start()

class GLWidget(QtOpenGL.QGLWidget):
    def __init__(self, parent = None):
        self.parent = parent
        QtOpenGL.QGLWidget.__init__(self, parent)
        self.resizeGL(640,800)
    
    def initializeGL(self):
        self.qglClearColor(QtGui.QColor(0,0,255))
        gl.glEnable(gl.GL_DEPTH_TEST)

        self.initGeometry()

        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0

    def resizeGL(self, width, height):
        gl.glViewport(0, 0, width, height)
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadIdentity()
        print(width, height)
        aspect = width / float(height)

        GLU.gluPerspective(45.0, aspect, 1.0, 100.0)
        gl.glMatrixMode(gl.GL_MODELVIEW)

    def paintGL(self):
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
        gl.glPushMatrix()

        gl.glTranslate(0.0, 0.0, -50.0)
        gl.glScale(20.0, 20.0, 20.0)
        gl.glRotate(self.rotX, 1.0, 0.0, 0.0)
        gl.glRotate(self.rotY, 0.0, 1.0, 0.0)
        gl.glRotate(self.rotZ, 0.0, 0.0, 1.0)
        gl.glTranslate(-0.5, -0.5, -0.5)

        gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
        gl.glEnableClientState(gl.GL_COLOR_ARRAY)

        gl.glVertexPointer(3, gl.GL_FLOAT, 0, self.vertVBO)
        gl.glColorPointer(3, gl.GL_FLOAT, 0, self.colorVBO)

        gl.glDrawElements(gl.GL_QUADS, len(self.cubeIdxArray), gl.GL_UNSIGNED_INT, self.cubeIdxArray)

        gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
        gl.glDisableClientState(gl.GL_COLOR_ARRAY)

        gl.glPopMatrix()

    def initGeometry(self):
        self.cubeVtxArray = np.array(
                [[0.0, 0.0, 0.0],
                 [1.0, 0.0, 0.0],
                 [1.0, 1.0, 0.0],
                 [0.0, 1.0, 0.0],
                 [0.0, 0.0, 1.0],
                 [1.0, 0.0, 1.0],
                 [1.0, 1.0, 1.0],
                 [0.0, 1.0, 1.0]])
        self.vertVBO = vbo.VBO(np.reshape(self.cubeVtxArray,
                                          (1, -1)).astype(np.float32))
        self.vertVBO.bind()
        
        self.cubeClrArray = np.array(
                [[0.0, 0.0, 0.0],
                 [1.0, 0.0, 0.0],
                 [1.0, 1.0, 0.0],
                 [0.0, 1.0, 0.0],
                 [0.0, 0.0, 1.0],
                 [1.0, 0.0, 1.0],
                 [1.0, 1.0, 1.0],
                 [0.0, 1.0, 1.0 ]])
        self.colorVBO = vbo.VBO(np.reshape(self.cubeClrArray,
                                           (1, -1)).astype(np.float32))
        self.colorVBO.bind()

        self.cubeIdxArray = np.array(
                [0, 1, 2, 3,
                 3, 2, 6, 7,
                 1, 0, 4, 5,
                 2, 1, 5, 6,
                 0, 3, 7, 4,
                 7, 6, 5, 4 ])

    def setRotX(self, val):
        self.rotX = np.pi * val

    def setRotY(self, val):
        self.rotY = np.pi * val

    def setRotZ(self, val):
        self.rotZ = np.pi * val
    

class MainGui(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.resize(600,600)
        self.cube = GLWidget(self)
        self.setupUI()

    def setupUI(self):
        central_widget = QtWidgets.QWidget()
        central_layout = QtWidgets.QVBoxLayout()
        central_widget.setLayout(central_layout)

        self.setCentralWidget(central_widget)
        
        pg.setConfigOption('background',0.95)
        pg.setConfigOptions(antialias=True)
        
        self.plot = pg.PlotWidget()
        self.plot.setAspectLocked(lock = True, ratio = 0.01)
        #self.cube = GLWidget(self)
        #self.cube.resize(200,200)
        
        central_layout.addWidget(self.cube)
        central_layout.addWidget(self.plot)

        self._plots = [self.plot.plot([], [], pen=pg.mkPen(color=color, width=2)) for color in ('g', 'r', 'y')]
        self._timeline = TimeLine(loopCount = 0, interval = 10)
        self._timeline.setFrameRange(0,720)
        self._timeline.frameChanged.connect(self.generate_data)
        self._timeline.start()
    def plot_data(self, data):
        for plt, val in zip(self._plots, data):
            plt.setData(range(len(val)),val)

    @QtCore.pyqtSlot(int)
    def generate_data(self, i):
        ang = np.arange(i, i + 720)
        cos_func = np.cos(np.radians(ang))
        sin_func = np.sin(np.radians(ang))
        tan_func = sin_func/cos_func
        tan_func[(tan_func < -3) | (tan_func > 3)] = np.NaN
        self.plot_data([sin_func, cos_func, tan_func])


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    gui = MainGui()
    gui.show()
    sys.exit(app.exec_())

似乎 QGLWidget(顺便说一下,它已被弃用,QOpenGLWidget should be used instead) doesn't implement sizeHint(),所以它 returns 一个无效的大小(QSize(-1, -1)),这意味着小部件可以可能会调整为 0 宽度 and/or 高度。

由于绘图小部件具有扩展大小策略(并动态重新实现 sizeHint()),结果是 gl 小部件完全隐藏,高度为 0。

一个可能的解决方案是将具有适当 stretch 参数的小部件添加到布局中。

如果您希望两个小部件具有相同的高度,您可以执行以下操作:

    central_layout.addWidget(self.cube, stretch=1)
    central_layout.addWidget(self.plot, stretch=1)

请注意,拉伸是基于比率的(仅考虑整数),因此,如果您希望立方体的高度为图的一半:

    central_layout.addWidget(self.cube, stretch=1)
    central_layout.addWidget(self.plot, stretch=2)

或者,您可以将 setMinimumHeight() 用于 gl 小部件,但由于绘图具有扩展策略(通常优先),该 gl 小部件将 始终 有那个高度。更好的解决方案是为 gl 小部件 实现 QSizeHint 设置扩展策略,但请记住,绘图小部件具有动态大小提示,因此它总是需要一些“大小优先”的数量。

class GLWidget(QtOpenGL.QGLWidget):
    def __init__(self, parent = None):
        QtOpenGL.QGLWidget.__init__(self, parent)
        self.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)

    def sizeHint(self):
        return QtCore.QSize(300, 150)

    # ...

应该不需要在__init__中手动调用resizeGL(),你也应该总是使用动态访问parent()函数来获取parent .