Update PyQt widget through ipython/jupyter noteboo

2020-07-23 05:32发布

问题:

I have an annoying issue that I have not been able to solve for the past few months. Basically, I'm using jupyter/ipython notebook to call pyqt and display 3d geometric data. This is how I initialize the app into an object and after I add some polygons and points, I call show():

class Figure(object):
    '''
    Main API functions
    '''

    def __init__(self):
        print "... initializing canvas ..."
        self.app = QApplication(sys.argv)
        self.app.processEvents()
        ...

    def show(self):   #Show
        self.GUI = GLWindow(data)   
        self.app.exec_()

I'd like to continuously interact/update the widget through the notebook cells. But once I call the show() command in the jupyter notebook I can't run any more cells or update the widget as the notebook output gets queued (?) and locked out:

#Initialize figure object inside the notebook
fig = plb.figure()
...
fig.show()  #Locks out any further jupyter commands while widget on screen
fig.update() #Does not get executed until widget is closed

It seems the .show() function called through the notebook gives up control of the python kernel (?) but it's unclear how to get it back, and also how to then connect it to the widget being displayed.

Mouse and keyboards events do interact with the widget, but they use intrinsic functions like mouseMoveEvent() that are inside the widget code:

    class GLWindow(QtGui.QWidget):

        def __init__(self, fig, parent=None):
            QtGui.QWidget.__init__(self, parent)

            self.glWidget = GLWidget(fig, parent=self)
            ...

    class GLWidget(QtOpenGL.QGLWidget):

            def __init__(self, fig, parent=None):
                QtOpenGL.QGLWidget.__init__(self, parent)
                ...

            def mouseMoveEvent(self, event):
                buttons = event.buttons()
                modifiers = event.modifiers()
                dx = event.x() - self.lastPos.x()
                dy = event.y() - self.lastPos.y()
                ...

I've tried to follow related suggestions but I don't understand how to use connections or events outside of the widget.

Any help is appreciated, I've spent so many hours on trying to fix this it's embarrassing. Cat

回答1:

I found the solution with help from the jupyter forums. Apparently there's a runtime trick in the notebook that's described here that allows you to dynamically interact with the glwindow. Very happy to finally solve this...

https://github.com/ipython/ipython/blob/master/examples/IPython%20Kernel/gui/gui-qt.py

Here's the entire function, in case that example is deleted in the future:

#!/usr/bin/env python
"""Simple Qt4 example to manually test event loop integration.
This is meant to run tests manually in ipython as:

In [5]: %gui qt

In [6]: %run gui-qt.py

Ref: Modified from http://zetcode.com/tutorials/pyqt4/firstprograms/
"""

from PyQt4 import QtGui, QtCore

class SimpleWindow(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        self.setGeometry(300, 300, 200, 80)
        self.setWindowTitle('Hello World')

        quit = QtGui.QPushButton('Close', self)
        quit.setGeometry(10, 10, 60, 35)

        self.connect(quit, QtCore.SIGNAL('clicked()'),
                     self, QtCore.SLOT('close()'))

if __name__ == '__main__':
    app = QtCore.QCoreApplication.instance()
    if app is None:
        app = QtGui.QApplication([])

    sw = SimpleWindow()
    sw.show()

    try:
        from IPython.lib.guisupport import start_event_loop_qt4
        start_event_loop_qt4(app)
    except ImportError:
        app.exec_()