ezdialog.py 1.5
Mike Callahan (mcalla314@gmail.com)
9/14/2015
Many times, a simple desktop application will need to obtain needed information from the user via a dialog window, and then show the user its progress via a message window. Unfortunately, early Python programmers may find designing and coding GUIs difficult. This module makes it easy to create both. It uses ttk and tkinter which are included with Python 2.7 and later, so no other GUI libraries are required. This is important since almost all Python installation include these libraries, so an early programmer does not have to worry about locating and installing other packages. With this update, a programmer can also produce a GUI that keeps the dialog part and the message part in the same window.
It is a pure Python module, and is extremely lightweight. It was intended for an early Python programmer with limited or no GUI experience. This module hides much of the complexity in GUI design. For dialog windows, the widgets are placed in a single row or column, within a title frame, centered, with the programmer deciding which row or column to use. The key for referencing the widget is the row or column number. However, each method returns the contained ttk widget so more advanced programmers can take advantage of ttk features as their programming. The widgets included in this library are label, line, entry, checkbox row/column, radio button row/column, tree list, spin box row, horizontal slider, text, open dialog, save as dialog, command button row/column. It also has two containers; labeled frame and tabbed notebook. Using containers is a more advanced programming technique but still not too difficult. This module is platform independent and will use the native look and feel of the operating system.
The idea for the module came out of the author's experience in teaching a GIS scripting course. As the final project, the student was required to create a GUI interface to the scripted GIS tool they created. After trying to teach simple tk/ttk, he realized that the complexity of GUI design could be hidden and still result in adequate dialog and message windows for their projects.
While a proper python installation package is supplied, all the installer really needs to do is place ezdialog.py somewhere on the Python path or in the same directory as the script. Thus, another complexity for the early programmer is eliminated.
The author hopes that this module helps instructors in early Python courses guide their students to quickly escape the command line so they can write modern GUI-based scripts. However, he has found it useful for his own scripts that need straight-forward dialog windows, simple output, and even more complex GUIs. This module is released in the public domain.
The 1.5 update adds the text widget, the ability to place widgets in columns, and a few other minor changes. Also, the examples have been expanded showing how to write GUIs that don't require a separate messages window. This document includes the reference for the library and an extensive primer showing how to use the library.
Classes and Methods
EzDialog: This makes it easy to create dialog windows. The logical steps for using EzDialog is to:
1) Create the dialog window
2) Place the required widgets in the dialog
3) Grid the dialog
4) Wait for the user to click on a command button
5) Process the user's input
Constructor:
Pop up a dialog window that the programmer fills with various widgets. If the user clicks on Ok, the state of all the widgets is returned in the result attribute which is a dictionary keyed on the row or column position of the widget. If this dialog window is to be placed in a container, the programmer will supply the container object as the master of this window. If this window is to stand-alone, no master should be supplied and a container will be automatically generated.
Syntax:
EzDialog(master, worient)
master:tk.frame – the frame that holds the dialog (optional)
worient – how to arrange widgets, either 'vertical' (default) or 'horizontal'
Example code:
dialog.grid()
Attributes:
result
Used by methods to collect the contents of the widgets. If the user clicks on Ok, the state of all the widgets is returned in the result attribute which is a dictionary keyed on the row or column position of the widget. The programmer gets and set these values by using the getParameter and setParameter methods or by using the index operator with the row or column position on the dialog object. Note, it might appear that widget row or column is an index, it is actually a key. The programmer should not access result directly except when they test if the user clicked on Ok by using:
if dialog.result is not None: or more simply, if dialog.result:
master
The Tk window which contains the dialog window. Normally not specified, it can be accessed by the programmer if desired. However, in setting up a notebook or an internal frame, this will be the frame that contains the widgets for that page.
Methods:
Get the contents from the widget at the given row or column. The return value is determined by the widget. If there is no widget at the row or column, a key error will result. The programmer can also use a key of the widget row or column on the dialog object which is usually easier. This method returns None for the text widget since it was intended for output messages only. Using x = dialog[n] is the same as x = dialog.getParameter(n).
Syntax:
getParameter(rowcol)
rowcol:int – row or column number
return:object – contents of widget
Example code:
from ezdialog import EzDialog
dialog = EzDialog()
dialog.makeEntry(0, 40, 'Map Title', 5)
dialog.makeButtons(1)
dialog.grid()
dialog.waitforUser()
if dialog.result is not None: # user entered something
mapTitle = dialog.getParameter(0) # same as mapTitle = dialog[0]
print(mapTitle)
Set the contents of the widget at the given row or column. If there is no widget at the row or column, a key error will result. The programmer can also use an assignment to a key of the widget row or column on the dialog object which is usually easier. For both the text and tree list widgets, this method will append the object to the end of the widget. To clear a text and tree list widget, call setParameter with None. Using dialog[n] = x is the same as dialog.setParameter(n, x).
Syntax:
setParameter(rowcol, value)
rowcol:int – row or column number
value:object – the desired value of widget
Example code:
Display methods
Display the dialog window or container on the screen. This is inherited from the tk.Frame widget, and supports all of the standard parameters for Frame.grid(). The example below explains the most common ones. EzDialog uses the tk grid geometry manager, hence the name. Containers should be displayed (gridded) after all widgets for that container are created. Widgets are gridded automatically at creation time. Think of grid() as the same as display().
Syntax:
grid(row, column, padx, pady)
row:int - row number, defaults to 0
column:int - column number, defaults to 0
padx:int - horizontal spacing (pixels), defaults to 0
pady:int - vertical spacing (pixels), default to 0
Example code:
from ezdialog import EzDialog
dialog = EzDialog()
dialog.makeEntry(0, 40, 'Map Title', 5)
dialog.makeButtons(1)
dialog.grid()
dialog.waitforUser()
if dialog.result is not None: # user entered something
mapTitle = dialog.getParameter(0) # same as mapTitle = dialog[0]
print(mapTitle)
Start the mainloop of the dialog window. Mainloop is the tk event loop processor. This allows the user to click on a command button and is the last statement executed after the dialog box is constructed and displayed. The label waitforUser was selected as a better method name for beginners but seasoned tk programmers can stick with mainloop. The programmer should call this method with the top most EzDialog object.
Syntax:
waitforUser()
Example code:
from ezdialog import EzDialog
dialog = EzDialog()
dialog.makeEntry(0, 40, 'Map Title', 5)
dialog.makeButtons(1)
dialog.grid()
dialog.waitforUser()
if dialog.result is not None: # user entered something
mapTitle = dialog.getParameter(0) # same as mapTitle = dialog[0]
print(mapTitle)
Set the title for a window. If not set, the title will default to "tk" which is probably not what the programmer wants. This is an alias for the title method of the master container.
Syntax:
setTitle(prompt)
prompt:str - title of window or frame
Example code:
dialog = EzDialog()
dialog.setTitle('The Title') # same as dialog.master.title('The Title')
dialog.makeLabel(0, 'A label so we can see the title')
dialog.grid()
dialog.waitforUser()
Widget creation methods
Place a ttk label widget in the dialog window at a given row or column. The label will be automatically centered. The programmer specifies the text in the label and can specify if the label is bold, italic, or both. This was added because adjusting the font in tk is tricky. The programmer can also specify how much vertical space is between the widgets. The method returns the tk label widget which can be used to further manipulate the widget. The text of the label can also be changed by using setParameter or assigning to the widget row or column key.
Syntax:
makeLabel(rowcol, init, style, gap)
rowcol:int - row or column number
init:str - text of the label
style:str - can be 'bold' and or 'italic' (defaults to '')
gap:int - vertical space between widgets (pixels, defaults to 3)
returns: - tk label widget
Example code:
dialog = EzDialog()
dialog.setTitle('The Title') # same as dialog.master.title('The Title')
dialog.makeLabel(0, 'A label so we can see the title')
dialog.grid()
dialog.waitforUser()
Place a horizontal or vertical line in the dialog box at a given row or column. The line will be oriented opposite of the dialog, ie. if dialog is oriented vertically, the line will be horizontal and vice-versa. The line will extend across the entire dialog and will be 2 pixels wide. The programmer can also specify how much vertical space is between the widgets. The method returns the ttk separator widget which can be used to further manipulate the widget. This is purely a cosmetic widget.
Syntax:
makeLine(rowcol, gap)
rowcol:int – row or column number
gap:int – vertical space between widgets (pixels, defaults to 3)
returns: – ttk separator widget
Example for both:
dialog.makeLine(1)
dialog.grid()
Place a ttk entry widget in the dialog window at the given row or column. This widget is used to get a string from the user. The programmer specifies the width of the window and a prompt that labels the widget frame. The programmer can also specify how much vertical space is between the widgets. The method returns the entry widget which can be used to further manipulate the widget. Any string entered into the entry widget is retrieved by using the getParameter method or row or column key which returns a string. In order to protect entries for passwords the programmer should use the show options of the ttk widget, ex.
entry['show'] = '*'.
Syntax:
makeEntry(rowcol, width, prompt, gap)
rowcol:int - row or column number
width:int - width of entry box (chars)
prompt:str - text of widget frame label
gap:int - vertical space between widgets (pixels, defaults to 3)
returns: - ttk entry widget
Example code:
dialog.makeButtons(1)
dialog.grid()
if dialog.result is not None: # user entered something
mapTitle = dialog[0]
Place a ttk combobox widget in the dialog window at the given row or column. A combobox combines a listbox and an entry box. This widget is used to get a one of suggested optional strings or have the user type in their own. The programmer specifies the width of the box and a prompt that labels the widget frame. The programmer can also specify how much vertical space is between the widgets. The method returns the combobox widget which can be used to further manipulate the widget. Any string entered into the combobox widget is retrieved by using the getParameter method or the row or column key which returns a string. In the example below, dialog.getParameter(0) or dialog[0] would return 'PNG'. A common enhancement is to designate a callback function after the user clicks on the down arrow. The items in the listbox part can be dynamically controlled by using the values option of the ttk widget, ex. combo['values'] = itemsList and assigning a callback function to the postcommand attribute of the ttk widget, ex. combo['postcommand'] = callback. Both these techniques will be demonstrated later.
Syntax:
makeCombo(rowcol, width, prompt, alist, gap)
rowcol:int - row or column number
width:int - width of entry box (chars)
prompt:str - text of widget frame label
alist:list - items to include in the list
gap:int - vertical space between widgets (pixels, defaults to 3)
returns: - ttk combobox widget
Example code:
types = ['PNG','BMP','KML']
dialog.makeButtons(1)
dialog.grid()
if dialog.result is not None: # user entered something
mapType = dialog.getParameter(0)
Place a series of ttk checkboxes widgets in the dialog window at the given row or column. This widget is used to let the user select a series of independent options. The programmer specifies the width of each widget, a prompt that labels the widget frame, and the labels for each widget. The programmer can also specify if the checkboxes are oriented horizontally or vertically and how much vertical space is between the widgets. The method returns a list of combobox widgets which can be used to further manipulate the widgets. The state of the checkboxes is retrieved by using the getParameter method or row or column key which returns a list of strings of the values checked. In the example below, dialog.getParameter(0) or dialog[0] would return ['Option1', 'Option3']. However, the order is undefined.
Syntax:
makeChecks(rowcol, width, prompt, alist, orient, gap)
rowcol:int - row or column number
width:int - width of each check box (chars)
prompt:str - text of widget frame label
alist:list - items to include in the list
orient:str - 'vertical' or 'horizontal' (defaults to 'horizontal')
gap:int - vertical space between widgets (pixels, defaults to 3)
returns - list of ttk checkbutton widgets
Example code:
from ezdialog import EzDialog
dialog = EzDialog()
types = ['Option1','Option2','Option3']
init = ['Option1','Option3']
dialog.makeChecks(0, 10, 'Options', types)
dialog[0] = init
dialog.makeButtons(1)
dialog.grid()
dialog.waitforUser()
if dialog.result is not None: # user entered something
options = dialog[0]
print(options)
Place a series of ttk radio button widgets in the dialog window at the given row or column. This widget is used to let the user select only one of a series of dependent options. It is similar to a combobox except the user cannot supply their own option. The programmer specifies the width of each widget, a prompt that labels the widget frame, and the labels for each widget. The programmer can also specify if the radio buttons are oriented horizontally or vertically and how much vertical space is between the widgets. The method returns a list of radio button widgets which can be used to further manipulate the widgets. The button that is selected is retrieved by using the getParameter method or row or column key which returns a string. In the result below, dialog.getParameter(0) or dialog[0] would return 'Polylines'.
Syntax:
makeRadios(rowcol, width, prompt, alist, orient, gap)
rowcol:int - row or column number
width:int - width of each radio button (chars)
prompt:str - text of widget frame label
alist:list - items to include in the list
orient:str - 'vertical' or 'horizontal' (defaults to horizontal)
gap:int - vertical space between widgets (pixels, defaults to 3)
returns - list of ttk radio button widgets
Example code:
Place a tk Option Menu widget in the dialog window at the given row or column. This widget is used to let the user select only one of a series of dependent options. It is similar to a radio button row but it doesn’t take up as much screen space. However, the option list cannot be dynamic. The programmer specifies a prompt that labels the widget frame, and the list of options. The programmer can also specify how much vertical space is between the widgets. The method returns an option menu widget which can be used to further manipulate the widget. The option that is selected is retrieved by using the getParameter method or row or column key which returns a string. In the result below, dialog.getParameter(0) or dialog[0] would return 'Polygons'.
Syntax:
makeRadios(rowcol, width, prompt, alist, gap)
rowcol:int - row or column number
prompt:str - text of widget frame label
alist:list - items to include in the list
gap:int - vertical space between widgets (pixels, defaults to 3)
returns - list of ttk radio button widgets
Example code:
Place a tree list in the dialog window at a given row or column. This widget is used to collect the contents of other widgets multiple times in a single dialog window. One might think of it as a dialog inside a dialog. A tree list is a simplification of a ttk treeview which was designed to hold a list of items which can be added to or deleted from. This widget includes an Add and Delete button. If the user clicks on Add, the contents of the request widgets are placed in the treelist widget and cleared from the other widgets, except spinboxes are not cleared. If the user selects a row from the treelist and clicks on Delete, the row is removed. The programmer specifies the width and title of each column in the tree list, a prompt that labels the widget frame, the other widget rows or columns to add, and the height of the widget. The programmer can also specify how much vertical space is between the widgets. The method returns the treeview and button widgets which can be used to further manipulate the widgets. The contents of the widget are retrieved by using the getParameter method or row or column key which returns a list of lists of strings. In the result below, dialog.getParameter(2) or dialog[2] would return [['Polylines','rivers'],['Polygons','watersheds']]. The setParameter method or row/column key assignment will append a list to the existing tree list. Note, this might violate the true spirt of assignment, but this syntax was selected to make it easy to the beginner. To clear a list, assign or call setParameter with None.
Syntax:
makeTreeList(rowcol, height, prompt, columns, rowcols, gap)
rowcol:int - row or column number
height:int - height of widget (pixels)
prompt:str - text of widget frame label
columns:list - the title and width (pixels) of each column
rowcols:list - the other widget row or column numbers to add to the list
gap:int - vertical space between widgets (pixels, defaults to 3)
returns - list of ttk treeview and two buttons
Example code:
from ezdialog import EzDialog
dialog = EzDialog()
features = ['Points','Polylines','Polygons']
dialog.makeRadios(0, 8, 'Feature Classes', features, 'Polylines')
dialog.makeEntry(1, 20, 'Shapefiles')
columns = [['Type', 60], ['Filenames', 160]]
dialog.makeTreeList(2, 6, 'Selected Files', columns, [0, 1])
dialog.makeButtons(3)
dialog.grid()
dialog.waitforUser()
if dialog.result is not None:
selection = dialog[2]
print(selection)
Place a horizontal integer scale with an associated entry box at the given row or column. This widget is used to get an integer from the user. The programmer specifies the length of the scale, the limits of the scale, the width of the entry box, and a prompt that labels the widget frame. The programmer can also specify how much vertical space is between the widgets. The method returns the scale and entry widgets which can be used to further manipulate the widgets. The contents of the entry box are retrieved by using the getParameter method or row or column key which returns an integer. In the result below, dialog.getParameter(0) or dialog[0] would return 1.
Syntax:
makeScale(rowcol, length, width, prompt, parms, gap)
rowcol:int - row or column number
length:int - length of scale (pixels)
width:int - width of entry box (chars)
prompt:str - text of widget frame label
parms:list - parameters for the scale [from, to, inint]
from:int - lower limit of scale
to:int - upper limit of scale
init:int - initial value of scale and entry box
gap:int - vertical space between widgets (defaults to 3)
returns - list of ttk scale and entry box
from ezdialog import EzDialog
dialog = EzDialog()
parms = [1, 10, 1]
dialog.makeScale(0, 100, 2, 'Number of Layers', parms)
dialog.makeButtons(1)
dialog.grid()
dialog.waitforUser()
if dialog.result is not None: # user entered something
layers = dialog[0]
print(layers)
Place a series of integer spin boxes in the dialog window at a given row or column. This widget is used to collect a group of integers from the user. The programmer specifies the width of each spin box, the parameters of each spin box, a string that appears between each spin box, and a prompt that labels the widget frame. The programmer can also specify how much vertical space is between the widgets. The method returns a list of the spin box widgets which can be used to further manipulate the widgets. The integers selected are retrieved by using the getParameter method or row or column key which returns a list of integers. In the result below, dialog.getParameter(0) or dialog[0] would return [2, 23, 2014]. To clear all the spin boxes at once use dialog.setParameter(0, '') or dialog[0] = ''. This widget is especially useful to input dates and times.
Syntax:
makeSpin(rowcol, width, prompt, parms, between, gap)
rowcol:int - row or column number
width:int - width of entry box (chars)
prompt:str - text of widget frame label
parms:list - parameters for the scale [from, to, init]
from:int - lower limit of spin box
to:int - upper limit of spin box
init:int - initial value of spin box
between:str - string to display between spinners (defaults to ‘’)
gap:int - vertical space between widgets (pixels, defaults to 3)
returns - list of ttk spin boxes
Example code:
Place an open filename dialog in the dialog window at the given row or column. The widget is used to get a filename from the user. The programmer specifies the width of the entry box, a prompt that labels the widget frame, and parameters for the dialog window. The programmer can also specify how much space is between the widgets. The method returns the entry and button widgets which can be used to further manipulate the widgets. The filename selected is retrieved by using the getParameter method or row or column key which returns a string.
Syntax:
makeOpen(rowcol, width, prompt, gap, parms)
rowcol:int - row or column number
width:int - width of entry box
prompt:str - text of widget frame label
gap:int - vertical space between widgets (defaults to 3)
parms - named parameters for the askopenfilename function
filetypes:list - list of type types and templates for each type
defaultextension:str - extension to add if user does not supply one
initialdir:str - the initial directory
initialfile:str - the default filename
multiple:bool - if user is allow to select more than one filename (defaults to False)
mustexist:bool - user cannot create new directory (defaults to False)
title:str - title of browse window
returns - list of ttk entry box and button
Example code:
from ezdialog import EzDialog
dialog = EzDialog()
data = [('CSV files','*.csv')]
inputFn = dialog.makeOpen(0, 40, 'Open File', filetypes=data)
dialog.makeButtons(1)
dialog.grid()
dialog.waitforUser()
if dialog.result is not None:
inputFn = dialog.getParameter(0)
print(inputFn)
Place a save as filename widget in the dialog window at the given row or column. The widget is used to get a filename from the user. The programmer can specify the width of the entry box, a prompt that labels the widget frame, and parameters for the dialog window. The programmer can also specify how much vertical space is between the widgets. The method returns the entry and button widgets which can be used to further manipulate the widgets. The widget will automatically warn the user before they overwrite a file. The filename selected is retrieved by using the getParameter method or row or column key which returns a string.
Syntax:
makeSaveAs(rowcol, width, prompt, gap, parms)
rowcol:int - row or column number
width:int - width of entry box
prompt:str - text of widget frame label
gap:int - vertical space between widgets (defaults to 3)
parms - named parameters for asksaveasfilename function
filetypes:list - list of type types and templates for each type
defaultextension:str - extension to add if user does not supply one
initialdir:str - the initial directory
initialfile:str - the default filename
multiple:bool - if user is allow to select more than one filename (defaults to False)
mustexist:bool - user cannot create new directory (defaults to False)
title:str - title of browse window
returns - list of ttk entry box and button
Example code:
Place a row or column of command buttons at the row or column indicated. This widget is used to let the user indicate that they are finished with the dialog. The default is the Ok/Cancel button bar. If the user clicked Ok, the result attribute will contain the contents of the widgets which are read using the getParameter method or the row or column keys. If the user clicks Cancel, the result attribute will be None. The programmer can supply a button label and callback for each button in the row or column. The method returns a list of the button widgets which can be used to manipulate the widgets.
If you choose to place messages in a text widget, you will have to supply the cmd list, because the default code for the Ok button closes the dialog. See the second simple dialog example to see how to do this.
Syntax:
makeButtons(rowcol, cmd, space, gap)
rowcol:int - row number
cmd:list - label/callback list for each button (defaults to Ok/Cancel)
orient:str - 'vertical' or 'horizontal' (defaults to horizontal)
space:int - space between buttons (defaults to 3)
gap:int - vertical space between widgets (defaults to 3)
returns - list of ttk buttons
Example code:
from ezdialog import EzDialog
dialog = EzDialog()
entry = dialog.makeEntry(0, 40, 'Map Title', gap=5)
dialog.makeButtons(1)
dialog.grid()
dialog.waitforUser()
if dialog.result is not None: # user entered something
mapTitle = dialog.getParameter(0)
print(mapTitle)
Place a vertical scrolling text box in the dialog window at a given row or column. This widget is used to display messages to the user and can be an alternate method than using a separate EzMessage window. If this option is selected, the programmer must override the default actions of the command button row widget. See the second simple dialog example for more information.
The programmer specifies the height and width of the text box and a prompt that labels the widget frame. The programmer can also specific how much space is between the widgets. The method returns the tk text widget which can be used to further manipulate the widget. To send appended text to the box, use the setParameter method or assign a string to the row/column index. Long strings will character wrap. Note, this might violate the true spirt of assignment, but this syntax was selected to make it easy to the beginner. To clear the contents of this widget assign or call setParameter with None. Since this is an output widget only, the getParameter method returns None. The tk text widget is extremely powerful and can handle fonts, styles, and even graphics, so a more advanced programmer can take advantage of this widget.
Syntax:
makeText(rowcol, width, height, prompt, gap)
rowcol:int - row or column number
width:int - width of box (chars)
height:int - height of box (chars)
prompt:str - text of widget frame label
gap:int - vertical space between widgets (pixels, defaults to 3)
returns - tk text
Example code:
dialog[0] = 'This is a text widget.\n'
Send all error messages to a text widget, used in try-except blocks. This is recommended for all scripts with non-trivial processing where text widgets rather than separate message windows are used.
Syntax:
catchExcept(rowcol)
rowcol:int - the row or column number of the text widget
For example code the second simple dialog example.
Container creation methods
Containers are widgets that hold other dialogs. When you create an EzDialog, a container is automatically created. However, it is possible to create a container inside another container. This module supports two types of containers, frames and notebooks. After one creates a container, they usually will want to place an EzDialog inside the new container.
One big difference between widgets and containers is that widgets in EzDialog are self-gridding. The programmer must manually grid containers. The reason is that all the widgets in a container must be gridded before the container is gridded and is why widgets are gridded at creation time automatically.
Remember to call waitforUser (mainloop) only as a method of the main (highest level) dialog.
Create a titled frame which can hold other widgets. This widget is used to group other widgets into an area. It is a way to include horizontally and vertically oriented widgets on the same dialog. Since frame is a simple concept, this example is more complete showing the dialog for a map making script. The basic technique is to create the master dialog, create the frame, create a dialog using the frame, create the widgets inside the frame, grid the frame, and grid the master dialog.
Syntax:
makeFrame(title)
title:str - text of container frame label
returns - tk.Frame container
Example code:
# more code would go here...
Here is the hierarchy of this dialog:
Create a tabbed notebook which each page can hold other widgets. This widget is used to organize complex dialogs into separate windows. Each page of the notebook will be a separate dialog. Most often, the programmer will create the notebook as a stand-alone window, store the pages in some structure, add the command buttons to this page, create the sub-dialogs where the masters for these new dialogs are the pages in the notebook, grid each dialog page, grid the notebook, grid the stand-alone window, and then wait for the user. Note, all contained dialogs and widgets must be gridded before the notebook is gridded. After the user clicks on a command button, the result attribute will contain the current page tab selected which is read using the getParameter method or the row or column key. The contents of page are accessed by using getParameter or the row or column key for every page in the notebook. The method returns a ttk notebook and a list of ttk frames which is each page in the notebook. To display a page use setParameter or the row or column key on the page tab. Using notebooks are a little more involved that a simple dialog, but this example should help the intermediate programmer.
Syntax:
makeNotebook(rowcol, tabs)
rowcol:int - row or column number (most times this will be 0)
tabs:str list - list of labels for each tab
returns - ttk.notebook and list of tk.Frame which is each page in the notebook
Example code:
# more code would go here...
Here is the hierarchy of this window:
Here is a screen shot showing a dialog window using all the widgets at once:
Here is the code:
Here is a breakdown of the hierarchy of this window:
As you can see, these widgets and containers should meet the needs for most beginning and intermediate programmers.
EzMessage: This class makes it easy to create message windows. Placing message in a separate window duplicates the method used in certain GIS programs, but this not the only way to send messages to the user. The logical steps for using EzMessage are to:
1) Use EzDialog to collect the necessary input from the user
2) Check to see if the user gave good data
3) Create the EzMessage window
4) Grid the message window
5) Process the data, writing any informative messages to the window
6) Inform the user that everything is finished
7) Wait for the user to click on the command button
8) Do any necessary clean up and exit the script
Constructor:
Pop up a scrolling message window that the programmer can write messages to. The window is displayed until the user clicks on Ok. The programmer usually creates this widget in the code after the user has supplied good data. The message window contains a scrolling tk text widget.
Syntax:
EzMessage(master, width, height, wait)
master:tk.frame - the frame that holds the dialog, optional
wait:bool - if True (default), create an Ok button which can display the window until clicked
width:int - width of window in characters, defaults to 60
height:int - height of windows in characters, defaults to 20
Example code:
message.grid()
Attributes:
master
The Tk window which contains the dialog window. Normally not specified, it can be accessed by the programmer if desired.
Methods:
Title a window. If not set, the title will default to "tk", which is probably not what the programmer wants.
Syntax:
setTitle(prompt)
prompt:str - title of window or frame
Example code:
message.setTitle('Message Window')
Display the message window on the screen. This was inherited from the tk.Frame widget, and supports all of the standard parameters.
Syntax:
grid()
Example code:
message.setTitle('Message Window')
This waits for the user to click on a command button and is usually the last statement executed after all messages are written. It is an alias to the mainloop() method and is inherited from tk.Frame.
Syntax:
waitforUser()
Example code:
message.setTitle('Message Window')
addMessage
Add a message in the message window.
Syntax:
addMessage(message, delay)
message:str - the message to display
delay:float - the number of seconds to pause, defaults to 0
Example code:
message.grid()
Clear all messages in the message window.
Syntax:
clear()
Example code:
message.grid()
Close the message window. Many times this will be automatic.
Syntax:
close()
Example code:
message.grid()
message.close()
Capture all error messages to the message window, used in try-except blocks. This is recommended for all scripts with non-trivial processing.
Syntax:
catchExcept(wait)
wait:bool - if true (default), wait for user to press Ok before closing window
Example code:
message.grid()
Using EzDialog and EzMessage
Our First Dialog
Below is the code to create a very simple dialog and message windows. It will show the basic philosophy of EzDialog and EzMessage and how to use them. EzDialog envisions a dialog window as a column or row of widgets that are centered. The programmer decides where to place the widget by using the desired row or column. The widget row/column is critical from that point and is the key that is used to obtain the data. The rows or columns should be in descending order from the top, starting with 0.
For our first dialog window, let's add three widgets; an entry widget, a scale, and a command button row. This will be a simple interface to a simple Python script. Again, the purpose of EzDialog is to let beginning Python programmers write GUI-based programs quickly without the usual learning curve required to learn a GUI library. We will have the user type in their name, and select a repeat value. After the user clicks on Ok, the program will display a window with their name repeated that number of times. Here is the dialog window layout:
So using this layout, the entry widget is in row 0, the scale widget is in row 1, the command button row is in row 2.
Here are the steps we need to code:
1) Import the EzDialog and EzMessage classes from ezdialog.
2) Create a dialog window with the title, "A Very Simple Dialog". Since we did not supply a Tk window, a new one will be created automatically.
3) Create an Entry widget that is 40 characters wide, titled "Type in your name", and place at row 0.
4) Create a scale widget that is 100 pixels wide, has a 2 character wide display box, runs between 1 and 10, and place at row 1.
5) Create an Ok/Cancel command button row and place at row 2
6) Grid (display) the dialog box and wait for the user to click on either Ok or Cancel.
7) Check to see if the user clicked on Ok, if so…
8) Get the contents of the entry widget and place into a variable labeled name.
9) Get the contents of the scale widget and place into a variable labeled n.
10) Create an output window that is 40 characters wide by 10 characters tall and has an ok button at the bottom.
11) Start a loop that will run n times and…
12) Print the entered name into the output window. The window will stay open until the user clicks on Ok.
Below is the complete script:
from ezdialog import EzDialog, EzMessage # import needed classes
dialog = EzDialog() # create dialog window
dialog.setTitle('A Very Simple Dialog') # title it
dialog.makeEntry(0, 40, 'Type in your name') # create entry widget
dialog.makeScale(1, 100, 2, 'Repeat value', [1, 10, 1]) # create scale widget
dialog.makeButtons(2) # create button row widget
dialog.grid() # display the dialog
dialog.waitforUser() # wait for a button click
if dialog.result is not None: # user clicked on Ok
name = dialog[0] # get name from the entry widget
n = dialog[1] # get repeat from the scale widget
output = EzMessage(width=40, height=10) # create the output window
output.setTitle('The result') # title it
output.grid() # display it
for i in xrange(n): # set up the loop
output.addMessage(name+'\n') # print the name
output.waitforUser() # wait for user to click on Ok
Here is the screen shot of the dialog and output windows:
Just for fun, here is the code one would have to write if they did not use EzDialog (without comments):
try:
import Tkinter as tk # support Python 2
except ImportError:
import tkinter as tk # support Python 3
import ttk
def go(entryValue, scaleValue, dialog):
dialog.master.destroy()
message = tk.Frame()
message.master.title('The result')
textWindow = tk.Text(message, width=40, height=10)
textWindow.grid(sticky='wens')
verticalBar = ttk.Scrollbar(message)
horizontalBar = ttk.Scrollbar(message, orient='horizontal')
textWindow['yscrollcommand'] = verticalBar.set
textWindow['xscrollcommand'] = horizontalBar.set
textWindow['font'] = ('Helvetica', '10')
verticalBar['command'] = textWindow.yview
horizontalBar['command'] = textWindow.xview
verticalBar.grid(row=0, column=1, sticky='ns')
horizontalBar.grid(row=1, column=0, sticky='we')
okButton = ttk.Button(message, text='Ok', width=8, command=lambda: stop(message))
okButton.grid(row=2)
message.grid()
for i in xrange(scaleValue):
textWindow.insert('end', entryValue+'\n')
textWindow.see('end')
textWindow.update()
def stop(window):
window.master.destroy()
dialog = tk.Frame()
dialog.master.title('A Very Simple Dialog')
entryVar = tk.StringVar()
entryFrame = ttk.Labelframe(dialog, text='Type in your name')
entry = ttk.Entry(entryFrame, width=40, textvariable=entryVar)
entry.grid(sticky='w')
entryFrame.grid(row=0, pady=3)
scaleVar = tk.IntVar()
scaleVar.set(1)
scaleFrame = ttk.Labelframe(dialog, text='Repeat value')
scale = ttk.Scale(scaleFrame, from_=1, length=100, to=10, variable=scaleVar,
orient='horizontal', command= lambda x: scaleVar.set(int(float(x))))
scale.grid()
scaleEntry = ttk.Entry(scaleFrame, width=2, textvariable=scaleVar)
scaleEntry.grid(row=0, column=1)
scaleFrame.grid(row=1, pady=3)
buttonFrame = ttk.Frame(dialog)
okButton = ttk.Button(buttonFrame, text='Ok', width=12,
command=lambda : go(entryVar.get(), scaleVar.get(), dialog))
cancelButton = ttk.Button(buttonFrame, text='Cancel', width=12,
command=lambda : stop(dialog))
okButton.grid(row=0, column=0, padx=3)
cancelButton.grid(row=0, column=1, padx=3)
buttonFrame.grid(row=2, pady=3)
dialog.grid()
dialog.mainloop()
Now you can see why I created EzDialog for my students!
As an example of a horizontally organized widget, I modified the dialog code to:
from ezdialog import EzDialog, EzMessage # import needed classes
dialog = EzDialog(worient='horizontal') # create dialog window
dialog.setTitle('A Very Simple Dialog') # title it
dialog.makeEntry(0, 40, 'Type in your name') # create entry widget
dialog.makeScale(1, 100, 2, 'Repeat value', [1, 10, 1]) # create scale widget
dialog.makeButtons(2, orient='vertical') # create button row widget
dialog.grid() # display the dialog
dialog.waitforUser() # wait for a button click
Which created this dialog:
Our Second Dialog Without Using EzMessage
The first dialog used separate dialog and message windows. This format is popular with certain GIS programs. However, with the use of the text widget, we can keep everything in a single window. We will have to change the code for the command buttons, since the default for Ok is to exit mainloop and close the window. These types of functions connected to command buttons are called "callbacks".
Here is the dialog window layout:
In this layout, the entry widget is in row 0, the scale widget is in row 1, the text widget is in row 2, and the command button row is in row 3.
Here are the logic steps:
1) Import the EzDialog class from ezdialog.
2) Create a dialog window with the title, "A Very Simple Dialog".
3) Create an Entry widget that is 40 characters wide, titled "Type in your name", and place at row 0.
4) Create a scale widget that is 100 pixels wide, has a 2 character wide display box, runs between 1 and 10, and place at row 1.
5) Create a Text widget that is 40 characters wide, 10 characters tall, titled "The result", and place at row 2.
6) Create a list of command buttons that override the default action. For the Ok button, we will have to send the function the dialog using a lambda command. We will discuss lambda later. This minor difficulty can be eliminated by using object-oriented syle, which also will be covered later.
7) Create an Ok/Cancel command button row using the new commands, and place at row 3.
8) Grid (display) the dialog box and wait for the user to click on either Ok or Cancel.
The Ok callback needs to:
1) Get the contents of the entry widget and place into a variable labeled name.
2) Get the contents of the scale widget and place into a variable labeled n.
3) Start a loop that will run n times and…
4) Print the entered name in the text widget.
Below is the script for the callback:
from ezdialog import EzDialog # import needs class
def go(dialog): # Ok button callback
name = dialog[0] # get name from the entry widget
n = dialog[1] # get repeat from the scale widget
for i in xrange(n): # set up the loop
dialog[2] = name+'\n' # display the name
dialog = EzDialog() # create dialog window
dialog.setTitle('A Very Simple Dialog') # title it
dialog.makeEntry(0, 40, 'Type in your name') # create entry widget
dialog.makeScale(1, 100, 2, 'Repeat value', [1, 10, 1]) # create scale widget
dialog.makeText(2, 40, 10, 'The result') # create the text widget
commands = [['Ok', lambda:go(dialog)],['Cancel', dialog.close]] # command buttons
dialog.makeButtons(3, cmd=commands) # create button row widget
dialog.grid() # display the dialog
dialog.waitforUser()
Here is the screen shot of the GUI:
Notice that the callback needs to be defined before you can use it. Basically you just put everything that was previously in the "if" statement, in the Ok function callback. Whether the programmer chooses to use EzMessage is really just a matter of taste. Any application using EzMessage can be written using a text widget as well. In the following examples, we won't worry about message widgets and concentrate on the input widgets.
Simple Map Creation Dialog
Below is the code to create a simple dialog window which might be useful for a GIS tool which creates a map. This example were not written in an object-oriented style in order to help the typical GIS script or early Python script writer. Object-oriented style will be demonstrated later. We will need the filename of the input CSV file, the output PNG map image, and the title for the map. We will use an open filename widget, a save as filename widget, and an entry widget, and the command button bar. We won't worry about EzMessage now, but notice since there is no text widget, we probably should assume this example would use EzMessage.
We want the layout for the dialog to look like this:
The steps are below:
1) Import EzDialog.
2) Create an EzDialog object with a given title.
3) Call each make method placing the desired widgets.
4) Call the grid and waitforUser methods.
5) Check to see if the user clicked on Ok by seeing if result is not None.
6) Retreive the value of each widget using the row key.
Below is the code with numerous comments:
from ezdialog import EzDialog # import the class from library
dialog = EzDialog() # create a dialog window
dialog.setTitle('Create a Map') # title the window
inputs = [('CSV files', ('*.csv'))] # create the input filetypes
dialog.makeOpen(0, 40, 'Input CSV file:', filetypes=inputs, gap=10)
outputs = [('PNG files', ('*.png'))] # create output filetypet
dialog.makeSaveAs(1, 40, 'Output PNG file:', filetypes=outputs, gap=10)
dialog.makeEntry(2, 40, 'Map Title', gap=10)# entry widget for map title
dialog.makeButtons(3, space=10, gap=10) # button line
dialog.grid()
dialog.waitforUser() # display the dialog window
if dialog.result is not None: # user entered something
inFile = dialog[0] # input filename
outFile = dialog[1] # output filename
title = dialog[2] # map title
# more code would go here...
Here is the resulting dialog window:
Dynamic Widgets
A useful option is to create a combobox which is dependent on the contents of another widget. The code below shows a combobox which is dependent on a radio button row. The trick is to create a combobox widget and assign it as an attribute to the dialog window. Next, you need to create a callback function which looks at the contents of the radio button row and then sets the values attribute of the combo widget. Again, we will avoid an object-oriented approach in order not to confuse the early script writer.
Here is the layout for this example:
The callback function will have to know the widget that called it. This can be accomplished by creating an attribute of the dialog object and passing the object to the function. This complexity can be eliminated by writing in an object-oriented fashion, which will be covered in a later example.
Here are the steps for the callback function:
1) Create the lookup dictionary.
2) Get the value from the radio buttons as an index.
3) Using this index, lookup the list in the lookup dictionary.
4) Set the values attribute of the combobox to this list.
Below is the code for the callback function:
def update(dialog): # callback function
""" set the alist attribute by what is in the radio button box """
lookup = {'Trees':['Oak','Maple','Beech'], # create the lookup dict
'Birds':['Cardinal','Robin','Sparrow'],
'Flowers':['Rose','Petunia','Daylily']}
index = dialog[0] # get the contents of widget
dialog.items['values'] = lookup[index] # this does the magic!
Here are the steps for the main script:
1) Import EzDialog.
2) Create an EzDialog object with a given title.
3) Create the category list for the radio buttons.
4) Call each make method placing the desired widgets.
5) Set the command option of the combobox to the callback function.
6) Set the initial value of the combobox.
7) Call the grid and waitforUser methods.
8) Check to see if the user clicked on Ok by seeing if result is not None.
9) Retreive the value of each widget using the row key.
Below is the code:
from ezdialog import EzDialog
categories = ['Trees','Birds','Flowers'] # create the radio categories
dialog = EzDialog() # create the dialog window
dialog.setTitle('Dynamic Widget Demo') # title it
dialog.makeRadios(0, 10, 'Feature Classes', categories, 0) # create radio widget
dialog.items = dialog.makeCombo(1, 10, 'Items', [], gap=40) # create combo # widget & save as attribute
dialog.items['postcommand'] = (lambda: update(dialog)) # must use lambda to pass # dialog argument
dialog[1] = '...' # set the initial value
dialog.makeButtons(2, gap=3, space=20) # create button bar at bottom
dialog.grid()
dialog.waitforUser()
if dialog.result:
category = dialog[0] # get category
item = dialog[1] # get item
# more code would go here...
All the lines are similar to the previous example except for the combobox creation. There is a lot going on here so let's break it down:
1) We create the combobox which will be an attribute to the dialog object which we will pass to the callback function.
2) We assign the postcommand option of the combobox to the callback function. The parameter should be a function name, but we need to pass an argument to this function. This is why we use the lambda command. Lambda creates an anonymous function that includes the argument as a single parameter. If we did not use lambda, the update(dialog) would be called only at the creation time. Lambda delays the call until the user clicks on the down arrow. If you don't understand this, don't worry, lambda takes some time to understand. Just remember, if you want to pass an argument in a callback function, you must use lambda.
3) We set the initial value of the combobox to '...' using the row key.
Here is the resulting dialog window:
More Advanced GUI Programming
Since EzDialog is based on Tkinter and ttk, the features for both libraries can be included when making more complex GUIs. Using the ideas introduced in the above example, let's walk through the creation of a GIS tool which will add a new field to an existing shapefile, based on the contents of a lookup table and a field in the existing shapefile. While this is a simple tool, the programming concepts learned should be useful for more complex GUIs.
The table for the existing shapefile looks like the above. As you can see, it is a simple table with only three attribute fields; Poly which contains the polygon number, Lat which is the latitude of the point, and Lon which is the longitude. What we want to do is to add a new field which contains the identification of the polygon. We are not going to worry about the GIS processing to accomplish this task, we are only going to look at the code needed to produce the GUI.
The lookup table is even simpler, having only two fields; the polygon number and the polygon identification:
The last field is the new field we want to add to the existing table.
Here is the layout for the GUI:
Here are the steps for this new callback function:
1) Get the shapefile name from the open filename widget.
2) Get the field names from the shapefile and place into a list. Note, the ListFields function must already exist and be imported at this point. We will use a list comprehension.
3) Set the values parameter from the combobox to this list.
Below is the code for the callback function:
def updateList(dialog):
""" dialog:EzDialog – dialog window object """
value = dialog[0] # get the contents of the widget
fields = [field.name for field in ListFields(value)] # get the field names # into a list
dialog.fields['values'] = fields # update the combobox list
The steps for the dialog function are below:
1) Create an EzDialog object with a given title.
2) Create a filter for shapefiles. Notice that the filter is a list of tuples.
3) Create the open file widget for shapefiles
4) Create a filter for csv files
5) Create the open file section for csv files
6) Call each make method placing the remaining widgets.
7) Call the grid and waitforUser methods.
8) Check to see if the user clicked on Ok by seeing if result is not None.
9) Retreive the value of each widget using the getParameter method.
Below is the code for a function that creates the dialog window:
def makeDialog(title):
""" title:str - title of window
-> list holding contents of widgets """
dialog = EzDialog() # create a dialog window
dialog.setTitle(title)
xfilter = [('Shapefiles','*.shp')] # limit open to shapefiles
dialog.infile = dialog.makeOpen(0, 40, 'Input Shapefile', 10,
filetypes=xfilter) # create file open
xfilter = [('CSV files','*.csv')] # limit open to csv files
dialog.makeOpen(1, 40, 'Lookup File', 10, filetypes=xfilter)
dialog.makeEntry(2, 40, 'New Field', 10) # create entry
dialog.fields = dialog.makeCombo(3, 40, 'Lookup Field',
[], 10) # create combobox
dialog.fields['postcommand'] = (lambda: updateList(dialog)) # link callback
dialog.makeButtons(4, space=30, gap=10) # create command button row
dialog.grid()
dialog.waitforUser() # wait for user
if dialog.result is not None: # clicked Ok
infile = dialog[0]
lookup = dialog[1]
newField = dialog[2]
lookupField = dialog[3]
return (infile, lookup, newField, lookupField)
else: # clicked Cancel
return None
Here is the resulting dialog window:
Object-oriented Version of the Previous Example
If you program in an object-oriented style, some of the above code would change. The actual dialog code would go into a class:
class Dialog(object):
""" an object-oriented dialog """
def __init__(self, title):
self.dialog = EzDialog() # create a dialog window
self.dialog.setTitle(title)
xfilter = [('Shapefile', ('*.shp'))] # limit open to shapefiles
self.dialog.makeOpen(0, 40, 'Input Shapefile', 10,
filetypes=xfilter) # create file open
xfilter = [('CSV files', ('*.csv'))] # limit open to csv files
self.dialog.makeOpen(1, 40, 'Lookup File', 10, filetypes=xfilter)
self.dialog.makeEntry(2, 40, 'New Field', 10)# create entry
self.fields = self.dialog.makeCombo(3, 40, 'Lookup Field',
[], 10) # create combobox
self.fields['postcommand'] = self.updateList # link callback
self.dialog.makeButtons(4, space=10, gap=10) # create command button row
self.dialog.grid() # display dialog
There are a few differences. First, the window is made an attribute of the instance using self.dialog. Also, the combobox is also made an attribute of the instance using self.fields. Because of this, the callback is a method to the instance does not need an argument. This is why lambda is not used.
Here is the callback method:
def updateList(self):
""" update the combobox list with fields names """
value = self.dialog[0] # get contents of widget
fields = [field.name for field in ListFields(value)] # get the field
# names into a list
self.fields['values'] = fields # update the combobox list
The only difference is now the combobox is an attribute to the instance using self.fields.
Here is what a main function might look like:
def main():
app = Dialog('Add a Field') # create the dialog
app.dialog.waitforUser() # wait for a selection
if app.dialog.result is not None: # user clicked on Ok
infile = app.dialog[0] # get values of widgets
lookup = app.dialog[1]
newField = app.dialog[2]
lookupField = app.dialog[3]
else: # user clicked on Cancel
return
# more code would go here
main()
As you can see, using an object-oriented style does simplify the code a little.
Object-oriented Second Example GUI
Now that we have seen an example of object-oriented programming, let's back up and look at our second example in an object-oriented style. We will also add error trapping. Here is the design again:
Here are the steps we need to code:
1) Import the EzDialog class from ezdialog. We will not need EzMessage.
2) Create a dialog window with the title, "A Very Simple Dialog".
3) Create an Entry widget that is 40 characters wide, titled "Type in your name", and place at row 0.
4) Create a scale widget that is 100 pixels wide, has a 2 character wide display box, runs between 1 and 10, and place at row 1.
5) Create a text widget for the messages, in row 2
5) Create an Ok/Cancel command button row and place at row 3. This line will be a little different. Since have the advantage of object-oriented programming, we will not need lambda.
6) Grid (display) the dialog box and wait for the user to click on either Ok or Cancel.
Since we are using object-oriented style, the callback just needs a self parameter:
1) Get the contents of the entry widget and place into a variable labeled name.
2) Get the contents of the scale widget and place into a variable labeled n.
3) Start a loop that will run n times and…
4) Display the entered name into the output window.
Here is the object-oriented code that creates the GUI.
from ezdialog import EzDialog # import needed class
class Gui(object):
def __init__(self):
self.dialog = EzDialog() # create dialog window
self.dialog.setTitle('A Very Simple self.dialog') # set the title
self.dialog.makeEntry(0, 40, 'Type in your name') # create entry widget
self.dialog.makeScale(1, 100, 2, 'Repeat value', [1, 10, 1]) # scale
self.dialog.makeText(2, 20, 10, '') # create text widget
callbacks = [['Ok', self.go],['Exit', self.dialog.close]] # new buttons
self.dialog.makeButtons(3, cmd=callbacks) # create button row widget
self.dialog.grid() # display the dialog
def go(self):
name = self.dialog[0] # get the name
n = self.dialog[1] # get repeat
for i in xrange(n) # set up loop
self.dialog[2] = name + '\n' # display name
def Main():
app = Gui() # build and display the GUI
try:
app.dialog.waitforUser() # run the dialog
except:
app.dialog[2] = app.catchExcept()
Another Example
The following example is a dialog for a tornado path map generator. The program generates HTML which has embedded KML maps. The user selects the tornadoes for the map based on date, county and EF scale. We will use the treelist widget to collect the user selections.
Below is the layout for the GUI:
The steps for the dialog function are below:
1) Create an EzDialog object with a given title.
2) Create a list of counties.
3) Call each make method placing the remaining widgets.
4) Call the grid and waitforUser methods.
5) Check to see if the user clicked on Ok by seeing if result is not None.
6) Retreive the value of each widget using the row key.
Below is the code for a function that creates the dialog window:
def makeDialog():
""" -> list - contents of widget """
dialog = EzDialog() # create the dialog
dialog.setTitle('Tornado Path Generator') # set the title
counties = ['Breckenridge','Bullitt','Hardin','Jefferson',
'Meade','Oldham','Trimble'] # create list of counties
parms = [[1,12,1],[1,31,1],[1930,2030,2000]] # parameters for date widget
ef = ['EF0','EF1','EF2','EF3','EF4','EF5'] # create list of damage levels
columns = [['Date',100],['County',200],['EF Scale',100]] # column headers
dialog.makeSpin(0, 4, 'Date of Tornado', parms, '/', 5) # make date widget
dialog[0] = '' # clear date
dialog.makeCombo(1, 15, 'County', counties, gap=5) # create county widget
dialog.makeRadios(2, 4, 'Maximum EF Damage', ef, gap=3) # create damage widget
dialog.makeTreeList(3, 10, 'Included Tornadoes', columns, [0, 1, 2], 5)
# create tornado selector
dialog.makeButtons(4, space=10, gap=10) # create Ok/Cancel row
dialog.grid() # display dialog
dialog.waitforUser() # wait for a selection
if dialog.result is not None: # user clicked OK
return dialog[3] # return tornado list
else: # user clicked Cancel
return None # return None
Here is the resulting dialog window:
Notice that the TreeList acts as a collection widget for all the other widgets. The row key or getParameter method will return a list of lists which the calling program will use to retrieve the desired KML files. In the above example, if the user clicks on Ok, dialog[3] would contain [[1,1,2000], 'Hardin', 'EF3'], [[5,10,2011], 'Oldham', 'EF1'], [[9,7,1982], 'Meade', 'EF0']].
An Example using Notebook
The following is a GUI for making precipitation maps. There are two types of maps, routine and accumulated. Routine maps are for a 24 hour period ending this morning. Accumulated maps are for a given number of days ending on a given morning. This example will show how EzDialog can be used for complex dialogs using pages in notebooks to organize the widgets. It will also demonstrate widget initialization and auto-updating techniques. Due to the complexity, this example will use object-oriented code. We will also use EzMessage.
Below is the layout for the Routine page:
We want our widgets to autoupdate based on the current contents of other widgets. First, for the Routine page, the first entry widget contents will the title for the map, "24 Hour Precipitation Ending 7AM [the current date]". The current date will be obtained from the computer clock using the today() function in the date class of the datetime module.
The second entry widget contents will be the filename of the map. It is "pcpn[MMDDYYYY].png" where MM is the month number, DD is the date, and YYYY is the year. The checkboxes tell which type of processing to do; make KMZs, make maps, or both. The default is to do both.
Here is an example of what the Routine page looks like for February 12, 2015:
Here are the steps to the main dialog method for creating the window, notebook and Routine page:
1) Create a new dialog window and title it "Mapper 1.00"
2) Create a notebook with 2 pages and label the tabs "Routine" and "Accumulate"
3) Get today's date and break it down into the needed elements
4) Create a new dialog window on the Routine page
5) Place the necessary widgets on that page
6) Initialize the widgets with default based on the date elements values
7) Grid the Routine page
Below is the layout for the Accumulated page:
This page two entry widget contents are dependent upon the two spin boxes. The first collection of spin boxes obtains the ending date for the accumulated map. The second single spin box tells the number of days to accumulate. These spin boxes control the contents of the entry widgets. The first entry widget is the title of the map which looks like this: "[N] Day Precipitation Total Ending [the ending date]. The second entry widget is the filename of the map which looks like this: "accum[MMDD-MMDDYYYY].png" where the first MMDD is the starting month and date and the second MMDDYYYY is the ending month, date, and year. As the user changes the spin boxes, both entries will update automatically.
Below is an example of a 2 day accumulation ending February 12, 2015:
Here are the steps for the Accumulate page and finishing up the main window:
1) Create a new dialog on the Accumulate page
2) Place the widgets on the page
3) Connect the widgets to a callback method
4) Call the callback function to initialize the widgets
5) Grid the Accumulate page
6) Grid the notebook
7) Place the command button row on the main window
8) Connect the Ok button to a new function
9) Grid the main window
10) Display the Routine page
Below is the code for a class that creates the dialog window and initializes the Routine page. All lines are commented:
class Gui(object):
""" the GUI for the script """
def __init__(self):
self.dialog = ezdialog.EzDialog() # create dialog
self.dialog.setTitle('Mapper 1.00') # set title
self.pageHeaders = ['Routine', 'Accumulate'] # page tabs
# make a notebook with two pages, with tab tiles
self.notebook, page = self.dialog.makeNotebook(0, self.pageHeaders)
today = datetime.date.today() # get todays date
# pull out the date, month, year, and month name into a list
self.dt = today.strftime('%d,%m,%Y,%B').split(',')
# routine page
self.routine = ezdialog.EzDialog(page[0]) # create routine page
self.routine.makeEntry(0, 60, 'Title', 10) # create title widget
# init widget to default title using today's date
self.routine[0] = '24 Hour Precipitation Ending 7 AM {0[3]} {0[0]},'\
{0[2]}'.format(self.dt)
self.routine.makeEntry(1, 20, 'Filename', 10) # create filename widget
# init widget to default filename using today's date
self.routine[1] = 'pcpn{0[1]}{0[0]}{0[2]}.png'.format(self.dt)
jobs = ['Make KMZs', 'Make Maps'] # create checkbox labels
self.routine.makeChecks(2, 15, 'Jobs', jobs, 10) # create jobs widget
self.routine[2] = jobs # select both checkboxes
self.routine.grid(padx=10) # display the routine page
Most of this code should seem familiar. However, date formation section and the use of the format method for strings might be new. Continuing with this method, here is the part that deals with the accumulation page and finishes up the main dialog:
# accum pcpn page
self.accum = ezdialog.EzDialog(page[1]) # create the accum page
# parameters for a date widget
parms = [1, 12, 1], [1, 31, 1], [2000, 2100, 2014]
# create a date widget
accumDate = self.accum.makeSpin(0, 4, 'Ending Date', parms, '/')
# init widget to today's date
self.accum[0] = [today.month, today.day, today.year]
# if user changes date run updateAccum
accumDate[0]['command'] = self.updateAccum
accumDate[1]['command'] = self.updateAccum
accumDate[2]['command'] = self.updateAccum
# create a days back widget
accumBack = self.accum.makeSpin(1, 3, 'Days Back', [[1, 45, 2]], '', 10)
# if user changes days back run updateAccum
accumBack[0]['command'] = self.updateAccum
self.accum.makeEntry(2, 60, 'Title', 10) # create title widget
self.accum.makeEntry(3, 40, 'Filename', 10) # create filename widget
self.updateAccum() # update all widgets
self.accum.grid(padx=10) # display the accum page
# dialog
self.notebook.grid() # display the notebook
# display Ok/Cancel row
buttons = self.dialog.makeButtons(1, space=20, gap=10)
buttons[0]['command'] = self.go # override default Ok
self.dialog.grid() # display dialog
self.dialog[0] = 'Routine' # show the routine page
Again, most of this code should seem familiar if one has been following the previous examples. Note that the date spin boxes widgets command all point to the same callback.
Here are the steps for the somewhat involved callback:
1) Get the contents of the ending date widget and split into the desired elements.
2) Create a new datetime object using those elements
3) Get the other elements of the ending date
4) Update the contents of the map title widget using these new elements
5) Calculate the beginning date by using the content of the days back widget.
6) Extract the necessary elements of the beginning date
7) Update the contents of the filename widget using both the beginning and ending date elements.
Below is the code for the method:
def updateAccum(self):
""" update widgets on accum page """
end = self.accum[0] # get ending date
# create a new datetime object using the user's input
endDate = datetime.date(end[2], end[0], end[1])
# extract the components of the date including month name
endDateFmt = endDate.strftime('%d,%m,%Y,%B').split(',')
# update the title using the new information
self.accum[2] = '{0} Day Precpitation Total Ending {1[3]} {1[0]},'\
'{1[2]}'.format(self.accum[1], endDateFmt)
# calculate the beginning date from the ending date and days back
begDate = endDate - datetime.timedelta(int(self.accum[1]))
# extract the day and month
begDateFmt = begDate.strftime('%d,%m').split(',')
# update the filename using the new information
self.accum[3] = 'accum{0[1]}{0[0]}-{1[1]}{1[0]}{1[2]}.png'.format(
begDateFmt, endDateFmt)
Here one can see how the callback method changes the contents of the entry widgets. Most of the work is done by the strftime() function and string methods.
The callback for the Ok button is very simple:
1) Get the current displayed page
2) Close the dialog window
Below is that simple code:
def go(self):
""" get current selected page workaround """
self.pageSelect = self.dialog[0] # assign select page as attribute
self.dialog.close() # kill dialog window
The main function is straightforward:
1) Import the necessary modules
2) Create the GUI
3) Wait for the user to click Ok
4) Get the displayed page
5) Create and title the message window
6) Run the GIS code
7) Close the message window
Here is the main driving function. The GIS code is not included:
import ezdialog, datetime
def main():
gui = Gui() # make the GUI
gui.dialog.waitforUser() # wait for a click
if gui.dialog.result: # user click Ok
try:
run = gui.pageSelect # get the selected page tab
message = ezdialog.EzMessage(width=80) # create the message window
message.setTitle('Mapper 1.00') # set the title
message.grid() # display the window
# this class contains all the code to do the GIS processing
# notice we are passing the addMessage method as an argument
mapper = Mapper(message.addMessage)
if run == 'Routine': # Routine page was selected
mapper.runRoutine(gui.routine)
elif run == 'Accumulate': # Accumulate page was selected
mapper.runAccum(gui.accum)
message.close() # close the window
except:
message.catchExcept() # catch all errors
I hope these examples will be adequate to guide the beginning programmer or the intermediate programmer with little GUI experience to create dialogs for scripts. Since all the methods return the created ttk or tk widgets, the more advanced programmer can modify the appearance and behavior of the widgets, dynamically. For more information, consult documentation on ttk and tk. I have found the New Mexico Tech site: infohost.nmt.edu/tcc/help/pubs/tkinter, to be extremely helpful. If you find ezdialog useful, please let me know! I am especially interested in educational uses.