Dialogs
Dialogs are the secondary windows for the interactions that are not the main window's job: the confirmation, the error report, the file open, the settings form. Qt's dialog family is built on one base - the QDialog, with its accept/reject methods, its result codes, and the exec modal loop - and the convenience classes (the QMessageBox, the QFileDialog, the QColorDialog) are the pre-built dialogs over that base. The two usage patterns are the exec (the modal dialog that blocks until a response, the simple case) and the show plus the finished signal (the modeless dialog the app keeps running beside). This chapter covers the QMessageBox (the alerts and the confirmations), the QFileDialog (the open and save), the custom QDialog (the form, the button box), and the exec versus show decision.
The QMessageBox: Alerts and Confirmations
from PyQt5.QtWidgets import (QMessageBox, QApplication)
import sys
app = QApplication(sys.argv)
# the informational alert - the one-button (the OK):
QMessageBox.information(None, "Saved",
"The document was saved.")
# the error:
QMessageBox.critical(None, "Save Error",
"The file could not be saved: disk full.")
# the question - the confirmation, the buttons, the result:
ret = QMessageBox.question(None, "Overwrite",
"Replace the existing file?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if ret == QMessageBox.Yes:
# do the overwrite
pass
# the buttons and the default:
# information / warning / critical / question - the icons
# the button set (the Yes|No, the Ok|Cancel)
# the default button (the Enter's, the last arg)
# the static methods (the information, the question) are the
# convenience; the QMessageBox(parent) + the setIcon +
# the setStandardButtons is the constructed form
The QMessageBox is the convenience for the standard messages: the static methods (information, warning, critical, question) take the parent, the title, the text, and (for the question) the button set and the default button, and they show the dialog modally, returning the pressed button's standard button. The button set is the QMessageBox.Yes | QMessageBox.No (the Ok | Cancel the alternative), and the return is compared (== QMessageBox.Yes) to branch. The constructed form (QMessageBox(parent) with the setIcon, the setStandardButtons, the setDefaultButton) is for the dialog that needs the non-static control (the modeless, the custom buttons). The message is the pre-built standard - the icon, the buttons, the modal exec under the static method - and the parent argument (the None for the top-level, the window for the centered-on-parent) is the dialog's position. The discipline is the one the other toolkits' dialog chapters share: the standard message is the convenience class, and the custom content is the base dialog the next section builds.
The QFileDialog: Open and Save
from PyQt5.QtWidgets import QFileDialog, QApplication
import sys
app = QApplication(sys.argv)
# the open: the static getOpenFileName (the modal, the block)
# the args: the parent, the caption, the directory, the
# filter, the selectedFilter (out), the options
path, _filter = QFileDialog.getOpenFileName(
None, "Open File", "/home/user/documents",
"Text files (*.txt *.md);;All files (*)")
if path:
# open the file
pass
# the save: the getSaveFileName (the suggested name):
path, _ = QFileDialog.getSaveFileName(
None, "Save File", "/home/user/untitled.txt",
"Text files (*.txt)")
# the multiple: the getOpenFileNames (the list of paths)
# the folder: the getExistingDirectory (the dir chooser)
# the FILTER string: the "Name (glob glob);;Name2 (...)" -
# the ;; is the separator, the glob is the pattern
# the filter is the file-type restriction the dialog shows
# the constructed form (the non-static, the modeless, the
# stateful): the QFileDialog with the setFileFilter, the
# setDirectory, the setAcceptMode, the exec
dialog = QFileDialog(None, "Open")
dialog.setFileFilter("Python (*.py)")
dialog.setDirectory("/home/user")
if dialog.exec_() == QFileDialog.Accepted:
selected = dialog.selectedFiles()
# the selectedFiles is the list (the multiple)
# the selectedFile is the first (the single)
The QFileDialog is the file open and save, in the static and the constructed forms. The static getOpenFileName (and the getSaveFileName, the getOpenFileNames for the multiple, the getExistingDirectory for the folder) takes the parent, the caption, the starting directory, and the filter string, and returns the selected path (the empty on the cancel) - the modal, the blocking, the one-call form. The filter string is the "Name (glob glob);;Name2 (...)" (the ;; the separator, the glob the pattern) - the file-type restriction the dialog shows. The constructed QFileDialog is the stateful, modeless-capable form: the setFileFilter, the setDirectory, the setAcceptMode, the exec_ for the modal or the show for the modeless, and the selectedFiles/selectedFile for the result. The platform note: the Qt uses the native file dialog where the platform provides one (the macOS's, the Windows's, the GTK's on Linux), which is the native look the cross-platform app gets for free - the QFileDialog.DontUseNativeDialog is the opt-out to the Qt's own. The open and the save are the two flows, and the filter plus the starting directory are the two settings that make them useful.
The Custom QDialog: The Form and the Button Box
from PyQt5.QtWidgets import (QDialog, QDialogButtonBox,
QFormLayout, QLineEdit, QSpinBox,
QApplication)
import sys
app = QApplication(sys.argv)
# the custom dialog: the QDialog + the form + the button box
class SettingsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Settings")
self.name = QLineEdit()
self.indent = QSpinBox()
self.indent.setRange(1, 12)
self.indent.setValue(4)
form = QFormLayout(self)
form.addRow("Name:", self.name)
form.addRow("Indent:", self.indent)
# the button box: the standard OK/Cancel, wired to
# the dialog's accept / reject (the result codes)
buttons = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
form.addRow(buttons)
# the accept: the setResult(Accepted) + the close
# the reject: the setResult(Rejected) + the close
# the exec returns the result (the Accepted / Rejected)
dialog = SettingsDialog()
if dialog.exec_() == QDialog.Accepted:
print(dialog.name.text(), dialog.indent.value())
# the REJECTED is the cancel / the close (the window X)
# the dialog.result() is the code (the accepted / the
# rejected) the exec returns and the finished carries
exec versus show: The Decision
from PyQt5.QtWidgets import (QDialog, QApplication, QLabel,
QVBoxLayout, QPushButton)
import sys
app = QApplication(sys.argv)
# the exec: the MODAL loop - the dialog blocks the caller,
# the app's loop keeps running (the dialog's own event
# processing), the exec returns the result
# right for: the confirmation the next step depends on,
# the error that must be acknowledged, the file choice the
# operation needs before it proceeds
def open_file():
from PyQt5.QtWidgets import QFileDialog
path, _ = QFileDialog.getOpenFileName(None, "Open")
return path or None # the None on the cancel
# the show: the MODELESS - the dialog appears, the app's
# loop continues, the finished signal is the handler
# right for: the "about" the user may leave open, the live
# settings, the secondary window that is a dialog in kind
def show_about():
d = QDialog()
d.setWindowTitle("About")
lay = QVBoxLayout(d)
lay.addWidget(QLabel("NotePad 1.0"))
d.finished.connect(lambda r: d.deleteLater())
d.show()
# the decision: does the NEXT STEP need the answer now?
# yes -> the exec (the modal block, the return value)
# no -> the show + the finished signal (the modeless)
# the modal flag: the setModal(True/False) - the exec is
# implicit modal; the show + the setModal(True) is the
# explicit modal the app controls (the modal while the app
# runs, the non-blocking)
d.setModal(True) # the modal, the show still non-blocking
# for the CALLER (the loop dispatches)
# the close (the window X): the reject - the result is the
# Rejected, the finished fires with it (the dialog's default
# close behavior is the reject)
The decision between the two patterns is the one question: does the next step need the dialog's answer now? If yes - the confirmation the overwrite depends on, the file the open operation needs, the error that must be acknowledged - the exec is the one: the modal loop blocks the caller (the app's event loop keeps dispatching, the dialog's own events processed), the exec returns the result (the Accepted/Rejected), the code reads it and proceeds. If no - the about box, the live settings, any dialog the user may leave open while the app continues - the show plus the finished signal is the one: the dialog appears, the loop continues, and the finished handler (carrying the result) fires when the dialog closes. The setModal(True) is the explicit modal (the modal while the app runs, the caller not blocked by the show), and the window's close (the X) is the reject (the result the Rejected, the finished fired with it). The discipline: the exec is the block-and-return for the dependency, the show is the open-and-continue for the independent, and the finished handler is where the modeless dialog's cleanup (the deleteLater) lives - the two patterns and their cleanups, the same decision the other toolkits' dialog chapters make.
The Working Rules
- Use the convenience class for the standard dialog. The
QMessageBoxfor the alert and the confirmation, theQFileDialogfor the open and save, theQColorDialog/QFontDialogfor the pickers - theQDialogbase only when the content is custom (the form, the multi-field settings). - The
execis the modal block, theshowis the modeless. Theexecreturns the result (theAccepted/Rejected) the next step needs; theshowplus thefinishedsignal is the non-blocking, the dialog the user may leave open. - The button box is wired to the
accept/reject. TheQDialogButtonBoxstandard buttons, theaccepted/rejectedconnected to the dialog'saccept/reject- the result codes theexecreturns, the standard response the platform's native buttons. - The window's close is the
reject. The X button's default is thereject(theRejectedresult, thefinishedfired) - the handler that treats only theAcceptedas "acted" must decide what the close means (usually the cancel), or the close is the silent no-op. - The parent argument centers the dialog. The
QMessageBoxand theQFileDialogtake the parent (theNonefor the top-level, the window for the centered-on-parent) - the dialog positioned on its parent, the standard the user expects.
Summary
The dialog family is one base with conveniences: the QDialog with its accept/reject (the result codes), the exec modal loop, and the finished signal, and the QDialogButtonBox wired to the accept/reject as the standard response. The QMessageBox is the pre-built standard message (the information/question statics, the button set, the default, the return compared to branch), the QFileDialog is the open and save (the getOpenFileName/getSaveFileName statics, the filter string, the native dialog where the platform provides one, the constructed form for the stateful), and the custom QDialog is the form (the QFormLayout fields, the button box, the exec returning the result). The decision is the one the other toolkits make: the exec when the next step needs the answer (the modal block, the return value), the show plus the finished when it does not (the modeless, the handler that cleans up), the setModal(True) the explicit modal, and the window's close the reject - the two patterns and their cleanups, the dialog's whole interaction in the flow.