0

I have a not very sophisticated but also not trivial Qt5 python application (the full thing is here: https://gitlab.com/rulrich/pydatagrab but see below for a minimal example)

My question is the following:

I have a QMainWindow where in the constructor I do many things, but also:

        self.exitAct = QAction("Exit", self, shortcut="Ctrl+Q", triggered=self.close)
        ...
        self.fileMenu = QMenu("File", self)
        self.fileMenu.addAction(self.exitAct)

furthermore, I call a file dialog in the constructor

          options = QFileDialog.Options()
          fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '',
                                                    'Images (*.png *.jpeg *.jpg *.bmp *.gif *.yaml)',
                                                    options=options)

When I either press Ctrl+Q or click on File->Exit the same thing happens: the GUI disappears, there are no errors. But the process is kept alive and does not terminate. On Linux, when I start this in the shell, killing the process with Ctrl+C (SIGINT) even does not work (very strange), I have to terminate it with kill (SIGTERM).

I don't have any special "close" method or functionality in my application. This is directly QMainWindow.close().

Initially I was 100% unclear where to start looking for the problem. However, with the help of this question here and the responses, I figured out it is the QFileDialog itself that is causing the behavior. If I take this out, the application behaves normally.

The minimally reproducing code is here:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys, os

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QMainWindow):

     def __init__(self, fileName=None):
         
          super().__init__()

          self.exitAct = QAction("Exit", self, shortcut="Ctrl+Q", triggered=self.close)
          self.fileMenu = QMenu("File", self)
          self.fileMenu.addAction(self.exitAct)
          self.menuBar().addMenu(self.fileMenu)

          self.openFile()

    
     def openFile(self):
          options = QFileDialog.Options()
          fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '',
                                                    'Images (*.png *.jpeg *.jpg *.bmp *.gif *.yaml)',
                                                    options=options)
          # load some data ...
          # ...
                        

        
if __name__ == '__main__':
     app = QApplication(sys.argv)
     window = MainWindow()
     window.show()
     sys.exit(app.exec_())

=================================

Output of print(app.allWidgets()) (after fixing the problem):

[<PyQt5.QtWidgets.QMenu object at 0x7fb8e6ae49d0>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6ae4b80>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae4c10>, <PyQt5.QtWidgets.QMenu object at 0x7fb8e6ae4ca0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae4d30>, <PyQt5.QtWidgets.QPushButton object at 0x7fb8e6ae4160>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6ae4dc0>, <PyQt5.QtWidgets.QLineEdit object at 0x7fb8ee6ea790>, <PyQt5.QtWidgets.QMenu object at 0x7fb8e6ae4790>, <PyQt5.QtWidgets.QLineEdit object at 0x7fb8ee6eae50>, <PyQt5.QtWidgets.QFrame object at 0x7fb8e6ae4e50>, <PyQt5.QtWidgets.QScrollArea object at 0x7fb8ee6ea5e0>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6ea670>, <PyQt5.QtWidgets.QLabel object at 0x7fb8e6ae40d0>, <PyQt5.QtWidgets.QToolButton object at 0x7fb8e6ae4ee0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae4f70>, <PyQt5.QtWidgets.QLineEdit object at 0x7fb8ee6eac10>, <PyQt5.QtWidgets.QFrame object at 0x7fb8e6af5040>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af50d0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5160>, <PyQt5.QtWidgets.QLineEdit object at 0x7fb8ee6ea9d0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae4af0>, <__main__.MainWindow object at 0x7fb8ee6ea160>, <PyQt5.QtWidgets.QLabel object at 0x7fb8e6ae4040>, <PyQt5.QtWidgets.QComboBox object at 0x7fb8ee6ea430>, <PyQt5.QtWidgets.QFrame object at 0x7fb8e6af51f0>, <PyQt5.QtWidgets.QListView object at 0x7fb8e6af5280>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5310>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af53a0>, <__main__.DataArea object at 0x7fb8ee6ea550>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5430>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af54c0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5550>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af55e0>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5670>, <PyQt5.QtWidgets.QMenuBar object at 0x7fb8e6ae4a60>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6eadc0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5700>, <PyQt5.QtWidgets.QFrame object at 0x7fb8e6af5790>, <PyQt5.QtWidgets.QListView object at 0x7fb8e6af5820>, <PyQt5.QtWidgets.QComboBox object at 0x7fb8ee6eab80>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af58b0>, <PyQt5.QtWidgets.QListView object at 0x7fb8e6af5940>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af59d0>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6eaaf0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5a60>, <PyQt5.QtWidgets.QComboBox object at 0x7fb8ee6ea4c0>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5af0>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5b80>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5c10>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5ca0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5d30>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6eaca0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5dc0>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6ea820>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6ea940>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5e50>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5ee0>, <PyQt5.QtWidgets.QComboBox object at 0x7fb8ee6ea700>, <PyQt5.QtWidgets.QListView object at 0x7fb8e6af5f70>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae41f0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6afc040>, <PyQt5.QtWidgets.QMenu object at 0x7fb8e6ae48b0>]

Further simplified MRE:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys, os

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class MainWindow(QMainWindow):

     def __init__(self, fileName=None):         
          super().__init__()
          key = QShortcut(QKeySequence("Ctrl+Q"), self)
          key.activated.connect(self.close)
          self.openFile()

    
     def openFile(self):
          fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '',
                                                    'Images (*.png *.jpeg *.jpg *.bmp *.gif *.yaml)')#)
          #,options=QFileDialog.DontUseNativeDialog)
                        

        
