Interactive Python Apps using tk GUI (tkinter)
Posted on Thu 27 February 2020 in Python
What is tcl-tk?
Tcl (Tool Command Language) is a simple open-source interpreted programming language that provides multiple common facilities such as variables, procedures, and control structures as well as many useful features that are not found in any other major language. Tcl runs on almost all modern operating systems such as Unix, Macintosh, and Windows (including Windows Mobile).
Tk (Tool Kit) is a free open-source, cross platform widget toolkit that together with tcl, allows users to program interfaces. Tk comes installed for pretty much all macOS and Linux machines (as of this post current version is 8.6). While tk provides all the snippets like buttons, boxes and windows, tcl defines what to do with them.
If you are using Windows please jump to Test tkinter for Windows Users.
Enable tkinter Python package for iOS
We will assume that you are using Python 3. One clean way to install components to macOS is via homebrew, if you are using it as your package manager first you need to install tcl-tk:
Via homebrew
Depending on the version you are in, this can be a tricky installation. The process will be shown in steps that hopefully would cover most of the known issues/scenarios.
1. Install tcl-tk cask
brew update
brew install tcl-tk
Now you would be able to see in cellar /usr/local/Cellar/
, in our case we installed tcl-tk 8.6.10
.
2. Install pyenv (if you have consider 2.b)
The pyenv 1.2.18
was used for this post.
brew install pyenv
Then you will need to do modify your bash and zsh profiles to facilitate the use of pyenv and the global python and pip in use.
a. Create or modify ~/.bash_profile
and include:
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
if command -v pyenv 1>/dev/null 2>&1; then
eval "$(pyenv init -)"
fi
b. Create or modify ~/.zshrc
and include:
export PATH="/usr/local/opt/tcl-tk/bin:$PATH"
export LDFLAGS="-L/usr/local/opt/tcl-tk/lib"
export CPPFLAGS="-I/usr/local/opt/tcl-tk/include"
export PKG_CONFIG_PATH="/usr/local/opt/tcl-tk/lib/pkgconfig"
if command -v pyenv 1>/dev/null 2>&1; then
eval "$(pyenv init -)"
fi
Make sure the above paths match the ones in your system (if you didn't customise anything on your homebrew installation the paths should match).
Restart the terminal and verify your path echo $PATH
, you should be able to see .pyenv and tcl-tk directories there.
3. Install python
If you have python you will have to reinstall it to add tcl-tk configuration.
a. it --with-tcl-tk works. Try (in our case we are installing python 3.8.2, but it can be any other python 3 version):
pyenv install python 3.8.2 --with-tcl-tk
If it doesn't work don't try to install python via brew, since this will mess up your environment. Rather proceed with option 3.b.
b. Hardcoded installation.
Modify the pyenv
python-build script. You will find it in /usr/local/opt/pyenv/plugins/python-build/bin
. Open python-build
and find the line that contains the Configuration options:
$CONFIGURE_OPTS ${!PACKAGE_CONFIGURE_OPTS} "${!PACKAGE_CONFIGURE_OPTS_ARRAY}" || return 1
and replace if with:
$CONFIGURE_OPTS --with-tcltk-includes='-I/usr/local/opt/tcl-tk/include' --with-tcltk-libs='-L/usr/local/opt/tcl-tk/lib -ltcl8.6 -ltk8.6' ${!PACKAGE_CONFIGURE_OPTS} "${!PACKAGE_CONFIGURE_OPTS_ARRAY}" || return 1
Save changes and now you will be able to install pyhton
pyenv install python 3.8.2
4. Change global python and test it
Check the versions of python installed
pyenv versions
Depending on what was your global python option before, you will see an '*' in front of one of the options. You will now select the newly installed python to be used as the global one.
pyenv global 3.8.2
Next step is to verify that indeed the new python is your default python by running:
pyhton -V
If everything went well you will see your python version displayed (in our case 3.8.2) if not yu may want to take a look to pyenv known issues and documentation.
Finally, its time to try out tkinter. Run:
python -m tkinter -c 'tkinter._test()'
A small window will pop up to your screen, click QUIT to exit.
Via python.org
All current installers for macOS downloadable from python.org supply their own private copies of Tcl/Tk. So no need to do something else. You can also install ActiveTcl via their website.
Test tkinter for Windows Users
To test tkinter
you can simply run see offcial documentation:
python -m tkinter -c 'tkinter._test()'
A small window will pop up to your screen, click QUIT to exit.
Create a Simple GUI
I will cover a general way to create a script for a GUI and a simple way to wrap it in a function for re-usability.
We will create a user interface that request credentials to "log in".
1. Create Window
Initialize a tkinter.Tk object, that is a root window.
Every tkinter application must have a root window.
import tkinter as tk
window = tk.Tk()
2. Modify Window Attributes
It is possible to modify aspects fo the window, like size, position, title among others. These attributes are not mandatory if you are not sure of the size of window you can avoid modifying its geometry and the final outcome will have the size that contains all contents.
For our "Log in" example, we will put a title and a fix size of 500 x 300 pp. A good practice is to make the windows appear on top of any other open, so that the user don't miss it.
# Modify window title
window.title('Log in')
# Define window size
window.geometry("500X300")
# Display window on top of all open windows
window.attributes("-topmost", True)
3. Declare User Input Variables
To request input from a user is needed to initialize variables that can collect it. Possible types are linked to basic variable types like String, Boolean, Double or Int.
For our example we require 2 string variables; one for email and another one for the password
user_mail = tk.StringVar(window)
user_pass = tk.StringVar(window)
4. Create Labels for User Input and Arrange them
It is important to let the user know what input do we require from them in each field. This can be achieved by defining Labels and Entries. There are two ways to specify the way the labels and entries will be shown in the window: - By pack. This will organize the objects and show them sequentially. - By grid. This provides flexibility to specify the desired location using rows and columns.
We will use the grid method in our example. We declare labels and entries in pairs, the user label requesting "User email" will be shown in row 0, column 0, its entry in the same row, but column 1. A similar thing is defined for password, however for the password entry we set the parameter show to '*' so that the password is masked.
# Request user email
tk.Label(window, text="email").grid(row=0)
tk.Entry(window, textvariable=user_mail).grid(row=0, column=1)
# Request password (masked)
tk.Label(window, text="password").grid(row=1)
tk.Entry(window, textvariable=user_pass, show='*').grid(row=1, column=1)
5. Add Buttons
For a better user interaction it is possible to define and include buttons so that the user can submit, run, accept, cancel,... the execution. Each button requires its own definition and command that specifies what to do when clicked.
We will include a submit and a cancel buttons that will help us either to terminate the process or to continue with the credentials provided
tk.Button(winroot, text="Submit", command=lambda: user_click('submit')).\
grid(row=6, column=2)
tk.Button(winroot, text="Cancel", command=lambda: user_click('cancel')).\
grid(row=6, column=1)
5.1 Buttons command
The command parameter in the Button class, allows you to pass a function to define what process to execute once the button is clicked.
We create a function that changes a global variable that let us know if the button option was cancel or not. We destroy the window after the interaction.
u_selection = 'submit'
def user_click(button_id):
global u_selection
if button_id == 'cancel':
u_selection = 'cancel'
window.destroy()
6. Display the window
To display the window, it's needed to block any other python process so that it waits for window's result.
You can call the method mainloop direclty from the tkinter library or to the window object created. In our case we will call directly the method to avoid issues when trying to quit the window.
tk.mainloop()
GUI Function
All the components describe above can be wrapped in a function that can be re-usable or if preferred a class that can be extended (npt covered here).
An example of a function that retrieves Log in credentials from user is:
def get_log_in(win_title: str ="Log in",
win_geometry: str=None) -> (str, str, str):
"""
Log in Credentials
Retreive user credientials
Parameters
----------
win_title: str
Window title. Default value is "Log in"
win_geometry: str
Window geometry with format "<number>x<number>". Default value is None
which will provide the minimum size.
Returns
-------
user selection: sumbit or cancel, user email and user password.
"""
def user_click(button_id):
nonlocal u_selection
nonlocal window
if button_id == 'cancel':
u_selection = 'cancel'
window.destroy()
window = tk.Tk()
u_selection = 'submit'
# Modify Geometry
if win_geometry is not None:
window.geometry(win_geometry)
# Title
window.title(win_title)
window.attributes("-topmost", True)
# Request user email
user_mail = tk.StringVar(window)
tk.Label(window, text="email").grid(row=0)
tk.Entry(window, textvariable=user_mail).grid(row=0, column=1)
# Request password (masked)
user_pass = tk.StringVar(window)
tk.Label(window, text="password").grid(row=1)
tk.Entry(window, textvariable=user_pass, show='*').grid(row=1, column=1)
# Display Buttons
tk.Button(window, text="Submit", command=lambda: user_click('submit')).\
grid(row=6, column=2)
tk.Button(window, text="Cancel", command=lambda: user_click('cancel')).\
grid(row=6, column=1)
tk.mainloop()
return u_selection, user_mail.get(), user_pass.get()