0
votes

I have a PyQt5 GUI class that I want to be able to create multiple instances of either from an interactive console or normal run. I need these GUIs to be non-blocking so that they can be used while subsequent code runs.

I've tried calling app.exec__() in separate threads for each GUI like this answer, but the program sometimes crashes as the comment on the answer warned it would:
Run pyQT GUI main app in seperate Thread

And now I'm trying to get the code below working which I made based on this answer:
Run Pyqt GUI main app as a separate, non-blocking process

But when I run it the windows pop and and immediately disappear

import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import time

class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        # call super class constructor
        super(MainWindow, self).__init__()
        # build the objects one by one
        layout = QtWidgets.QVBoxLayout(self)
        self.pb_load = QtWidgets.QPushButton('Load')
        self.pb_clear= QtWidgets.QPushButton('Clear')
        self.edit = QtWidgets.QTextEdit()
        layout.addWidget(self.edit)
        layout.addWidget(self.pb_load)
        layout.addWidget(self.pb_clear)
        # connect the callbacks to the push-buttons
        self.pb_load.clicked.connect(self.callback_pb_load)
        self.pb_clear.clicked.connect(self.callback_pb_clear)

    def callback_pb_load(self):
        self.edit.append('hello world')
    def callback_pb_clear(self):
        self.edit.clear()

def show():
    app = QtWidgets.QApplication.instance()
    if not app:
        app = QtWidgets.QApplication(sys.argv)
    win = MainWindow()
    win.show()

if __name__ == '__main__':
    show()
    show()

EDIT - I don't see how this question is a duplicate. The 'duplicate' questions are only slightly related and don't provide solutions to my problem at all.

I want to be able to create multiple instances of a GUI (MainWindow in my example) by calling the show() function from either an interactive session or script, and I want those windows to stay on my screen while subsequent code is running.

EDIT2 - When I run the code as a script I can do what I want by using multiprocessing, see this demo:
https://www.screencast.com/t/5WvJNVSLm9OR

However I still need help because I want it to also work in interactive Python console sessions, and multiprocessing does not work in that case.

2
You don't need separate threads or processes. Just create one QApplication, and then open multiple windows. Obviously you must also call app.exec_() when running outside of a console, otherwise the script will end immediately. - ekhumoro
@ekhumoro Where do I call app.exec__() ? If I add app.exec__() to show() then it blocks until I close the window. I want to be able to call show() twice to open 2 GUI windows and have them both usable while subsequent code runs - pyjamas
@ekhumoro I want to be able to create as many GUI instances as I want during a console session by repeatedly calling show(), not just exactly 2. And your code still blocks as soon as you call app.exec_(), right? I want subsequent code to run without having to close the GUI first. - pyjamas
@eyllanesc I don't want to just continue creating widgets. My GUI class is a spreadsheet and the point of it is to let you view Pandas DataFrames in a GUI window while doing data analysis with Pandas. So the GUI needs to be open for me to look at while I'm typing commands into the console. I want my module to be imported, and then I want to be able to call show(df) to pop up a spreadsheet or many and still have the code after it run, whether in an interactive session or a single script. - pyjamas
I have added an answer which should do what you want. It works fine for me, but I only used a standard console for testing, so I can't guarantee that it will work in all python IDEs. - ekhumoro

2 Answers

2
votes

It isn't necessary to use separate threads or processes for this. You just need a way to maintain a reference to each new window when importing the script in a python interactive session. A simple list can be used for this. It is only necessary to explictly start an event-loop when running the script from the command-line; in an interactive session, it will be handled automatically by PyQt.

Here is an implementation of this approach:

...
_cache = []

def show(title=''):
    if QtWidgets.QApplication.instance() is None:
        _cache.append(QtWidgets.QApplication(sys.argv))
    win = MainWindow()
    win.setWindowTitle(title)
    win.setAttribute(QtCore.Qt.WA_DeleteOnClose)
    win.destroyed.connect(lambda: _cache.remove(win))
    _cache.append(win)
    win.show()

if __name__ == '__main__':

    show('Foo')
    show('Bar')

    sys.exit(QtWidgets.QApplication.instance().exec_())
1
votes

This is a minor addendum to @ekhumoro's answer. I don't have enough reputation to only add a comment so I had to write this as an answer.

@ekhumoro's answer almost fully answers @Esostack's question, but doesn't work in the Ipython console. After many hours of searching for the answer to this question myself, I came across a comment from @titusjan in a three year old thread (here) also responding to a good answer from @ekhumoro. The missing part to @ekhumoro's answer which results in the gui windows freezing for Ipython specifically is that Ipython should be set to use the qt gui at launch or once running.


To make this work with Ipython:

Launch Ipython with ipython --gui=qt5

In a running Ipython console run the magic command %gui qt5

To fix it from a Python script you can run this function

def fix_ipython():
    from IPython import get_ipython

    ipython = get_ipython()
    if ipython is not None:
        ipython.magic("gui qt5")