Best Practices
After nineteen chapters of mechanisms, the discipline that separates a maintainable, responsive, accessible PyGTK application from a tangle of ad-hoc widget code is a set of rules about where the logic lives, how the main loop is treated, and how the UI and the behavior are separated. The practices are not new ideas - they are the decision criteria for the tools the tutorial built: the main loop is the owner, the widgets are the view, the signals are the behavior, and the accessibility and the theming are the interface to the desktop and to the assistive technologies. This chapter collects the rules, each tied to the chapter that introduced the mechanism it governs, because the value of a practice is the mechanism that makes it possible and the failure it prevents.
Rule 1: The Main Loop Is the Owner - Never Block It
The main loop (gtk.main()) is the process's owner: it dispatches the events, it renders the widgets, and it is the thread the widgets belong to (the multithreading chapter's rule). The practice is the one that follows: nothing the app does may block the loop for longer than a frame - the long work goes to a worker thread (the threading.Thread, the idle_add marshal), the file I/O and the network calls are off the loop, and the UI update is the posted function on the main thread. The failures the rule prevents are the ones the user sees: the frozen window (the loop blocked by the synchronous work), the unresponsive buttons (the event queue backing up behind the block), and the "the app hangs on the big file" that is the synchronous read in the handler. The practices: the worker thread for the long work, the idle_add for the marshal (the widget touched only on the main thread), the progress bar posted from the worker, and the launch control disabled on start. The loop is the owner, and the app's discipline is to keep it dispatching - the responsive UI is the loop that is never blocked, and the responsiveness is the first quality the user judges.
Rule 2: The UI Is Built Once, the Behavior Is the Signals
The structure is the separation the skeleton established: the main() (or the controller's build_ui) builds the widget tree once - the window, the containers, the widgets, the layout - and the behavior is the signal handlers, attached to the widgets, that react to the events. The practice is that the build and the behavior do not mix: the handler does not build widgets (the build is the one place), and the build does not contain the logic (the logic is the handlers, the worker functions, the model). The failures the rule prevents are the ones the maintenance meets: the widget built in the handler (the dialog constructed on every click, the state lost between constructions), the logic in the build (the if in the pack_start chain, the behavior that is not a signal), and the state scattered (the data in the widget, in the global, in the handler's closure - the three places the reader searches). The practices: the build function that returns the tree, the handlers that are the behavior (the on_action dispatcher the menus chapter used, the one place for the command logic), the state in the model or the controller (not the widget, not the global), and the main() behind the __main__ guard (the first-window chapter's skeleton) that is importable for the test. The UI is the view, the signals are the behavior, and the separation is what the refactor and the test both depend on.
Rule 3: The State Is the Model, Not the Widget
The widget is the view of the state, not the state itself: the Entry's text is a view of the app's data, the CheckButton's active flag is a view of the setting, the TreeView's model is the data the view renders. The practice is that the app's state lives in the model (the data structure, the controller's attribute, the ListStore for the table) and the widgets are synchronized to it - the set_text from the model, the store.append for the row, the set_active for the check - and the read of the state is from the model, not the widget (the get_text is the sync point, not the source). The failures the rule prevents are the ones the round-trip exposes: the state in the widget that is lost when the widget is rebuilt (the notebook's hidden page, the dialog's re-open), the read from the widget that is stale (the get_text after the model changed, the unsynced view), and the two widgets showing the same state that diverge (the entry and the label, both "showing" the name, neither the source). The practices: the model as the source, the widget as the view, the sync in one direction (the model to the widget on the change, the widget to the model on the user's edit - the activate, the toggled, the changed), and the read from the model. The state is the data, the widget is its rendering, and the separation is what makes the UI rebuildable and the state trustworthy.
Rule 4: The Interface Is the Desktop's and the AT's
The app's interface is not only the user's eyes: it is the desktop's (the theme, the icons, the platform integration the icons-themes chapter covered) and the assistive technologies' (the accessible name, the role, the keyboard path the accessibility chapter covered). The practice is that the app uses the desktop's vocabulary (the theme names for the icons, the stock or the named icons for the standard actions, the theme's styling by inheritance) and it presents its semantics to the AT (the accessible name on every control, the role on the custom widget, the keyboard path to everything, the focus drawn). The failures the rule prevents are the two interfaces the app breaks: the out-of-place app (the hardcoded colors that fight the theme, the file-path icons that do not follow the theme change, the app that looks like it was built for a different desktop) and the inaccessible app (the icon-only button with no name, the custom widget with no focus ring, the UI that is mouse-only). The practices: the named icon reference (the theme's resolution), the theme inheritance (the get_style only for the custom drawing that must match), the accessible name and description on the control the toolkit cannot name, the set_role on the custom widget, the set_can_focus and the focus draw, and the keyboard path as the test (the UI usable without the mouse). The interface is the contract with the desktop and with the AT, and the practice is to honor both - the native look and the accessible semantics are the two halves of the app's place on the desktop.
Rule 5: The Resources Are Managed - the Connections, the Threads, the Dialogs
The app's resources are the things it allocates and must release: the signal connections (the handler ids, the disconnect on teardown), the threads (the worker's stop flag, the daemon or the join), the dialogs (the destroy after the run, the self-destroy in the show handler), and the files (the open handle, the close). The practice is that every allocation has its release, and the release is structural (the try/finally, the teardown function, the response handler) not remembered (the disconnect that is sometimes called). The failures the rule prevents are the leaks the long session exposes: the handler that fires after the widget is gone (the undischonnected connection on the destroyed widget), the dialog that reappears (the run dialog not destroyed), the thread that holds the process open (the non-daemon worker the app did not join), and the file handle that is never closed. The practices: the handler id kept and the disconnect on the component's teardown, the destroy after every run, the stop flag and the daemon or the join for the worker, the with or the finally for the file, and the component's teardown function that releases its connections and its threads. The resource is the allocation, and the discipline is the matching release - the long-running app is the one whose resources are managed, not the one whose leaks accumulate.
The Review Checklist
- Is the main loop never blocked? The long work is in the worker thread, the UI update is the
idle_addon the main thread, the progress is posted, and the launch control is disabled on start - the responsive loop is the first quality. - Is the UI built once and the behavior in the signals? The build function returns the tree, the handlers are the behavior (the one dispatcher for the commands), the state is not in the build, and the
main()is importable for the test. - Is the state in the model, not the widget? The model is the source, the widget is the view, the sync is one direction (the model to the widget, the widget to the model on the user's edit), and the read is from the model - the rebuildable UI, the trustworthy state.
- Does the interface honor the desktop and the AT? The named icons (the theme's resolution), the theme inheritance (the
get_styleonly for the matching custom draw), the accessible name on every control, the role on the custom widget, and the keyboard path as the test - the native look and the accessible semantics. - Are the resources managed? The handler id and the
disconnecton teardown, thedestroyafter therun, the stop flag and the daemon or thejoinfor the worker, thewith/finallyfor the file - the allocation and its release, structural not remembered.
Summary
The practices are the decision criteria for the mechanisms, and each is a lock between two things that must not diverge. The main loop and the work are one discipline - the loop is the owner, the long work is off it, the idle_add is the marshal, and the responsive UI is the loop that is never blocked (Rule 1). The UI and the behavior are one separation - the build is the one place for the tree, the signals are the behavior, the main() is importable, and the refactor and the test depend on the split (Rule 2). The state and the widget are one model-view pair - the model is the source, the widget is the view, the sync is one direction, the read is from the model, and the UI is rebuildable and the state is trustworthy (Rule 3). The interface and the desktop and the AT are one contract - the named icons and the theme inheritance for the native look, the accessible name and the role and the keyboard path for the semantics, and the app is in its place on the desktop and usable without the mouse (Rule 4). And the resource and its release are one discipline - the connection and the disconnect, the run and the destroy, the thread and the stop flag, the file and the finally, structural not remembered (Rule 5). The checklist is the five questions that catch the divergence - the blocked loop, the mixed build and behavior, the state in the widget, the broken interface, the leaked resource - and answered together, they are what keep the PyGTK application a component the desktop and the user and the maintainer can rely on: the loop responsive, the structure separated, the state in the model, the interface native and accessible, and the resources managed.