If you follow my posts or projects much, you know that, when it comes to GUI toolkits for Python, I’m a confirmed PyQT guy. QT is arguably the most advanced, polished, complete, and powerful GUI toolkit do-everything programming library out there, and a treat to use. But sometimes it’s overkill, and when I have to put together something quick for certain legacy OS that lack decent package management, having to load in Python and extra libraries just to get a couple text fields and a button doesn’t make sense1.
Wouldn’t it be great if there was a really simple, easy to use, lightweight graphics toolkit built right in to the standard Python libraries, so you could pop together those little one-window GUIs without loading in extra stuff? Well, hey, what’s this dusty thing over here in the corner? It looks like… Tkinter!?!??
Um… yeah. Tkinter
Once upon a time when computers were beige and Michael Keaton was Batman, graphical user interfaces were new-fangled things only found on UNIX workstations and artsy boutique machines like the Amiga or Mac. Idioms and conventions we now take for granted had yet to gel, and the resources available on most computers enforced a spartan aesthetic. It was in this age that TK was born and enjoyed its heyday. In human years, TK is old enough to drink; in toolkit years, it’s more than old enough to retire.
By the early- to mid- aughties, TK had pretty well fallen behind the state of the art even in Unix/FOSS circles, and faded from common usage. It’s reputation fell somewhere south of Java Swing; it lacked a lot of widgets that had become standard (like drop-down lists), had poor documentation, and more than anything was just plain ugly.
From a programming perspective, though, Tkinter (Python’s TK bindings) isn’t all bad; there are a few positives:
- It’s in the standard library
- It’s easy to use
- It’s got a really cool canvas widget, if you want to draw stuff
- It’s in the standard library
- It’s reasonably lightweight
- It’s in the standard library
- Also, it’s in the standard library
Because of these advantages, and the fact that it’s in the Python standard library, Tkinter still enjoys a small but steady stream of usage. Between some home and work projects that required simple GUIs, and this presentation by Russell Keith-Magee, I found myself compelled to give Tkinter another shot. I wasn’t blown away, but I was pleasantly surprised…
All the makings of a comeback…
Despite those bad years of decline during the last decade, TK development hasn’t stood still; in recent years it’s improved in various ways to make it quite usable as a simple GUI toolkit. Some of these improvements, like anti-aliased text, have shown up without requiring anything but an upgrade. Others, however, require a change to older and more thoroughly documented ways of using Tkinter.
With this in mind, I’d like to share a few tips I’ve picked up to help create serviceable and reasonably attractive GUIs in Tkinter.
Use the most recent Tk
TK seems to be adding some significant new functionality in each release, and releasing fairly regularly. As of this writing, the current stable is 8.5, but there’s a newer 8.6 release that’s just out (though Python doesn’t yet expose all the functionality). There are usually 1-2 releases every year, and even point releases can contain pretty significant new functionality.
Most distributions of Python should have at least version 8.5, which is a a must if you want anti-aliased fonts (i.e., you don’t want to look like a DOS GUI).
Read the right documentation
With a history like TK/Tkinter, there’s bound to be bad old documentation spread all over the Internet. You can trust the API reference found at http://www.tcl.tk, but unfortunately (though quite naturally) it’s written for TCL, not Python.
An effort to provide modern, friendly documentation for TK usage in a variety of languages is underway at http://www.tkdocs.com. As of this writing, tkdocs provides a very nice tutorial that emphasizes modern best practices. I can’t recommend it highly enough (I just hope the reference section gets finished!)
Overall, just be aware that there is an old, bad way to use Tkinter and a newer, better way to use it; so check the date on any advice you find and don’t just copypasta the first thing that Google turns up.
Use the new themed widgets
This is the big one. Tk has been around so long that they didn’t want to break things by changing the default widget set. Instead, the Tk folks created a new set of widgets with modern theming and behavior called “ttk”. The names of the ttk widgets are the same as the old widgets, but they’re kept in a separate namespace. In Python 2 they’re found in the “ttk” module, while in Python 3 it’s “tkinter.ttk”.
Of course, if you’re making new code, and you are cavalier with your namespacing, you can do something like this:
#In python 3 from tkinter import * from tkinter.ttk import * # Or, for python 2 from Tkinter import * from ttk import *
Now, creating (for example) a Button() instance will make a ttk Button rather than a classic TK Button. These are not drop-in replacements, as they take a different set of options, so make sure you’re looking at the ttk documentation rather than the old Tk Widgets.
Even if you’re not doing any deep configuration of your widgets, using the ttk versions will make sure they’re automatically themed like native widgets on your platform.
Use themes and styles
Much like CSS proved a much better way to style HTML than archaic tags like <FONT> and <BIG>, the new ttk widgets eschew a lot of the per-widget styling arguments in favor of themes and styles.
A style is a set of appearance settings for a given widget (e.g. a Button or Label).
A theme is a collection of styles that can be applied application-wide.
Setting a theme for your application is pretty simple:
# "sometheme" is just a string that names a theme. # Available themese vary by platform, but you can get a list with Style().theme_names() Style().theme_use(sometheme)
Setting up a style is a little more work, but definitely worth it if you’ve got a lot of widgets that need a similar look & feel. For example, suppose we need a button with red text on it2:
# We instantiate a Style() object. You only need one of these for the whole application. s = Style() # The default style for ttk Buttons is "TButton". You can find out an objects default style # by calling its .winfo_class() method in the shell. # You can use dot notation to create a 'sub-style' that inherits the parent style's attributes, like so: s.configure("Visible.TButton", foreground="red", background="pink") # My new style is called "Visible.TButton", and I can apply it to a button like so: b = Button(text="It's a Button!", style="Visible.TButton") # Or like so: b.configure(style="Visible.TButton")
There’s a lot more to styles and themes, but you can dig in to the docs and read more.
A frustrating side note
The new ttk themed widgets by default emulate a “Native look”. Unfortunately, nobody knows what that means outside of OSX and Windows; Linux, BSD, et al have no “Native look” as far as anyone knows. So on these platforms, Tk defaults to… something rather “meh”. It’d be great if a common GTK theme like Clearlooks were included (word has it there’s one available out there, but it kind defeats the point of “Included in the standard library” if we have to add third-party software), but right now the best I can suggest on X11 platforms is either the “alt” or “clam” themes. If you care about looks, don’t leave it at the default!
Use .grid() for layout
The original layout manager in Tkinter was a method called .pack(). Trying to make pack() do what you want is about as fun as doing a pure CSS float layout for Internet Explorer 6.
Fortunately, the new hotness is .grid(), which works pretty much like an HTML table3. Each widget is assigned to a row and column, and can optionally span multiple rows or columns. It’s pretty straightforward, and if called with no arguments just puts the widget at the begining of the next row.
Use .bind() to handle events
TK widgets can be created with a “command” argument which specifies a function or method to be run when the widget is “invoked”. This works ok for things like Buttons, which have a quite obvious way to “invoke” them and likely only one action to take when they are.
A more powerful way to handle events is with the .bind() method, which not only allows a wider range of actions to be handled, but also provides the receiving function with an “event” object chok full of useful information. Bind can snag keystrokes, key combinations, and a variety of mouse actions.
If you want to use both the “command” argument and the bind() method to point to the same function call (for example, you want to call the same thing when “OK” is clicked or when “Return” is pressed), bear in mind that your function has to be written in such a way as to work with or without an argument.
So for instance:
def dostuff(*args): # Note that we use *args to optionally accept arguments # If you wanted to do something with the event argument, # it's found at args[0] event = (len(args) > 0 and args[0]) or None print(event) pass root = Tk() b = Button(root, text="OK", command = dostuff) b.grid() root.bind("<Return>", dostuff)
More fun to come
As I mentioned, I’ve been having fun tinkering with Tkinter at home (creating GUIs for my Arduino projects) and at work (creating GUIs for batch processes). I also started a small project at github called TkAutoBox which provides some wrapper functions to create Tk dialog boxes. While I may not have convinced you to dump your nice modern toolkit for TK, I hope you’ll be less afraid to take it for a spin the next time you need a simple python GUI without a lot of library installation fuss.
Hack on, friends…
Footnotes:
And then there’s installing QT in a python virtualenv. Hope you got a fast machine and the ability to install development libraries…
I don’t know why, but quite often I’m asked by users to make certain text or buttons red on the pretext that they stand out more. I just find it harder to read, but what do I know?
Theres a subtle irony here, if you think much about it…