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:

from ezdialog import EzDialog

dialog = EzDialog()

dialog.grid()

dialog.waitforUser()

 

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:

 

Data access methods

getParameter

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)

 

setParameter

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:

from ezdialog import EzDialog

dialog = EzDialog()

entry = dialog.makeEntry(0, 40, 'Map Title', 5)

dialog.setParameter(0, 'A Map') # same as dialog[0] = 'A Map'

dialog.makeButtons(1)

dialog.grid()

dialog.waitforUser()

if dialog.result is not None: # user entered something

    mapTitle = dialog.getParameter(0)

    print(mapTitle)

 

Display methods

 

grid

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)

 

waitforUser

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)

 

setTitle

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:

from ezdialog import EzDialog

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

 

makeLabel

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:

from ezdialog import EzDialog

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()

 

makeLine

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:

from ezdialog import EzDialog

dialog = EzDialog()

dialog.makeLabel(0, 'Label Widget', style='bold italic')

dialog.makeLine(1)

dialog.grid()

dialog.waitforUser()

 

makeEntry

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:

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[0]

    print(mapTitle)

     

 

 

makeCombo

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:

from ezdialog import EzDialog

dialog = EzDialog()

types = ['PNG','BMP','KML']

combo = dialog.makeCombo(0, 40, 'Map Types', types)

dialog.makeButtons(1)

dialog.grid()

dialog.waitforUser()

if dialog.result is not None: # user entered something

    mapType = dialog.getParameter(0)

    print(mapType)

 

 

 

makeChecks

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)       

 

makeRadios

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:

from ezdialog import EzDialog

dialog = EzDialog()

features = ['Points','Polylines','Polygons']

dialog.makeRadios(0, 12, 'Feature Classes', features)

dialog.setParameter(0, 'Polylines')

dialog.makeButtons(1)

dialog.grid()

dialog.waitforUser()

if dialog.result is not None: # user entered something

    feature = dialog.getParameter(0)

    print(feature)

 

makeOption

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:

from ezdialog import EzDialog

dialog = EzDialog()

features = ['Points','Polylines','Polygons']

dialog.makeOption(0, 'Feature Classes', features)

dialog[0] = features[2]

dialog.makeButtons(1)

dialog.grid()

dialog.waitforUser()

if dialog.result is not None: # user entered something

    feature = dialog[0]

    print(feature)

 

makeTreeList

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)

 

makeScale

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

                       

 

Example code:

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)

  

 

makeSpin

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:            

from ezdialog import EzDialog

dialog = EzDialog()

parms = [[1, 12, 2],[1, 31, 23],[2000, 2100, 2014]]

dialog.makeSpin(0, 4, 'Enter a Date', parms, '/')

dialog.makeButtons(1)

dialog.grid()

dialog.waitforUser()

if dialog.result is not None: # user entered something

    date = dialog[0]

    print(date)

 

 

makeOpen

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)

 

makeSaveAs

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:

from ezdialog import EzDialog

dialog = EzDialog()

shapes = [('Python Files',('*.py','*.pyc'))]

inputFn = dialog.makeSaveAs(0, 40, 'Save File', filetypes=shapes)

dialog.makeButtons(1)

dialog.grid()

dialog.waitforUser()

if dialog.result is not None: # user entered something

    outputFilename = dialog[0]

    print(outputFilename)

 

makeButtons

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)

 

 

makeText

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:            

from ezdialog import EzDialog

dialog = EzDialog()

dialog.makeText(0, 40, 10, 'Contents')

dialog[0] = 'This is a text widget.\n'

dialog.grid()

dialog.waitforUser()

 

catchExcept

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.

 

makeFrame

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:

from ezdialog import EzDialog

dialog = EzDialog() # create main dialog

dialog.setTitle('Create a Map') # set title

frame = dialog.makeFrame('Map Parameters') # create frame

dialogf = EzDialog(frame) # create dialog inside the frame

dialogf.makeEntry(0, 40, 'Map Title') # create entry inside the frame

alist = ['PNG','BMP','KML'] # create combobox list

dialogf.makeCombo(1, 40, 'Output Type', alist) # create combobox

dialogf[1] = 'PNG' # set combobox default

alist = ['Towns','Roads','Counties','Rivers'] # create checkbox list

dialogf.makeChecks(2, 9, 'Items on Map', alist) # create checkbox

dialogf.grid() # display dialog

frame.grid(padx=3) # grid frame 3 pixels space

dialog.makeSaveAs(1, 40, 'Save File As', gap=10) # create saveas

dialog.makeButtons(2, gap=10, space=30) # create command buttons

dialog.grid() # display main dialog

dialog.waitforUser() # wait for button click, note this is the main container

if dialog.result: # user clicked on Ok

    title = dialogf[0] # get map title, note use dialogf NOT dialog

    mtype = dialogf[1] # get map type

    layers = dialogf[2] # get layers

    fname = dialog[1] # get the filename

    # more code would go here...

 

Here is the hierarchy of this dialog:

 

makeNotebook

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:

