PyQt5 QPushButton doesn't respond in Spyder, OK from command line

991 views
Skip to first unread message

jim...@yahoo.com

unread,
Aug 12, 2017, 6:28:59 PM8/12/17
to spyder
I'm new to PyQt, and I'm following the tutorial at http://zetcode.com/gui/pyqt5/firstprograms/ to build a GUI with a "Quit" push button. When I run it from Spyder, the Quit button doesn't work and Spyder locks up. I must restart the kernel to recover. However, when I run this code from the python command line directly, it works just fine and clicking on the "Quit" button closes the window and terminates the application.

After several hours of looking, I have not found this issue addressed anywhere online. Any explanation and/or work-around would be greatly appreciated.

I am running Windows 10 Home Edition (64b), and using Python 3.5.3, Spyder 3.2 and Qt5.5.1.


import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication
from PyQt5.QtCore import QCoreApplication


class Example(QWidget):
    
    def __init__(self):
        super().__init__()
        
        self.initUI()
        
        
    def initUI(self):               
        
        qbtn = QPushButton('Quit', self)
        qbtn.clicked.connect(QCoreApplication.instance().quit)
        qbtn.resize(qbtn.sizeHint())
        qbtn.move(50, 50)       
        
        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Quit button')    
        self.show()
        
        
#if __name__ == '__main__':
#    
#    app = QApplication(sys.argv)
#    ex = Example()
#    sys.exit(app.exec_())


   # The following "if" logic keeps the kernal from dying every other time.
    # https://stackoverflow.com/questions/40094086/python-kernel-dies-for-second-run-of-pyqt5-gui
    # Qt does not like more than one QApplication object in the same process.
    # The "instance()" method retrieves the application instance ("None" if not
    # already created).

if __name__ == "__main__":
     app = QCoreApplication.instance()
    if app is None:
        app = QApplication(sys.argv) # QApp requires the sys arg list.

    ex = Example()

    app.exec_()

Konstantin Krayn

unread,
Aug 26, 2017, 2:11:55 PM8/26/17
to spyder
I am a newcomer to Qt too, and I had exactly the same issue with Windows+Python+Qt+Spider while trying to run the same zetcode tutorial example. I have no idea why it does not work as intended but replacing the handler in question QCoreApplication.instance().quit with self.close appears to work. I came up with this workaround after observing that the standard way to close the app window by clicking "x" button in the window title bar works as expected, and I assume that self.close() does something like that.

Still would be helpful to understand why quit() does not work as one would expect and what is the right way to quit this application in Spyder environment.

        qbtn.clicked.connect(QCoreApplication.instance().quit)

jim...@yahoo.com

unread,
Aug 26, 2017, 4:41:55 PM8/26/17
to spyder



Hello Konstantin,

Thank you for your observation that self.close() is a reasonable work-around. I had the same thought as you and found that it worked, too. But I wasn't satisfied without some sort of explanation, so I continued searching the web during the last week and I found the following information related to the order of garbage collection that might point to the root of this issue. However, I still have no idea why the code behaves differently in the Spyder enviroment vs. from the command line:
http://pyqt.sourceforge.net/Docs/PyQt5/gotchas.html#crashes-on-exit

After implementing the structural change per the sourceforge description, here's what my code looks like, and it works properly both from the Spyder environment as well as from the command line:

#########################

import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication
from PyQt5.QtCore import QCoreApplication


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):

        qbtn = QPushButton('Quit', self)
        qbtn.clicked.connect(QCoreApplication.instance().quit)
#        qbtn.clicked.connect(self.close)

        qbtn.resize(qbtn.sizeHint())
        qbtn.move(50, 50)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Quit button')
        self.show()


def main():
    """
    SOURCE:
    http://pyqt.sourceforge.net/Docs/PyQt5/gotchas.html#crashes-on-exit

    Creating the "main" function to contain the event loop and call the GUI
    Example class changes the garbage collection order (module destructors vs.
    the actual Qapplication destruction). This keeps the GUI from hanging
    and killing the kernel. Note the location of "ex.show()"
    """
    global app

    # The "if app is None" logic below keeps kernal from dying every other time.

    # https://stackoverflow.com/questions/40094086/python-kernel-dies-for-second-run-of-pyqt5-gui
    # Qt does not like more than one QApplication object in the same process.
    # The "instance()" code gets the application instance if it was already
    # created.

    app = QCoreApplication.instance()
    if app is None:
        app = QApplication(sys.argv) # QApp requires the sys arg list.

    ex = Example()
    ex.show() # Widget first created in memory; "show()" required for display.

    app.exec()


