pyjamas
pyjamas

Reputation: 5357

How to run PyQt5 GUIs in non-blocking threads?

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.

Upvotes: 0

Views: 2535

Answers (2)

Tim Child
Tim Child

Reputation: 468

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")

Upvotes: 1

ekhumoro
ekhumoro

Reputation: 120578

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_())

Upvotes: 2

Related Questions