from ezdialog import EzDialog

dialog = EzDialog() # create main dialog

dialog.setTitle('Sample Notebook') # set the title

pageHeaders =  ['Page 0', 'Page 1'] # create tab labels

notebook, page = dialog.makeNotebook(0, pageHeaders) # create notebbook

page0 = EzDialog(page[0]) # create dialog for page 0, dialog is inside the page

page0.makeEntry(0, 35, 'Entry on Page 0') # put entry widget there

page1 = EzDialog(page[1]) # create dialog for page 1

page1.makeScale(0, 180, 2, 'Scale on Page 1', [1, 10, 1]) # scale widget

page0.grid() # display page 0

page1.grid() # display page 1

notebook.grid() # grid notebook

dialog.makeButtons(1) # create command buttons

dialog.grid() # display main dialog

dialog.waitforUser() # wait for click

if dialog.result is not None: # user clicked Ok

    result0 = page0[0] # get result from page 0

    result1 = page1[0] # get result from page 1

    pageSel = dialog[0] # get selected page tab 

       

        # more code would go here...

 

Here is the hierarchy of this window:

A Sampler of all widgets and containers

Here is a screen shot showing a dialog window using all the widgets at once:

 

Here is the code:

from ezdialog import EzDialog

dialogm = EzDialog()

dialogm.setTitle('A test')

notebook, pages = dialogm.makeNotebook(0, ['Page1','Page2','Page3'])

page1 = EzDialog(pages[0])

page1.makeLabel(0, 'Label Widget', style='italic bold')

page1.makeLine(1)

page1.makeEntry(2, 20, 'Entry Widget')

page1[2] = 'Type in data here'

options = ['Combobox1', 'Combobox2', 'Combobox3']

page1.makeCombo(3, 20, 'Combobox Widget', options)

page1[3] = options[2]

options = ['Checkbutton1', 'Checkbutton2', 'Checkbutton3', 'Checkbutton4']

page1.makeChecks(4, 14, 'Checkbutton Widget', options)

page1[4] = options[1:3]

options = ['Radiobutton1','Radiobutton2','Radiobutton3']

page1.makeRadios(5, 14, 'Radiobutton Widget', options)

page1[5] = options[1]

options = ['Optionmenu1','Optionmenu2','Optionmenu3']

page1.makeOption(6, 'Option Menu Widget', options)

page1[6] = options[0]

frame = page1.makeFrame('Frame Container')

frame1 = EzDialog(frame)

frame1.makeOpen(0, 40, 'Open File Widget')

frame1.makeSaveAs(1, 40, 'Save As File Widget')

frame.grid(padx=3)

frame1.grid(row=7)

columns = [['Radiobutton Widget', 160], ['Option Menu Widget', 160]]

page1.makeTreeList(8, 5, 'Treelist Widget', columns, [5, 6])

page1.makeText(9, 40, 5, 'Text Widget')

page1[9] = 'Messages should go here.'

page1.makeScale(10, 100, 2, 'Scale Widget', [1, 10, 1])

parms = [[1, 12, 2], [1, 31, 23], [2000, 2100, 2014]]

page1.makeSpin(11, 4, 'Spinbox Widget', parms, '/')

page1.grid()

notebook.grid(row=0)

test = dialogm.makeButtons(1, space=30, gap=10)

dialogm.grid()

dialogm.waitforUser()

 

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:

from ezdialog import EzMessage

message = EzMessage()

message.grid()

message.waitforUser()

 

Attributes:

 

master

 

The Tk window which contains the dialog window. Normally not specified, it can be accessed by the programmer if desired.

 

Methods:

 

setTitle

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:

from ezdialog import EzMessage

message = EzMessage()

message.setTitle('Message Window')

message.grid()

message.waitforUser()

 

grid

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:

from ezdialog import EzMessage

message = EzMessage()

message.setTitle('Message Window')

message.grid()

message.waitforUser()

 

waitforUser

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:

from ezdialog import EzMessage

message = EzMessage()

message.setTitle('Message Window')

message.grid()

message.waitforUser()

 

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:

from ezdialog import EzMessage

message = EzMessage()

message.grid()

message.addMessage('This is a message')

message.waitforUser()

 

clear

Clear all messages in the message window.

 

Syntax:

clear()

 

Example code:

from ezdialog import EzMessage

message = EzMessage()

message.grid()

message.addMessage('This is a message', 5)

message.clear()

Message.addMessage('This is a new message', 5)

message.waitforUser()

 

 

 

close

Close the message window. Many times this will be automatic.

 

Syntax:

close()

 

Example code:

from ezdialog import EzMessage

message = EzMessage()

message.grid()

message.addMessage('This is a message', 5)

message.clear()

Message.addMessage('This is a new message', 5)

message.waitforUser()

message.close()

 

catchExcept

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:

from ezdialog import EzMessage

message = EzMessage()

message.grid()

try:

    message.addMessage('Starting processing…')

    processData(message)

    message.waitforUser()

except:

    catchExcept()   

 

 

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.