1

In my Python Dash app I would like to show some PyQt5 window pop-ups. These are activated from within in a callback.

Unfortunately, when the pop-ups are shown, I get this warning in the console:

WARNING: QApplication was not created in the main() thread.

My question: how can I create a QApplication in the main() thread if it is called from inside a Python Dash callback?

Below you can find the code which represents a mwe. If you run this snippet, you will see the warning. Apparently, when QApplication is activated from within a callback, it is not run on the main thread (my guess is that app.run_server() is already running on the main thread). How can I modify it so the QApplication is in the main thread?

The example is similar to Use Dash (plot.ly) in PyQt5 GUI but is not the same (I want pyqt5 in Dash, not Dash in pyqt5).


import os, dash
from PyQt5.QtWidgets import QApplication,QMainWindow
from PyQt5.QtCore import QCoreApplication
import dash_html_components as html
from dash.dependencies import Input, Output

class MainWindow(QMainWindow):
    #some Qpop up
    pass

app = dash.Dash()

app.layout = html.Div(children=[
    html.H1(children='Hello Dash'),
    #button which, if pressed, created a QApplication in a non-main thread
    html.Button("show pop up", id="button"),
    html.H2(children='', id='result')
    ])

#The callback which creates the pop up I want to show.
#This is activated when the button "show pop up" is pressed, and causes the warning to appear
@app.callback(
Output(component_id='result', component_property='children'),
[Input(component_id='button', component_property='n_clicks')]
)   
def popUp(n_clicks):
    if not n_clicks:
        raise dash.exceptions.PreventUpdate
    app = QCoreApplication.instance()
    if app is None:
        app = QApplication([os.getcwd()])
    mainWin = MainWindow()
    mainWin.show()
    app.exec_()
    return "You saw a pop-up"

if __name__ == '__main__':
    app.run_server(debug=False)

1 Answers1

1

The logic is to run Dash in the secondary thread and send notifications to the GUI through signals as I show below (this answer is based on my other answer):

import functools
import os
import threading

from PyQt5 import QtCore, QtWidgets

import dash
import dash_html_components as html
from dash.dependencies import Input, Output


class MainWindow(QtWidgets.QMainWindow):
    closed = QtCore.pyqtSignal()

    def closeEvent(self, event):
        self.closed.emit()
        super().closeEvent(event)


class Manager(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._view = None

    @property
    def view(self):
        return self._view

    def init_gui(self):
        self._view = MainWindow()

    @QtCore.pyqtSlot()
    def show_popup(self):
        if self.view is not None:
            self.view.show()


qt_manager = Manager()

app = dash.Dash()

app.layout = html.Div(
    children=[
        html.H1(children="Hello Dash"),
        html.Button("show pop up", id="button"),
        html.H2(children="", id="result"),
    ]
)


@app.callback(
    Output(component_id="result", component_property="children"),
    [Input(component_id="button", component_property="n_clicks")],
)
def popUp(n_clicks):
    if not n_clicks:
        raise dash.exceptions.PreventUpdate

    loop = QtCore.QEventLoop()
    qt_manager.view.closed.connect(loop.quit)
    QtCore.QMetaObject.invokeMethod(
        qt_manager, "show_popup", QtCore.Qt.QueuedConnection
    )
    loop.exec_()

    return "You saw a pop-up"


def main():
    qt_app = QtWidgets.QApplication.instance()
    if qt_app is None:
        qt_app = QtWidgets.QApplication([os.getcwd()])
    qt_app.setQuitOnLastWindowClosed(False)
    qt_manager.init_gui()
    threading.Thread(
        target=app.run_server, kwargs=dict(debug=False), daemon=True,
    ).start()

    return qt_app.exec_()


if __name__ == "__main__":
    main()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Ok so this solves my question. However, it gets stuck in this qt_app.exec_() which doesn’t respond to my ctr+c. Is there a way to safely exit my program? – Nicole Kappelhof Mar 12 '20 at 09:33