Your First Window
The first window is the three-part shape made concrete: the QApplication, the window widget, the signal/slot wiring for the close, and the exec loop. The window is the QMainWindow (the framed application window with the menu bar, the toolbar, and the status bar as its standard parts) or the QWidget (the plain window), and the two calls that complete the program are show() (making the window visible) and app.exec() (entering the loop that makes it live). This chapter builds both window types, walks the window's properties (the title, the size, the window flags, the icon), the close behavior (the closeEvent, the QApplication.quit), and the minimal complete program - the skeleton every later chapter's app is built on.
The QMainWindow: The Application Frame
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow,
QLabel, QAction)
# the QMainWindow - the framed application window:
# the central widget + the menu bar + the toolbars +
# the status bar, as its standard parts
window = QMainWindow()
window.setWindowTitle("NotePad")
window.resize(640, 480)
window.setWindowIcon(...) # the QIcon (the resource, later)
# the central widget - the main content (ONE central widget):
window.setCentralWidget(QLabel("The editor goes here"))
# the status bar - the message line at the bottom:
window.statusBar().showMessage("Ready", 3000)
# the menu bar - the standard part (the menus chapter):
action = QAction("Quit", window)
action.setShortcut("Ctrl+Q")
menu = window.menuBar().addMenu("&File")
menu.addAction(action)
action.triggered.connect(window.close)
window.show() # visible; the loop is the next step
The QMainWindow is the application frame - the window with the standard parts: the central widget (the main content, set with setCentralWidget, one per window), the menu bar (menuBar(), the addMenu for the menus), the toolbars (addToolBar), the dock widgets (addDockWidget), and the status bar (statusBar(), the showMessage for the transient message). The frame properties are set on the window: the setWindowTitle, the resize (the initial size), the setWindowIcon (the QIcon, the resource chapter's subject), and the window flags (the setWindowFlag for the frameless, the tool window, the Qt.WindowStaysOnTopHint). The key state to internalize is the same as the other toolkits: the window is invisible until show(), so the whole tree is built and wired before the single show. The QMainWindow is the choice for the application window (the menu, the toolbar, the status bar are its parts), and the QWidget is the choice for the plain window and for every child - the two window types are the skeleton's options, and the running example uses the QMainWindow because the editor has the menu and the status bar.
The Plain QWidget: The Dialog and the Child
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel,
QVBoxLayout, QPushButton)
# the plain QWidget - the window WITHOUT the frame parts:
# (no menu bar, no status bar) - the dialog, the utility
window = QWidget()
window.setWindowTitle("About")
window.setFixedSize(320, 120) # the non-resizable
# the layout: the plain window NEEDS a layout for its
# children (the QMainWindow's central widget does too)
lay = QVBoxLayout(window)
lay.addWidget(QLabel("NotePad 1.0\nThe PyQt example app."))
btn = QPushButton("Close")
lay.addWidget(btn)
btn.clicked.connect(window.close)
window.show()
# the QWidget is ALSO the child widget - every non-window
# widget is a QWidget (the QLabel, the QPushButton above
# are QWidgets used as children); the window is the QWidget
# with the window flags (the default for a top-level)
# the setFixedSize: the width + the height locked (the
# dialog's non-resizable); the setMinimumSize / the
# setMaximumSize are the bounds the layout respects
The plain QWidget is the window without the frame parts - no menu bar, no status bar - and it is the choice for the dialog and the utility window, and it is also the base of every child widget (the QLabel, the QPushButton are QWidget subclasses used as children). The plain window needs a layout for its children (the QVBoxLayout on the window, the addWidget for each), and the setFixedSize locks the dimensions (the non-resizable dialog), with the setMinimumSize/setMaximumSize as the bounds the layout respects. The QMainWindow versus the QWidget decision is the one to hold: the application window with the menu, the toolbar, and the status bar is the QMainWindow, and the plain window (the dialog, the about box, the utility) is the QWidget - the frame parts are what distinguish them, and the running example's main window is the QMainWindow for exactly those parts.
The Close Behavior
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QMessageBox
class Editor(QMainWindow):
def closeEvent(self, event):
# the closeEvent: the window's close (the X button,
# the Ctrl+Q, the window.close()) - the handler that
# decides the close, with the accept / the ignore:
ret = QMessageBox.question(
self, "Quit", "Save the changes?")
if ret == QMessageBox.Yes:
# save_document()
event.accept() # proceed with the close
else:
event.ignore() # cancel: the window stays
# the default (no handler): the close proceeds, and
# if it is the last window, the app quits (the
# quitOnLastWindowClosed, True by default)
app = QApplication(sys.argv)
ed = Editor()
ed.show()
sys.exit(app.exec())
The close behavior is the closeEvent - the handler the window's close (the X button, the shortcut, the close() call) triggers, with the event.accept() (proceed with the close) and the event.ignore() (cancel, the window stays). The pattern is the save-prompt: the QMessageBox.question asks, and the answer decides the accept/ignore - the close that saves, the close that cancels. The default (no closeEvent override) is the proceed, and the application-level behavior is the quitOnLastWindowClosed (True by default - the app quits when the last window closes, the setQuitOnLastWindowClosed(False) for the system-tray app that lives without a window). The discipline is the one the chapter drills: the closeEvent is the place the close is decided, the accept/ignore is the mechanism, and the quitOnLastWindowClosed is the application's exit policy - the three that make the close behavior complete and deliberate instead of the default the app did not think about.
The Minimal Complete Program
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow,
QLabel, QAction)
# the minimal complete PyQt program - every later chapter's
# app is this skeleton with the content and the behavior
# filled in:
def main():
app = QApplication(sys.argv)
window = QMainWindow()
window.setWindowTitle("NotePad")
window.resize(640, 480)
# the content: the placeholder the later chapters replace
# with the menu actions, the toolbar, and the text edit
window.setCentralWidget(QLabel("The editor goes here"))
# the behavior: the Quit action (the signal/slot)
action = QAction("Quit", window)
action.setShortcut("Ctrl+Q")
window.addAction(action)
action.triggered.connect(window.close)
window.statusBar().showMessage("Ready")
window.show()
return app.exec()
if __name__ == "__main__":
sys.exit(main())
The minimal complete program is the skeleton: the main() that builds the QApplication, the QMainWindow with its title and size, the placeholder central widget, the Quit action with its shortcut and its triggered slot, the status bar message, the show, and the app.exec() wrapped in the sys.exit (the exit code). The if __name__ == "__main__" guard makes it runnable as a script and importable as a module (the testing chapter imports main or the Editor class without entering the loop). The placeholder is deliberate - the structure (the app, the window, the content, the action, the loop) is complete even though the content is a stub, and each later chapter replaces the stub: the layouts chapter builds the real content, the signals chapter wires the behavior, the model-view chapter fills the center with the editor. The skeleton is the contract - any PyQt app, from a dialog to a full editor, is this shape with more in the content and more signals connected.
The Working Rules
- One
QApplicationper process, built before any widget. The app object is the process's owner (the event loop, the style, the platform plugin), and the secondQApplicationis an error - it is the first line ofmain, before the window. - The
QMainWindowfor the app frame, theQWidgetfor the plain window and the child. The menu bar, the toolbar, and the status bar are theQMainWindow's parts; the dialog and the utility are the plainQWidget, and every child widget is aQWidget. - Build the whole tree, then
show()once. The window is invisible until shown; the hierarchy is assembled and wired before the singleshow, and theexecfollows. - The
closeEventis the close decision. Theaccept/ignoreis the mechanism, the save-prompt is the pattern, and thequitOnLastWindowClosedis the application's exit policy - the close is deliberate, not the default. - Wrap the
execin thesys.exit. Theapp.exec()returns the exit code, and thesys.exit(main())is the process's exit with that code - the clean termination the script and the frozen app both need.
Summary
The first window is the three-part shape made concrete: the QApplication(sys.argv) (one per process, built first), the window - the QMainWindow with its frame parts (the central widget, the menu bar, the status bar) for the application frame, or the plain QWidget (with its layout) for the dialog and the utility - the signal/slot wiring (the QAction with its shortcut, the triggered connected to the close), and the show() plus the sys.exit(app.exec()) that make the window visible and the loop live. The frame properties are the title, the size, the icon, the window flags; the close behavior is the closeEvent with its accept/ignore (the save-prompt the pattern) and the quitOnLastWindowClosed policy; and the minimal complete program - the main() behind the __main__ guard with the placeholder central widget - is the skeleton every later chapter fills. The rules are the discipline: the one QApplication first, the window type matched to the frame, the show-once, the deliberate close, and the exec in the sys.exit.