Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Unable to exit app properly
QtWS25 Last Chance

Unable to exit app properly

Scheduled Pinned Locked Moved Solved Qt for Python
9 Posts 3 Posters 16.7k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T Offline
    T Offline
    Tyskie
    wrote on last edited by
    #1

    Hello,
    I have 2 issues to close my app, first from closing a QDialog, and second to close app from systray.
    I have build an app with a systray, and some overlays (QFrame transparent), there is a QMainWindow as parent for all components, but it is hidden, as I want to the app run in background all the time.

    1. When I start my app, the user needs to select a profile, nothing fancy, just a combobox so far. Unfortunately when I close the QDialog with the dialog the app closes, but the process still runs, and the command line is not released (I am on windows - using gitbash).
    2. When using the systray I have added an exit button, but it seems to not work either. I call QCoreApplication.closeAllWindows() then QCoreApplication.quit() but it crashes on closeAllWindows() - I also tried with QApplication.quit() doesnt help, both tries end up like in #1, app hide itself, but process still runs and terminal not released.
      Ironically when it crashes with QCoreApplication.closeAllWindows() it exits the app and finishes the process and frees the terminal :D

    Anyone got any ideas what I am missing?

    Here are sample of code:

    the entrypoint app.py:

    def main():
        app = QApplication(sys.argv)
        app.setQuitOnLastWindowClosed(False)
        main_window = MainWindow()
        sys.exit(app.exec_())
    
    if __name__ == '__main__':
        main()
    

    The main window (truncated too long):

    class MainWindow(QMainWindow):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.tray_icon = None
            self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
            self.setAttribute(Qt.WA_TranslucentBackground)
            self.setAttribute(Qt.WA_NoSystemBackground)
            self.log_line_queue = queue.Queue() # queues for Internal usage
            self.threadpool = QThreadPool() # threadpool for Internal usage
            self.threadpool.setMaxThreadCount(100) # threadpool for Internal usage
            self.set_profile()  # <- will prompt QDialog with combobox
            self.init_ui()  # <- set all the other component, systray and some overlay QFrame
        def set_profile(self):
            self.profile_select = ProfileSelectionDialog(self)
            self.profile_select.show()
            if not self.profile_select.exec_():
                sys.exit(0)  # <- Something is missing here to close properly
    

    The systray:

    class MySystemTrayIcon(QSystemTrayIcon):
        def __init__(self, icon, parent=None):
            super(MySystemTrayIcon, self).__init__(icon, parent)
            menu = QMenu()
            settings_action = QAction(
                'Settings', self, triggered=self.parent().ex_popup.show)
            quit_action = QAction(
                '&Quit', self, triggered=self.exit)
            menu.addAction(settings_action)
            menu.addAction(quit_action)
            self.setContextMenu(menu)
    
        def exit(self):
            self.hide()
            QCoreApplication.closeAllWindows()  # <- Crashes here, if I comment this app closes but doesnt kill process nor release terminal
            QCoreApplication.quit()
    

    thanks

    1 Reply Last reply
    0
    • jazzycamelJ Offline
      jazzycamelJ Offline
      jazzycamel
      wrote on last edited by
      #2

      Hi there,

      1. You are starting your Dialog in the __init__() method of the main window, this means that the main event loop for your program has not yet started (as this call is in main() before QApplication().exec_()) and calls to quit() won't work.
      2. You need to call QApplication().quit() (or the shortcut qApp.quit()) rather than QCoreApplication().quit(). You don't need to worry about hiding/closing your windows.

      See my small working example below:

      try:
          from PyQt5.QtCore import QTimer, pyqtSlot
          from PyQt5.QtGui import QKeySequence, QIcon
          from PyQt5.QtWidgets import QMainWindow, QMessageBox, qApp, QMenu, QSystemTrayIcon
      except ImportError:
          from PySide2.QtCore import QTimer, Slot as pyqtSlot
          from PySide2.QtGui import QKeySequence, QIcon
          from PySide2.QtWidgets import QMainWindow, QMessageBox, qApp, QMenu, QSystemTrayIcon
      
      
      class MainWindow(QMainWindow):
          def __init__(self, parent=None, **kwargs):
              super().__init__(parent, **kwargs)
      
              self._menu = QMenu()
              self._menu.addAction("&Quit", qApp.quit, QKeySequence.Quit)
      
              self._trayIcon = QSystemTrayIcon(QIcon("./icon.png"), self)
              self._trayIcon.setContextMenu(self._menu)
              self._trayIcon.show()
      
              # This defers the call to open the dialog after the main event loop has started
              QTimer.singleShot(0, self.setProfile)
      
          @pyqtSlot()
          def setProfile(self):
              if QMessageBox.question(self, "Quit?", "Quit?") != QMessageBox.No:
                  qApp.quit()
              self.hide()
      
      
      if __name__ == "__main__":
          from sys import exit, argv
      
          try:
              from PyQt5.QtWidgets import QApplication
          except ImportError:
              from PySide2.QtWidgets import QApplication
      
          a = QApplication(argv)
          m = MainWindow()
          m.show()
          exit(a.exec_())
      

      Hope this helps :o)

      For the avoidance of doubt:

      1. All my code samples (C++ or Python) are tested before posting
      2. As of 23/03/20, my Python code is formatted to PEP-8 standards using black from the PSF (https://github.com/psf/black)
      1 Reply Last reply
      3
      • T Offline
        T Offline
        Tyskie
        wrote on last edited by
        #3

        Hi @jazzycamel thank you so much, the deferred call indeed solved all my problems.
        I have a LOT of things in self.init_ui() that need to be set after profile is set.
        Would it be fine to put self.set_profile() in the init_ui and defer that last one instead?

        jazzycamelJ 1 Reply Last reply
        0
        • T Tyskie

          Hi @jazzycamel thank you so much, the deferred call indeed solved all my problems.
          I have a LOT of things in self.init_ui() that need to be set after profile is set.
          Would it be fine to put self.set_profile() in the init_ui and defer that last one instead?

          jazzycamelJ Offline
          jazzycamelJ Offline
          jazzycamel
          wrote on last edited by
          #4

          @Tyskie
          Glad that sorted your issue.

          There's no reason why you can't defer the call to setup your UI, widgets can be created and destroyed at any time. Creating them in the __init__() is just a convention, particularly for persistent UI elements, as it means there will be no delay in their being rendered.

          For the avoidance of doubt:

          1. All my code samples (C++ or Python) are tested before posting
          2. As of 23/03/20, my Python code is formatted to PEP-8 standards using black from the PSF (https://github.com/psf/black)
          1 Reply Last reply
          2
          • T Offline
            T Offline
            Tyskie
            wrote on last edited by
            #5

            One last question, please, I see you used a @pyqtSlot decorator on the setProfile method.
            Using it or not doesn't seem to affect code execution.
            Could you please explain why you used it then?

            jazzycamelJ 1 Reply Last reply
            0
            • T Tyskie

              One last question, please, I see you used a @pyqtSlot decorator on the setProfile method.
              Using it or not doesn't seem to affect code execution.
              Could you please explain why you used it then?

              jazzycamelJ Offline
              jazzycamelJ Offline
              jazzycamel
              wrote on last edited by jazzycamel
              #6

              @Tyskie
              It's not always necessary, any method or function can be connected (including lambdas), but in those cases where the signal has an overloaded signature in C++, the @pyqtSlot decorator allows you to define which set of arguments you want to respond to and ignore the others.

              For example, QComboBox has two version of the currentIndexChanged signal: one emits a QString and the other an int. The example below shows how you can ensure your slot will only receive the signal that has the desired type:

              from PyQt5.QtCore import pyqtSlot
              from PyQt5.QtWidgets import QWidget, QVBoxLayout, QComboBox
              
              
              class Widget(QWidget):
                  def __init__(self, parent=None, **kwargs):
                      super().__init__(parent, **kwargs)
              
                      l = QVBoxLayout(self)
              
                      self._combo = QComboBox(self)
                      self._combo.addItems(["One", "Two", "Three"])
              
                      # The default version of this signal has a single int argument, you could select this specifically
                      # by appending [int] to the signal name as we have to to select the [str] version in the 
                      # following line.
                      self._combo.currentIndexChanged.connect(self.intCurrentIndexChanged)
                      self._combo.currentIndexChanged[str].connect(self.strCurrentIndexChanged)
                      l.addWidget(self._combo)
              
                  @pyqtSlot(int)
                  def intCurrentIndexChanged(self, intValue):
                      assert type(intValue) == int
                      print(intValue)
              
                  @pyqtSlot(str)
                  def strCurrentIndexChanged(self, strValue):
                      assert type(strValue) == str
                      print(strValue)
              
              
              if __name__ == "__main__":
                  from sys import argv, exit
                  from PyQt5.QtWidgets import QApplication
              
                  a = QApplication(argv)
                  w = Widget()
                  w.show()
                  exit(a.exec_())
              

              When the user selects a different value in the QComboBox, both slots are called wih the correct type of arugment (verified by the assert statements).

              Also, the @pyqtSlot decorator is necessary if you wish to expose methods from Python to QML. For example, say we had Python class like that in the following snippet:

              class Bob(QObject):
                  @pyqtSlot(result=bool)
                  def hello(self):
                      return True
              

              which we expose to QML via qmlRegisterType (before we construct our QApplication) as follows:

              qmlRegisterType(Bob, "Bob", 1, 0, "Bob")
              

              We could then instanstiate Bob from QML and call hello() as QML would know what types need to be passed and what to expect to be returned:

              import Bob 1.0
              
              ApplicationWindow {
                  visible: true
                  width: 640
                  height: 480
              
                  Bob {
                      id: bob
                  }
              
                  Component.onCompleted: {
                      console.log(bob.hello());
                  }
              }
              

              These are just snippets rather than full examples, but hopefully you get the idea and I have answered your question.

              Hope this helps :o)

              For the avoidance of doubt:

              1. All my code samples (C++ or Python) are tested before posting
              2. As of 23/03/20, my Python code is formatted to PEP-8 standards using black from the PSF (https://github.com/psf/black)
              1 Reply Last reply
              3
              • T Offline
                T Offline
                Tyskie
                wrote on last edited by
                #7

                Thank you for your answer, that is what I thought too, but it's just not used in your example, I was thinking it may have a trick to it, I guess it is just a habit to put it where it could be used :)
                On the other hand I have another issue. In the init_ui that is being deferred I have added some threadpool for multi threading purpose and it brings back the problem of closing the app again, hanging, and have to terminate manually process.

                this is how I instanciate the threadpool:

                class MainWindow():
                    def __init__(...):
                        # [...]
                        QTimer.singleShot(0, self.init_ui)
                
                    def init_ui(self):
                        self.setprofile()
                        self.threadpool = QThreadPool()
                        self.threadpool.setMaxThreadCount(100)
                        worker = MyWorker()
                        self.threadpool.start(worker)
                

                The workers are similarly based on those ones except that I do a while True loop in the function instead.

                The quit button as the same code as your working example a simple qApp.quit()

                Any ideas ? :D

                1 Reply Last reply
                0
                • SGaistS Offline
                  SGaistS Offline
                  SGaist
                  Lifetime Qt Champion
                  wrote on last edited by
                  #8

                  Hi,

                  You should give your threads an exit point so that you can stop them nicely.

                  Interested in AI ? www.idiap.ch
                  Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                  1 Reply Last reply
                  1
                  • T Offline
                    T Offline
                    Tyskie
                    wrote on last edited by Tyskie
                    #9

                    hi @SGaist thanks for the hint, that make senses, I endup adding a variable willing_to_exist = False to MainWindow and passed it to the functions of workers, exiting loops if set to True and also a threadpool.waitForDone(msecs=X) before calling qApp.quit() to avoid their destruction too fast, might not be the sexiest but it does the trick :P

                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved