The module control_dialog.py provides a simplified interface to Tkinter dialogs. It is the first layer in a series of layers over Tkinter that try to separate the mechanics of the GUI from the functionality of the program. It is first, both in it's level above Tkinter and the first to be written.
The aim of control_dialog.py is to allow the programmer to create a dialog to present information and accept directions from the user without worrying about how this is done. The type of individual controls used to present the information and accept directions are selected based on the variable types and limits passed to the control_dialog constructor. There are only two formatting flags available and optional.
Formatting is not the priority and if this is required then the programmer would be better using Tkinter directly, using Tix (Tkinter extension library) or using another high level gui engine with python bindings such as Qt, Wx or Gtk.
The choice of Tkinter is for it's light weight, portability, that it ships with current versions of python and that it is not likely to change (??). For the same reasons I have made no use of the many tkinter extensions available - to do so would require a completely different philosophy, but I'm not saying that a different version of control_dialog which allowed for extensions wouldn't be a good idea - I just can't get my head around it.
Despite what is said on some forums, Tkinter works quite well in conjunction with pygame. The module, ControlDialog, and it's children, are designed specifically to work with pygame as I will show in Pygame and Tkinter
The module control_dialog.py is the simplest layer above Tkinter and is currently the layer best suited for real time applications. For debugging of OOP applications, the auto_dialog module (built on control_dialog) might be worth a look. For data editing applications, the modules tkfront(for menu management) and managed_dialog (built on control_dialog) are designed to simplify menu and dialog creation. All can and are being used in conjunction with pygame.
This is one of the three layers which can be called from a menu item using the tkfront module.
from control_dialog import *
def test_press():
print "button pressed"
def test_callback(dialog):
print "Test callback:",dialog.variables
print "Radio value:",dialog.getValue("Test radio")
def main():
root = Tk()
int_val = 1
float_val = 2.34574563
min_val = 1.3
max_val = 3.4
bool_val = True
str_val = "Hello"
selector_options = ("Aust","B","C")
selector_val = "B"
list_val = [False,True,True]
radio_val = 0
var_list = (("Just some text>>>",),
("Test button",None,test_press),
("Test Float:",float_val,None,max_val,min_val,3),
("Test Bool:",bool_val),
("Test String:",str_val),
(("Test selector box+-",)+selector_options,selector_val),
(("Test list>>>","Sydney","Melbourne","Adelaide"),list_val,2,1),
(("Test radio","One","Two","3"),radio_val),
("Test Int:",int_val))
status_line = "This is the status line"
dialog = ControlDialog(root,var_list,"Control Dialog Test",test_callback,status_line,False)
dialog.update()
dialog.lift()
frame = 0
while not dialog.exit_request:
dialog.update()
sys.stdout.flush()
dialog.setStatusLine('%10d' % frame)
frame += 1
dialog.destroy() # don't need to do this here but it is good practice
if __name__ == '__main__': main()
Running this code creates a blank parent window plus the following dialog:
This instructions to create the dialog are contained in var_list. Each entry describes a different field to place in the dialog:
Additional information is supplied to the dialog with the constructor call:
dialog = ControlDialog(root,var_list,"Control Dialog Test",test_callback,status_line,False)Here the parent, var_list, title, apply_callback and status line are added. In the above example the status line is changed regularly to the current frame with:dialog.setStatusLine('%10d' % frame)
An apply callback is used when the APPLY button is pressed to access the values set. Alternatively a callback can be supplied to each of the controls as was necessary with the Test button.
If a maximum and/or minimum value are supplied for float and integer fields, then entering values outside of the prescribed range will cause a message box to appear.
| Note: The selector box is not a regular feature of Tkinter. The version of Tkinter I was using had no drop down list, combo box or scroll wheel, so I came up with the selector box. The selector box created by control_dialog is actually just a text with a white background and raised border. The left and right mouse buttons are used to scroll the selector box options - this also works with integer fields. The hidden '+-' suffix for the label that is used to create the selector box gives an indication of it's purpose but at present the appearance on the dialog is not intuitive to it's function. |
In this example a callback function has been supplied for each of the fields so that the dialog may have an immediate effect.
def main():
def test_press():
print "button pressed"
class test_class:
def __init__(self):
pass
def test_press2(value,parent,stage=0):
print "button pressed:",value,parent,stage
return False # possibly required for stage 1 validation
# value callbacks - may be called immediatly on change (stage = 0), at validation (state = 1) or
# if and only if a change occured from the orininal value at apply stage (return key or apply button)
def int_fn(name="",value = None, stage = 0):
if stage == 1:
return False
print "int is ", value," at stage:", stage
def bool_fn(name="",value = None, stage = 0):
print "bool is ", value, " at stage:",stage
def selector_fn(name="",value = None, stage = 0):
if stage == 1:
return False
print "selector is ", value," at stage:", stage
def list_fn(name="",value = None, stage = 0):
if stage == 1:
return False
print "list is ", value," at stage:", stage
def radio_fn(name="",value = None, stage = 0):
if stage == 1:
return False
print "radio is ", value," at stage:", stage
import sys
root = Tk()
int_val = 1
float_val = 2.34574563
min_val = 1.3
max_val = 3.4
bool_val = True
str_val = "Hello"
selector_options = ("Aust","B","C")
selector_val = "B"
list_val = [False,True,True]
radio_val = 0
class_instance = test_class()
var_list = (("Just some text>>>",),
("Test button",None,test_press),
("Test button2",class_instance,test_press2),
("Test Float:",float_val,None,max_val,min_val,3),
("Test Bool:",bool_val,bool_fn),
("Test String:",str_val),
(("Test selector box+-",)+selector_options,selector_val,selector_fn),
(("Test list>>>","Sydney","Melbourne","Adelaide"),list_val,list_fn),
(("Test radio","One","Two","3"),radio_val,radio_fn),
("Test Int:",int_val,int_fn))
status_line = "This is the status line"
dialog = ControlDialog(root,var_list,"Control Dialog Test",test_callback,status_line,False)
#app.mainloop()
frame = 0
while not dialog.exit_request:
dialog.update()
dialog.lift()
sys.stdout.flush()
dialog.setStatusLine('%10d' % frame)
frame += 1
dialog.destroy() # don't need to do this here but it is good practice
if __name__ == '__main__': main()
In this example callbacks have been provided for most of the fields so that changes to the dialog fields can have an immediate effect.
A class instance is also given as an initial variable and this gets turned into a button. When pressed the button callback is given the instance value and this can be used to take action on this class instance. This will be used in a higher module, auto_dialog.py, to create a dialog to edit that class instance member variables.
The variable list can be a list or tuple of the following elements:
( label, # must be unique or list/tuple
intial value, # determines field type
callback_function,
maximum_value,
minimum_value,
float_precision # how many decimals to display
)
Apart from the control dialog class constructor there are three useful functions:
The only way to access variables set through the control_dialog is through callbacks, either by the values passed by individual callbacks or by using the dialog.getValue(label) method.
This requirement to fetch the dialog values through callbacks is rather tedious, so the managed_dialog has been created to address this.
When Enter is pressed on a text field or the APPLY button is clicked, then a validation function is applied to check that all the values are within limits.
If one or more entries are found to be invalid they are highlighted in red and a message box appears listing the offenders and their crimes.
Note: This is not the regular Tkinter messagebox but rather another instance of
ControlDialog.
This has the advantage of allowing entries to be repaired while the list is still in view
and allowing the main application to continue to run unimpeded (a major flaw with regular
message boxes). The call to create this warning box is just:
|
First off grab the module control_dialog.py here or the complete tkfront suite of modules as a zip file here. Test programs used on these webpages can also be downloaded here.
Unless you are planning on making a whole suite of programs based on control_dialog, I would just copy control_dialog.py into your source directory and that's it. This has the advantage that people who use you program will not need to find a compatible version of the tkfront module (it's bound to change afterall) and all tkfront dependencies can be found in any Python that ships with Tkinter.
On the other hand, since I am developing the control_dialog and tkfront modules for a number of different applications, I keep them in a separate directory tkinter_interface and rely on a special path.py script to add this directory to my application module paths. As such you may find the following at the top of my application source code:
try: import path except: passand in the path.py script you will find:
import sys sys.path.insert(0, "../tkinter_interface")
This solves the problem in the most intelligent way possible. I did try using the PYTHONPATH environment variable but it didn't seem to work on XP at least.
To test a module, just run it as though it were an application e.g. python control_dialog.py. This applies to most modules I write - in built unit testing / scaffolding.
Documentation can be generated by running python help("control_dialog").
In case you didn't notice them, please heed the warnings on the microde page.
Copyright Robert Parker November 2009