Dialogs and Message Boxes
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. GTK's dialog family is built on one base - the gtk.Dialog, a window with a content area and an action button box at the bottom - and the convenience subclasses (the MessageDialog, the FileChooserDialog) are pre-built content over that base. The two usage patterns are the run() loop (the modal dialog that blocks until a response, the simple case) and the show() plus response signal (the non-blocking case where the app keeps running while the dialog is open). This chapter covers the MessageDialog (the alerts and the confirmations), the Dialog base (the custom content, the action buttons, the response ids), and the run versus show decision.
The MessageDialog: Alerts and Confirmations
import gtk
# the informational alert - one button (OK), non-questioning:
def show_error():
d = gtk.MessageDialog(
None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
gtk.BUTTONS_OK,
"The file could not be saved:\n%s", "disk full")
d.set_title("Save Error")
d.run() # blocks until the button is pressed
d.destroy() # the run() dialog must be destroyed
# the confirmation - the QUESTION type, the YES/NO buttons,
# and the response decides the branch:
def confirm_overwrite():
d = gtk.MessageDialog(
None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION,
gtk.BUTTONS_YES_NO,
"Replace the existing file?")
response = d.run()
d.destroy()
return response == gtk.RESPONSE_YES
# the message types: MESSAGE_INFO, MESSAGE_WARNING,
# MESSAGE_QUESTION, MESSAGE_ERROR - they set the icon and
# the tone; the button set is a separate argument
# the printf-style format: the MessageDialog formats its
# primary text with the extra arguments
The MessageDialog is the convenience for the standard messages: the constructor takes the parent, the modal flag, the message type (MESSAGE_ERROR, MESSAGE_QUESTION, the info and warning), the button set (BUTTONS_OK, BUTTONS_YES_NO, BUTTONS_OK_CANCEL), and the primary text (printf-formatted with the extra arguments). The type sets the dialog's icon and tone, the button set sets the action buttons, and the two are independent - a MESSAGE_QUESTION with BUTTONS_YES_NO is the confirmation, a MESSAGE_ERROR with BUTTONS_OK is the alert. The run() call enters the dialog's own modal loop - the app's main loop keeps running, the dialog blocks the caller until a button is pressed, and run returns the response id, which the caller compares (== gtk.RESPONSE_YES) to branch. The destroy() after the run is the required cleanup - a run dialog is not destroyed by the response, and leaking it is the "the dialog reappears" bug.
The Dialog Base: Custom Content and Responses
import gtk
# the base dialog - the frame + the action button box:
def build_settings():
dialog = gtk.Dialog("Preferences")
# the action buttons: (label, response_id)
dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
dialog.add_button("Save", gtk.RESPONSE_OK)
# the content: the dialog.vbox is the container the
# content goes into (the button box is already at the
# bottom of the dialog.vbox)
grid = gtk.Grid()
grid.set_column_spacing(8)
grid.set_row_spacing(8)
grid.set_border_width(12)
name = gtk.Entry()
grid.attach(gtk.Label("Name:"), 0, 0, 1, 1,
False, False, gtk.Align.START, gtk.Align.CENTER)
grid.attach(name, 1, 0, 1, 1, True, False,
gtk.Align.FILL, gtk.Align.CENTER)
dialog.vbox.pack_start(grid, True, True, 0)
# the response signal - the collection point:
def on_response(dialog, response):
if response == gtk.RESPONSE_OK:
apply_settings(name.get_text())
dialog.destroy()
dialog.connect("response", on_response)
dialog.show() # NON-blocking: the app keeps running
# the built-in response ids: RESPONSE_OK, CANCEL, YES, NO,
# APPLY, CLOSE - and add_button with a custom id for the
# actions that are not one of the built-ins
The gtk.Dialog is the base the conveniences are built on: a window with a vertical box whose bottom is the action button box, and the content is packed into the dialog.vbox above it. The add_button(label, response_id) adds an action button bound to a response id - the built-in ids (RESPONSE_OK, RESPONSE_CANCEL, RESPONSE_YES, RESPONSE_NO) cover the common cases, and a custom id names the action that is not one of them. The response signal fires when any action button is pressed (or the dialog is closed by the window manager, which emits RESPONSE_DELETE_EVENT), carrying the id, and it is the collection point where the dialog's content is read and acted on. The show() form (as opposed to run()) is non-blocking - the dialog appears and the app's main loop continues, and the response handler runs when the user acts, which is the pattern for a dialog that does not need to block the app (the settings that can be applied live, the "about" box the user can leave open).
run() versus show(): The Decision
import gtk
# the run() pattern - MODAL, the caller blocks:
# right for: the confirmation the next step depends on,
# the error that must be acknowledged, the file choice the
# operation needs before it can proceed
def open_file():
d = gtk.FileChooserDialog("Open", None,
gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN, gtk.RESPONSE_OK))
fn = None
if d.run() == gtk.RESPONSE_OK:
fn = d.get_filename()
d.destroy()
return fn
# the show() pattern - the app keeps running, the response
# signal is the handler:
# right for: the non-blocking "about", the live settings,
# any dialog the user may leave open while the app works
def show_about():
d = gtk.MessageDialog(None, False, gtk.MESSAGE_INFO,
gtk.BUTTONS_OK, "NotePad 1.0")
d.connect("response", lambda d, r: d.destroy())
d.show()
# the decision: does the NEXT STEP need the answer now?
# yes -> run() (the modal block, the return value)
# no -> show() + the response signal (the non-blocking)
The decision between the two patterns is 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 run() pattern is the one: the modal loop blocks the caller, the response id is the return value, the code reads it and proceeds, and the destroy follows. If no - the about box, the live settings, any dialog the user may leave open while the app continues - the show() plus response signal is the one: the dialog appears, the main loop keeps running, and the handler fires when the user acts. The run() pattern's modal flag is the default (the dialog blocks its parent's input), and the show() pattern's dialog is typically non-modal so the app stays usable. The discipline: the run dialog is always destroyed after the run returns, and the show dialog's response handler destroys itself - the two cleanups that keep the dialog from leaking.
The Working Rules
- Use the
MessageDialogfor the standard messages - the alert, the confirmation, the error - and theDialogbase only when the content is custom (the form, the multi-field settings). - Every
run()is followed by adestroy(). The response does not destroy arundialog; the leaked dialog is the one that reappears on the next call. - The
responsesignal is the collection point - the content is read and acted on there, and theshowdialog's handler destroys the dialog on the response. - run() when the next step needs the answer, show() when it does not. The modal block is for the dependency; the non-blocking show is for the dialog the user may leave open.
- The window-manager close emits
RESPONSE_DELETE_EVENT. The handler that treats onlyRESPONSE_OKas "acted" must decide what the close means (usually the same as the cancel), or the dialog's close button is the silent no-op.
Summary
The dialog family is one base with conveniences: the gtk.Dialog is the window with the content area and the action button box, the add_button(label, response_id) builds the actions, and the response signal is the collection point where the content is read and the dialog is destroyed. The MessageDialog is the pre-built standard message - the type sets the icon and tone, the button set the actions, and the printf-formatted primary text the content. The two usage patterns are decided by one question - does the next step need the answer now: the run() modal loop (the blocking caller, the response return value, the required destroy) for the yes case, the show() plus response signal (the non-blocking app, the handler that destroys the dialog) for the no case. The confirmations, the errors, the file choices, and the settings forms are all compositions of these two patterns, and the cleanup discipline - the destroy after every run, the self-destroy in every show handler - is what keeps the dialogs from leaking.