if __name__ == '__main__':
     app = QApplication(sys.argv)
     window = MainWindow()
     window.show()
     sys.exit(app.exec_())

-> no change in behaviour.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
Ralf Ulrich
  • 1,575
  • 9
  • 25
  • please provide a [mre], nor external links – eyllanesc Sep 24 '21 at 07:35
  • I was hoping the solution is so simple and I am so stupid that the pieces of code I added are sufficient for experts to fix the problem. But I will see if I manage a smaller example... – Ralf Ulrich Sep 24 '21 at 07:40
  • The minimum is not less code but the code needed to reproduce the problem. In your case the 3 lines of code is not enough. Please read [ask] and pass the [tour] – eyllanesc Sep 24 '21 at 07:42
  • @RalfUlrich the point is that the code provided in the question is not sufficient to understand the problem, and posts should always be self-contained, because questions (and their answers) should be useful to the whole community and in the future, not only to those who ask them. You provided a repository, and at some point that code might change or even become unavailable, which would make your question invalid and impossible to understand to others that might face a similar issue in the future. Besides that, with all due respect, you can't expect us to debug 1000 lines of code for you. – musicamante Sep 24 '21 at 07:49
  • Yes I understand, and I am looking at this (anyway), but my trouble is: if I just scale down to a minimal Qt example: of course it works... But, I don't know where to start looking why it doesn't work in my code. I did not want to put the entire code (1000lines) since clearly most of this is unrelated... but let me do it (temporarily) until I manage to find ways to scale it down. – Ralf Ulrich Sep 24 '21 at 07:55
  • "killing the process with Ctrl+C (SIGINT) even does not work": that's not strange, and it is the expected behavior. It happens because Qt is occupied with its own event loop (which is implemented in the C++ side), so python cannot process signals until Qt releases control. See [What is the correct way to make my PyQt application quit when killed from the console (Ctrl-C)?](https://stackoverflow.com/q/4938723) – musicamante Sep 24 '21 at 07:56
  • @RalfUlrich if it works when shortened, it means that the problem is somewhere in the middle. Start by removing small pieces of code, and test it until the problem cannot be reproduced any more. At that point you'll know *where* is the problem, if you still don't understand *why* then restore that portion, and remove any other part that doesn't seem related to it (by constantly trying if the problem *still* occurs), and then you'll have managed a MRE. Note that most of the times creating a MRE leads to finding the solution on your own, so it's always worth it, even if it's not always painless. – musicamante Sep 24 '21 at 07:59
  • Now, there is a mini example. And note: the problem is after I would expect the Qt event loop to stop (after calling QMainWindow.close()). – Ralf Ulrich Sep 24 '21 at 08:41
  • @RalfUlrich I cannot reproduce the problem (using python-3.9.7, qt-5.15.2 and pyqt 5.15.4 on linux). Is the point of your example that the file-dialog is the deciding factor? Try adding `options |= QFileDialog.DontUseNativeDialog` to see if that makes any difference. You could also try adding `self.setAttribute(Qt.WA_DeleteOnClose)` in the main-window `__init__`. If neither of those things work, change the last line of the example to `app.exec_(); print(app.allWidgets())` and show the output. – ekhumoro Sep 24 '21 at 10:44
  • GREAT! The `options |= QFileDialog.DontUseNativeDialog` fixed it easily. Do you know what is the background for this? Can you please copy your comment into an answer so that I can accept it? – Ralf Ulrich Sep 24 '21 at 18:06
  • @RalfUlrich I'm not exactly sure - somehow, the native dialog is being kept alive and preventing termination of the main event-loop. What platform(s) are you testing on, and what *exact* version(s) of Qt5 and PyQt5 are you using? Also, before I attempt to write an answer, it would be helpful if you tried the other suggestions given in my previous comment and gave some feedback on those. And please test your MRE without the menus, so we can be sure it's only the native file-dialog that is causing the problem. – ekhumoro Sep 25 '21 at 13:20
  • I am on ubuntu 20.04.3 (Linux rucktop 5.4.0-86-generic #97-Ubuntu SMP Fri Sep 17 19:19:40 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux), Python 3.8.10, PyQt5 5.14.1 The `self.setAttribute(Qt.WA_DeleteOnClose)` did not help. The `app.exec()` call does not finish if the problem occurs. Otherwise, I printed the output above. When I removed the Gui Menu (replaced with QShortCut, see above). The problem is the same, and can be solved with `QFileDialog.DontUseNativeDiolog` – Ralf Ulrich Sep 25 '21 at 22:47

1 Answers1

2

It seems this issue may be related to QTBUG-59184, which affects native GTK file dialogs opened by Qt5. The bug is still unresolved and has critical priority, so hopefully it will be fixed reasonably quickly. In the meantime, the work-around is to use the built-in Qt file-dialog, which can be specified via the options, like this:

QFileDialog.getOpenFileName(parent, caption, options=QFileDialog.DontUseNativeDialog)

or like this:

dialog = QFileDialog(parent, caption)
dialog.setOption(QFileDialog.DontUseNativeDialog, True)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336