TkFRONT - A layer for simplified Tkinter menu application

TkFront uses a similar strategy to control_dialog.py to simplify menu creation and use.

There are three dialog creation layers which can be called from a tkfront menu item:


Contents


A Simple Example

import tkfront
import sys   # for exit and arg
from copy import *

fid = "filename"

def open_callback():
  global fid
  fid = tkfront.open_dialog("python modules","*.py")
  print "Opening:",fid

def save_callback():
  global fid
  print "Save file:",fid

def saveas_callback():
  global filename
  fid = tkfront.save_dialog("html files","*.html","tkfront.html")
  print "Save as:",fid


def about_callback():
  about_text = "TkFront Test Program by Robert Parker (c)2009"
  tkfront.about_dialog(about_text)
  
def tk_key_event(event):
  ''' transfer key event to other windows if necessary'''
  print "Key event code:",event.keycode

Done = False

def program_destroy_callback():
  global Done
  Done = True

def main():

  menu_design = (("File",
                    (("Open road",open_callback),
                     ("Save road",save_callback),
                     ("SaveAs road",saveas_callback),
                     ("Seperator",),
                     ("Exit",tkfront.quit_callback))
                 ),
                 ("Help",
                    (("About",about_callback),
                    )
                 )
                )

  control_design = ("Just some text to show\n that it can be done.",
                    ("About>>>",about_callback),"is the same as the menu item",
                    ("QUIT",tkfront.quit_callback,"red")
)

  tkfront.parent_destroy_callback = program_destroy_callback
  tkfront.make_gui(menu_design,tk_key_event,control_design,"")

  while not Done:
    tkfront.update("Status Line")
    sys.stdout.flush()

if __name__ == '__main__': main()

Running this will give you a simple window:

The callbacks for items in the menu should be familiar to any users of gui system and there is no difference here to a normal Tkinter application. It is how the menu is created in the main() function that simplifies the code. A simple nested tuple called menu design contains all the information required.

Text and buttons that appear on the window are created using a tuple called control_design. Only text and buttons can appear on the main window. Any complex editing of application variables is left to popup dialogs. Keeping it simple is the main criteria here, so buttons cannot even have images or bitmaps. For a fancy gui, either use raw Tkinter or move to a more advanced gui such as gtk/gdk, qt or wx.

The tkfront method make_gui is a wrapper around another method called control_gui. make_gui(menu_design_list,key_event_callback) takes care of Tkinter intialisation and keeps track of the parent window. It's call could be replaced by the following code:


    root = Tkinter.Tk()
    root.protocol("WM_DELETE_WINDOW", quit_callback)
    main_dialog = control_gui(root,menu_design,contol_design)
    main_dialog.pack()
    tkfront.main_dialog = main_dialog

The tkfront method update() updates both the main Tkinter window and any child popup dialogs.


A More Complex Example

In this example a the menu has items with callbacks that open_item_dialog() and open_properties_dialog() to bring up managed and auto dialogs respectively. For more details on these see managed_dialog and auto_dialog but the reason for showing them here is to demonstrate the creat_popup_*() function in the tkfront arsenal.

Certainly more complex but it demonstrates how it all comes together.

Here is the code:

import tkfront
import sys   # for exit and arg
from copy import *

fid = "filename"

def open_callback():
  global fid
  fid = tkfront.open_dialog("python modules","*.py")
  print "Opening:",fid

def save_callback():
  global fid
  print "Save file:",fid

def saveas_callback():
  global filename
  fid = tkfront.save_dialog("html files","*.html","tkfront.html")
  print "Save as:",fid

class testClass:
  int_value = 3
  def __init__(self,name):
    self.name = name
    self.float_value = 2.3

# make some data to play with
test_items = []
for i in range(10):
  test_items.append(testClass("testClass#"+str(i)))
current_itemi = 0

def refreshDialog(d = None):
  global current_item
  # auto dialog replies with self for apply - but we want the other dialog if it exists
  d = tkfront.get_popup_dialog("item_dialog")
  if d != None:
    new_item = copy(test_items[current_itemi])
    d[0].setObj(current_item,new_item)
    current_item = new_item

def dialogChange(d):
  print "There was a change in the dialog"
  d.change()
  print "Current float:",current_item.float_value
  test_items[current_itemi] = current_item

def previous_item_callback():
  changeItem(-1)

def next_item_callback():
  changeItem(1)

def changeItem(inc = 1):
  global current_itemi
  current_itemi += inc
  if current_itemi < 0:
    current_itemi = 0
  elif current_itemi >= len(test_items):
    current_itemi = len(test_items) - 1
  print "Moving dialog to item#",current_itemi
  refreshDialog()

def open_item_dialog():
  global current_item
  # list the fields that are required as used by control_dialog.py
  # (label,initial_value,callback_function,max_value,min_value,float_precision)
  current_item = copy(test_items[current_itemi])
  dialog_array = (("Name",(current_item,'name')),
                  ("Integer",(current_item,'int_value'),None,10,0),
                  ("Float",(current_item,'float_value'),None,5,-5,3),
                  ("Item->>>",None,previous_item_callback),
                  ("Item+>>>",None,next_item_callback))
  tkfront.create_popup_managed_dialog("item_dialog",dialog_array,"test item custom edit",dialogChange)

def open_properties_dialog():
  tkfront.create_popup_auto_dialog("properties_dialog",test_items,None,refreshDialog)

def about_callback():
  about_text = "TkFront Test Program by Robert Parker (c)2009"
  tkfront.about_dialog(about_text)
  
def error_callback():
  # create a non-blocking error
  tkfront.error_dialog("TEST",False)

