Our next GUI-programming technique is all about changing
a GUI while it is running—the ultimate in customization. The Python
reload
function lets you
dynamically change and reload a program’s modules without stopping the
program. For instance, you can bring up a text editor window to change
the source code of selected parts of a system while it is running and
see those changes show up immediately after reloading the changed
module.
This is a powerful feature, especially for developing programs
that take a long time to restart. Programs that connect to databases
or network servers, initialize large objects, or travel through a long
series of steps to retrigger a callback are prime candidates for
reload
. It can shave substantial
time from the development cycle.
The catch for GUIs, though, is that because callback handlers
are registered as object references rather than
module and object names, reloads of callback handler functions are
ineffective after the callback has been registered. The Python
reload
operation works by changing
a module object’s contents in place. Because Tkinter stores a pointer
to the registered handler object directly, though, it is oblivious to
any reloads of the module that the handler came from. That is, Tkinter
will still reference a module’s old objects even after the module is
reloaded and changed.
This is a subtle thing, but you really only need to remember that you must do something special to reload callback handler functions dynamically. Not only do you need to explicitly request reloading of the modules that you change, but you must also generally provide an indirection layer that routes callbacks from registered objects to modules so that reloads have impact.
For example, the script in Example 11-11 goes the extra mile to indirectly dispatch callbacks to functions in an explicitly reloaded module. The callback handlers registered with Tkinter are method objects that do nothing but reload and dispatch again. Because the true callback handler functions are fetched through a module object, reloading that module makes the latest versions of the functions accessible.
Example 11-11. PP3EGuiToolsReload ad.py
from Tkinter import * import actions # get initial callback handlers class Hello(Frame): def _ _init_ _(self, master=None): Frame._ _init_ _(self, master) self.pack( ) self.make_widgets( ) def make_widgets(self): Button(self, text='message1', command=self.message1).pack(side=LEFT) Button(self, text='message2', command=self.message2).pack(side=RIGHT) def message1(self): reload(actions) # need to reload actions module before calling actions.message1( ) # now new version triggered by pressing button def message2(self): reload(actions) # changes to actions.py picked up by reload actions.message2(self) # call the most recent version; pass self def method1(self): print 'exposed method...' # called from actions function Hello().mainloop( )
When run, this script makes a two-button window that triggers
the message1
and message2
methods. Example 11-12 contains the
actual callback handler code. Its functions receive a self
argument that gives access back to the
Hello
class object, as though these
were real methods. You can change this file any number of times while
the rad
script’s GUI is active;
each time you do so, you’ll change the behavior of the GUI when a
button press occurs.
Example 11-12. PP3EGuiToolsReloadactions.py
# callback handlers: reloaded each time triggered def message1( ): # change me print 'spamSpamSPAM' # could build a dialog... def message2(self): print 'Ni! Ni!' # change me self.method1( ) # access the 'Hello' instance...
Try running rad
and editing
the messages printed by actions
in
another window; you should see your new messages printed in the
stdout
console window each time the
GUI’s buttons are pressed. This example is deliberately simple to
illustrate the concept, but the actions reloaded like this in practice
might build pop-up dialogs, new top-level windows, and so on. Reloading the code that creates such windows
would also let us dynamically change their appearances.
There are other ways to change a GUI while it’s running. For
instance, we saw in Chapter 10
that appearances can be altered at any time by calling the widget
config
method, and widgets can be
added and deleted from a display dynamically with methods such as
pack_forget
and pack
(and their grid
manager relatives). Furthermore,
passing a new command=action
option
setting to a widget’s config
method
might reset a callback handler to a new action object on the fly; with
enough support code, this may be a viable alternative to the
indirection scheme used earlier to make reloads more effective in
GUIs.
3.15.140.68