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