Tuesday, January 5, 2016

How to write programs that run in Tkinter

There are thousands and thousands of tutorials and discussion threads about Tkinter (and by extension Tcl/Tk), but very few of them seem to address the proper approach to writing a useful program that uses Tkinter. It is easy to think first about the code you want to write to accomplish whatever task your program is supposed to do and work later on whatever GUI you'll build for your user interface. This approach however always results for me in hitting a big wall when I start coding up the GUI. That's because Tkinter (and by extension, etc) is not naitively multi threaded. It needs to either be the only thing running, or be the only thing running in a special sandbox that you make for it by implementing your own threading. It is often unhelpfully pointed out in forum threads that Tkinter is event driven. What this is meant to say is that you can't just write linear code and call routines that do Tkinter stuff; the code that you write can only consist of responses to events in your Tkinter GUI. Essentially, Tkinter is the language of whatever program you are writing; it just happens to have pythonic syntax. It's sort of like how C++ has C-like syntax. This seems terribly unfair at first, but after you decide to do it Tkinter's way it turns out to be not so bad. I've written GUIs using both methods, but these day's I'm finding it easiest to let my programs be Tkinter programs instead of Python programs that use Tkinter.

The breakthrough for me was an example that I found that was structured like an executive loop, although it wasn't presented as such by it's author. What was happening in this example was that the __init__ method was calling a start() method that then called a function that ran some code and then called itself again after a short wait using after(). mainloop() then serviced each of the calls of the executive function as well as the rest of the GUI. Here is that example:


So, the structure of the python __main__ routine for a useful Tkinter program needs to be:

1. define your top-level GUI object, which will call it's __init__. In that __init__ you build all the objects in your control screen and define its appearance.
2. If you don't do it in your __init__ method, you can call your GUI's start() function from your main python routine. But it won't run because mainloop() isn't running yet.
3. call your GUI's mainloop(). Now your start() function will run. It will need to do something that causes itself or another function to be scheduled to be run by mainloop(). The best thing to do here is to have an executive function that functions like a state machine; each time the executive is entered, it checks what the current state is and executes the code for that state, and decides whether to change the state variable before rescheduling itself.
4. Somewhere in your GUI's functions find a place to put a call to your GUI's quit() function, which will exit mainloop().
5. Now you're back in your python main function, where you can either do something else or exit.

Since most programs are basically linear, it is very easy to break them up into state machine pieces. One could even argue that all programs are state machines to begin with.

Here are some of the links that helped me figure this out:

Presently my go-to link that helped me get the idea:

http://stackoverflow.com/questions/29158220/tkinter-understanding-mainloop


From the above link, another example of the ball game with some better defined executive loop examples:

http://stackoverflow.com/questions/25430786/moving-balls-in-tkinter-canvas/25431690#25431690


http://stackoverflow.com/questions/8683217/when-do-i-need-to-call-mainloop-in-a-tkinter-application

https://www.reddit.com/r/learnpython/comments/2rpk0k/how_to_update_your_gui_in_tkinter_after_using/

This one has great discussions about both the threading and executive loop methods:

http://stupidpythonideas.blogspot.com/2013/10/why-your-gui-app-freezes.html


This question and answer sort of addressed the underlying Tkinter philosophy that is part of this topic:

http://programmers.stackexchange.com/questions/213935/why-use-classes-when-programming-a-tkinter-gui-in-python


A google search which brings more good answers of this sort:

https://www.google.com/#q=python+tkinter+define+code+to+be+run+with+mainloop()+is+called


One other tip that is helpful is that it's possible to call update() on your main screen from within the executive loop function to make things appear and move on the GUI while code is executing in the executive loop. In my example, one of my states has a loop of data processing that I want to show progress on; I can set the progress bar value and call update() for each iteration of the loop.