if __name__ == "__main__":
    main()

#################################

I'm not sure as to why "app" has to be set as a global in main(). It appears to work OK with this line commented out...

Since I don't understand, I've just left the "global app" line as-is, and for every subsequent PyQt5 tutorial I have been using this same main() and show(), et. al., code structure with great success.


Incidentally, how far along in the zetcode tutorials are you? Were you able to get the "View StatusBar" checkMenu's QAction to work properly? I was thinking about posting this as another, separate Windows+Python3+PyQt5+Spyder issue...

Here are my symptoms: Running the checkMenu code in Spyder, the toggle doesn't permanently change the statusBar view on/off as I believe is intended. The statusBar message turns off permanently just by hovering the mouse cursor over the View menu label. Then, the only way to get the statusBar message to be visible again is to click the checkMenu "View" button and hover the mouse over the text in the QAction sub-menu. Alternatively, when I run this same code from the command line, the checkMenu QAction to toggle whether the StatusBar is visible or not actually terminates the application instead.

Let me know when you get to this point in the tutorial or what you experienced if you've already tried it out...

Jim


 

Konstantin Krayn

unread,
Aug 26, 2017, 5:48:49 PM8/26/17
to spyd...@googlegroups.com
Jim,

Thank you for the garbage collection info link and the restructured code sample that works in Spyder. It looks quite relevant and helpful if even a bit too complicated for me to understand from the first glance.

As to the zetcode tutorials, I am at "Dialogs in PyQt5", and so far I have not encountered any further issues while running the tutorial code samples in Spyder. I keep using self.close handler to exit apps, and btw at some point in the tutorial they too stopped using app.quit and switched to self.close (without explaining why).

I had no problem with the "View StatusBar" tutorial code, which worked properly both in Spyder and from the command line on my system. I do not know why the app unexpectedly terminates when run from the command line on your system, but the behavior when run in Spyder that you described as symptoms sounds like the intended functionality to me. My understanding is that, apart from the initial "ready" message that legitimately disappears for good after you rollover the menu bar, the only thing to see in the status bar of this Qt application is the status bar tip for the toggle action menu item itself. So, when it is checked and you hover the mouse pointer over it, you see the status bar tip, and you do not see the tip when the action item is unchecked. This is how it works on my system, and, judging by you description, it appears to work the same way on yours, which I think is the intended behavior.

Konstantin

On Sat, Aug 26, 2017 at 9:58 PM, jimmott via spyder <spyd...@googlegroups.com> wrote:
[...]

jim...@yahoo.com

unread,
Aug 27, 2017, 12:48:22 PM8/27/17
to spyder
Hi Konstantin,

Interesting observation about the shift to self.close() in the subsequent tutorials...  I think I will switch over to the self.close() method as my standard way to quit the application, but I'm also going to keep the main() layout recommended by sourceforge because it seems to help keep the order of garbage collection in proper sequence. And given my experience with interactions between Qt functionality (described below), I want to keep things as orderly as possible.

I went back to the View StatusBar tutorial to review my understanding and do some further checks. Here's my observations:

1.  I'm not sure I agree with you about the intended functionality of the "View" checkmenu, but as the tutorial isn't exactly super clear about what is supposed to happen, I will assume you are correct. That is to say, as soon as the mouse hovers over the "View" menubar text, the statusbar "ready" text disappears. From then on, the statusbar text is only visible when the View menu is clicked open and a) the "view statusbar" box is positively checked and b) the mouse hovers over the "view statusbar" submenu. If the "view statusbar" box is unchecked in the submenu, nothing will ever show up in the statusbar.

2. When I go back and run *just* the checkMenu code by itself, as shown in the tutorial (but also with my "if app is None" logic added to avoid multiple app instances causing Spyder grief...), I get correct "View" behavior (as defined by you) both in Spyder and from the command line (yay!). Here is that code:

*************************************************
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, QApplication
from PyQt5.QtCore import QCoreApplication

class Example(QMainWindow):


    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):

        self.statusbar = self.statusBar()
        self.statusbar.showMessage('Ready')

        menubar = self.menuBar()
        viewMenu = menubar.addMenu('View')

        viewStatAct = QAction('View statusbar', self, checkable=True)
        viewStatAct.setStatusTip('View statusbar')
        viewStatAct.setChecked(True)
        viewStatAct.triggered.connect(self.toggleMenu)

        viewMenu.addAction(viewStatAct)

        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Check menu')
        self.show()


    def toggleMenu(self, state):

        if state:
            self.statusbar.show()
        else:
            self.statusbar.hide()