def tk_key_event(event):
  ''' transfer key event to other windows if necessary'''
  print "Key event code:",event.keycode

Done = False

def program_destroy_callback():
  global Done
  Done = True

def main():

  menu_design = (("File",
                    (("Open",open_callback),
                     ("Save",save_callback),
                     ("SaveAs",saveas_callback),
                     ("Seperator",),
                     ("Exit",tkfront.quit_callback))
                 ),
                 ("Dialogs",
                    (("Item",open_item_dialog,None,"item_dialog"),
                     ("Network",open_properties_dialog)
                    )
                 ),
                 ("Help",
                    (("About",about_callback),
                     ("Error",error_callback)
                    )
                 )
                )

  tkfront.parent_destroy_callback = program_destroy_callback
  tkfront.make_gui(menu_design,tk_key_event,None,"0")

  count = 0
  while not Done:
    try:
      tkfront.update("%d"%count)
    except:
      pass  # status line can raise "_tkinter.TclError: invalid command"during shutdown
    sys.stdout.flush()
    count +=1
    count = count % 1000

if __name__ == '__main__': main()

The last two values in the line ("Item",open_item_dialog,None,"item_dialog") of menu design tell the menu item that it has two states (ticked or not). Observe that the tick appears when the item dialog exists, cannot be un-ticked and is un-ticked when the item dialog is closed. More on this in menu design protocol.

Class testClass exists only so we have something to edit. Ten instances of this class is created in test_items.

The callback open_item_dialog() is used to create the item dialog using the tkfront method create_popup_managed_dialog(...).

When the apply button is pressed on the dialog, the callback dialog_change(dialog) is used to update the actual data.

The callback refreshDialog() is used to update the values shown in open item dialog. In this case the tkfront method get_popup_dialog(key) is used to find the dialog rather than using a local copy (for no particular reason). This callback is used both when the next and previous buttons are used on the item dialog but also when the alternative properties dialog is used to change a value.

The error_callback() is used to demonstrate non-blocking error boxes which is rather useful in realtime applications.


Menu Design List Protocol

The menu design list can be a list or tuple of the following elements:

  1. Top level menu name (eg. File, View, Help)
  2. A tuple containing menu items:
    1. Menu item name (eg. Open, Save, SaveAs, Exit, Seperator)
    2. Primary callback (if two callbacks then first is for menu item is true)
    3. Optional secondary callback for menu item is false.
    4. Optional variable name - must be unique for the menu system - item can be ticked.
    5. Optional initial state - True or False
    6. Optional list of menu items (by variable name) on which this item depends in order to be enabled.

Control Design List Protocol

The control design list, used to create text and buttons on the main window. can be a list or tuple of the following elements:

  1. Text (for simple text) or a tuple of values for widget:
    1. Text for simple text or the name on a button. If ends with >>> then next widget on same line.
    2. Callback - if exits and not none then this is a button widget.
    3. Foreground Color - text eg. "Red"

Application Program Interface

Following are the functions to be used in the tkfront module. You won't need to use them all normally.

make_gui(menu_design, key_event_callback=None, control_design=None, status_str=None)
Used to both initialise Tkinter and load all the menu, text and buttons onto the main window. The variables menu_design, control_design and status_str are just passed to create a control_gui instance using root as the parent.
control_gui(parent, menu_design=None, control_design=None, status_str=None) - class initialiser
Creates a Tkinter frame with menu and widgets as described in menu_design and control_design protocols. The use of status_str is hopefully obvious and can be changed using either the control_gui method setStatusLine(new_text) or by inserting it with the update(status_text) command.
update(status_text = None)
Updates everything to do with the gui in one foul shot - including any popup dialogs that have been created. The status line is only updated if it exists and the text has changed.
quit_callback()
Called when any attempt is made to close the application - a useful function if you have other reasons to shut down. A confirmation box will appear. Since this is blocking it can be used to suspend execution of the application - bit of a cheat if it's a game you are playing. I may change this to a non-blocking box in future if it proves to be a nuisance.
error_dialog(text, blocking = True)
Since I am interested in real time applications (not necessarily games) an error box that suspends the application could be good or bad - with this function we have the choice. If using the blocking mode, be aware that clearing the error will continue the application which could be a bad thing if the error was "your thumb is in the way of the laser beam".
open_dialog(type_str = "All",suffix_str = "*.*")
A helper function to bring up the standard Tkinter file browser.
save_dialog(type_str = "All",suffix_str = "*.*",default = "")
A helper function to bring up the standard Tkinter file browser.
create_popup_dialog(key, dialog_settings_array, dialog_title, dialog_callback)
Creates an instance of control_dialog - see module description here. By doing it this way, the update function is able to keep track of what should and shouldn't happen. The key value is used to ensure that the same dialog is not created twice and also allows it to be identified for deletion and refresh (ie. get_popup_dialog(key) uses this value).
create_popup_managed_dialog(key, dialog_settings_array, dialog_title, dialog_callback)
Creates an instance of managed_dialog - see module description here.
create_popup_auto_dialog(key, dialog_settings_array, dialog_title, dialog_callback)
Creates an instance of auto_dialog - see module description here.
destroy_popup_dialog(key)
Removed the dialog indexed by the key used during it's creation.
get_popup_dialog(key)
Used to find the dialog created with this key, if it still exists.

Installation

First off grab the module tkfront here or the complete tkfront suite of modules as a zip file here. Test programs used on these webpages can also be downloaded here.

Installation is similar to control dialog except that both the auto_dialog.py and control_dialog.py modules must be in the path of your application.

Documentation can be generated by running python help("tkfront").


In case you didn't notice them, please heed the warnings on the microde page.


Copyright Robert Parker November 2009