if __name__ == '__main__':


    app = QCoreApplication.instance()
    if app is None:
        app = QApplication(sys.argv)

    ex = Example()

    app.exec_()
*************************************************




But what I didn't explain to you initially was that as I did each tutorial, I added each lesson's new functionality to the existing GUI design. So by the time I was at the checkmenu tutorial, I had incorporated the statusBar, menuBar (with both "File" and it's sub-menus & the "View" function in question), pushButtons (including "Quit"), and toolTips. It is this code where "View" runs fine from Spyder (as defined by you) but exits when I choose "View" when run from the command line. So there is some weird interaction that I don't understand. Here is the code that misbehaves, do you see anything obviously wrong?

Jim

*************************************************
import sys
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QApplication, QMessageBox
from PyQt5.QtWidgets import QToolTip, QPushButton, QDesktopWidget
from PyQt5.QtWidgets import QMainWindow, QAction, QMenu
from PyQt5.QtGui import QFont, QIcon


class Example(QMainWindow):
    """
    QMainWindow is a subclass of QWidget and inherits all of its methods, but
    also has some of its own, like the status bar. The QWidget is the base
    class for all user interface objects in PyQt5. The default constructor has
    no parent; a widget with no parent is called a window.
    """

    def __init__(self): # Constructor method for "Example" class.
        super().__init__() # Inherited QWidget constructor.

        self.title = 'PyQt5 EXAMPLE MAIN WINDOW; STATUS & MENU BARS; TITLE w/'\
                     ' ICON; QUIT PUSHBUTTON, & TOOLTIPS'
        self.width = 800
        self.height = 400

        self.initUI()

    def initUI(self): # Creating the GUI.

        # QMainWindow ATTRIBUTES:
        QToolTip.setFont(QFont('SansSerif', 10)) # Static method.
        self.setToolTip('This is a <b>QWidget</b> widget') # Rich text (bold).

        self.resize(self.width, self.height)
        # Code that will center the window is placed in the custom "center()"
        # method below.
        self.center()

        # Methods below inherited from QWidget class:
        self.setWindowTitle(self.title)
        self.setWindowIcon(QIcon('icons8_List_48px.png')) # QIcon object.

        # Method below inherited (uniquely) from QMainWindow class. First call
        # creates status bar, subsequent calls return the statusBar object.
        self.statusBar().showMessage('Ready')


        # MENUBAR:
        menubar = self.menuBar()
        fileMenu = menubar.addMenu('File')
        viewMenu = menubar.addMenu('View') # Doesn't work properly.

        newAct = QAction('New', self)

        impMenu = QMenu('Import', self)
        impAct = QAction('Import mail', self)
        impMenu.addAction(impAct)

        exitAct = QAction(QIcon('exit.png'), 'Exit', self)
        exitAct.setShortcut('Ctrl+Q')
        exitAct.setStatusTip('Exit application')
        exitAct.triggered.connect(self.close)

        fileMenu.addAction(newAct)
        fileMenu.addMenu(impMenu)
        fileMenu.addAction(exitAct)

        viewStatAct = QAction('View statusbar', self, checkable=True)
        viewStatAct.setStatusTip('View statusbar')
        viewStatAct.setChecked(True)
        viewStatAct.triggered.connect(self.toggleMenu)

        viewMenu.addAction(viewStatAct)


        # BUTTONS:
        btn = QPushButton('Button', self)
        btn.setToolTip('This is a <b>QPushButton</b> widget')
        btn.resize(btn.sizeHint())
        btn.move(150, 50)


        qbtn = QPushButton('Quit', self)
        qbtn.setToolTip('<b>QPushButton</b> widget to quit the application')
#        qbtn.clicked.connect(QCoreApplication.instance().quit)
        qbtn.clicked.connect(self.close)
        qbtn.resize(qbtn.sizeHint())
        qbtn.move(150, 80)


    def toggleMenu(self, state):
        if state:
            self.statusBar.showMessage()
        else:
            self.statusBar.clearMessage()


    def center(self):
        # QDesktopWidget class provides information about the user's desktop,
        # including screen size. This allows Qt to center the window on the
        # user's desktop.
        # Rectangle specifying geometry of main window, including any window
        # frame:
        qr = self.frameGeometry()
        # Figure out screen resolution for monitor and get center point:
        cp = QDesktopWidget().availableGeometry().center()
        # Given the width & height of the rectangle, set center of rectangle
        # to center of the screen:
        qr.moveCenter(cp)
        # Move top-left point of application window to the top-left point of
        # the qr rectangle, thus centering the window on the screen:
        self.move(qr.topLeft())


    def closeEvent(self, event):
        """
        If a QWidget is closed via the "X" button in the upper-right corner,
        the QCloseEvent is generated. To modify the widget behavior, the
        closeEvent() event handler needs to be reimplemented.
        """
        reply = QMessageBox.question(self, 'Message',
                "Are you sure you want to quit?", QMessageBox.Yes |
                QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()




def main():
    """
    SOURCE:
    http://pyqt.sourceforge.net/Docs/PyQt5/gotchas.html#crashes-on-exit

    Creating the "main" function to contain the event loop and call the GUI
    Example class changes the garbage collection order (module destructors vs.
    the actual Qapplication destruction). This keeps the GUI from hanging
    and killing the kernel. But the VIEW menubar fucntionality still does not
    work properly...

    """
    global app

    # The "if app is None" logic below keeps kernal from dying every other time.
    # https://stackoverflow.com/questions/40094086/python-kernel-dies-for-second-run-of-pyqt5-gui
    # Qt does not like more than one QApplication object in the same process.
    # The "instance()" code gets the application instance if it was already
    # created.
    app = QCoreApplication.instance()
    if app is None:
        app = QApplication(sys.argv) # QApp requires the sys arg list.

    ex = Example()
    ex.show() # Widget first created in memory; "show()" required for display.

    app.exec_()



if __name__ == "__main__":
    main()


*****************************************************

Konstantin Krayn

unread,
Aug 27, 2017, 4:13:36 PM8/27/17
to spyd...@googlegroups.com
Jim,

I have checked your "cumulative" tutorial code on my computer, and I think I know where the problem is. I was able to replicate the described misbehavior and then to make a few corrections that restore nominal functionality of the program both in Spyder and when run from the command line.

The key observation is that your code does not work properly in Spyder either: run it and look at the effect of checking/unchecking the toggle menu item to make sure that there is none.

I think there are two issues with your code that are responsible for this misbehavior, both in the definition of the toggle event handler. First, you omitted parentheses in your calls to self.menuBar() method that returns the app's menu bar object (if it has already been created, which is the case here). Your calls self.statusBar.showMessage() and self.statusBar.clearMessage() might do something weird and are unlikely to have the intended effect. Second, you use methods showMessage() and clearMessage() of the statusBar() object instead of show() and hide() methods recommended by the tutorial. Adding parentheses and restoring the tutorial-recommended methods - self.statusBar().show() and self.statusBar().hide() - sufficed to make the program behave properly both in Spyder and when run from the command line on my computer.

In fact, I would think of doing this check menu item exercise more along line with the tutorial because their way looks more transparent to me. They introduce self.statusbar reference to the self.statusBar() object upon its creation (note 'b'/'B' alternation in the two identifiers), and then operate with this reference, which eliminates subsequent calls to statusBar() method and makes the code overall clearer and safer.

So, I think here are a couple of better changes to your code that have the program work properly:

# constructor, replaces self.statusBar().showMessage('Ready')
self.statusbar = self.statusBar()      #creates self.statusbar reference to fresh new self.statusBar() object
self.statusbar.showMessage('Ready')

# toggle handler definition, replaces your initial code
if state:
            self.statusbar.show()
   else:
            self.statusbar.hide()

The bottom line is that Spyder appears to be innocent with respect to this issue. :)
 
Konstantin

On Sun, Aug 27, 2017 at 6:43 PM, jimmott via spyder <spyd...@googlegroups.com> wrote:
 
[...] 
    def toggleMenu(self, state):

jim...@yahoo.com

unread,
Aug 27, 2017, 7:33:46 PM8/27/17
to spyder
Thank you, Konstantin!

As you point out, I thoroughly confused the "statusbar" instance with the statusBar object class itself. Once I implemented the changes you specified (more in line with the way they were in the tutorial in the first place), everything worked correctly, both in Spyder and from the command line.

I agree with you that the statusbar instance (or any of the Qt widgets) should be created as a separate step before using any of their methods. I don't know what my very initial error was, but clearly each change I made to try to get the "View" function to work, the more I dug myself into a hole.

Thanks for your explanations, I feel like I understand Python classes and Qt5 that little bit much more!

Jim
Reply all
Reply to author
Forward
0 new messages