PyGest: A Python Tkinter Tutorial, Part 1

This is part one in our tutorial series on building a simple Python GUI app with tkinter. You can find the introductory article to the series at the link. In this article, we'll get things up and running by filling out the basic structure for our Python script and the application itself. We'll add a top level comment, define the necessary imports, set up our main name space, define the main function, and then configure a logger to aid in sanity checking . . .


Our app doesn't have a name yet. Typically, Python application names have a 'py' prefix or suffix attached to a word describing the app itself. Since this app processes hash digests, we'll call it PyGest, for lack of a better idea. So we'll create pygest.py, add our top level comment and our main namespace. Then we define our main function with a comment and a pass for the moment, and then call this function from the top level script environment.


Let's talk about imports. We don't need to import that many libraries for our little app. We'll use the hashlib module to perform the hashing operations, the logging module to help with debugging, and, of course, the tkinter library. The imports for tkinter require a bit of explanation.

With tkinter it is common practice not to import the module itself, but rather all the elements in it directly (i.e. "from tkinter import *" instead of "import tkinter"), which is normally frowned upon in other contexts. Perhaps this is to avoid unnecessary keystrokes? However, in this project, we're going to flout convention and import the module just like any other, i.e. "import tkinter". This will require us to explicitly declare the module's name when we call up one of its objects (ex. tkinter.Label()), but it will help us keep better visual track of tkinter objects in our code.

Secondly, one of the additions to the tkinter module in Python 3 is the ttk submodule, which offers many of the same objects as the tkinter module itself: labels, buttons etc. However, it also contains a number of widgets that are not in the tkinter standard library (ex. combo boxes), and its objects are optimized to take on the native look of the OS platform running the app. (Personally, on my system, I can't tell much of a difference between the look of most tkinter widgets and ttk widgets.)

For our tutorial app, we're not going to be doing anything especially fancy, so I've opted not to use the ttk submodule, as all of the widgets we'll be using that are in ttk can also be found in tkinter proper. Moreover, widgets in the ttk submodule require a different styling format than the same widgets in tkinter, so keeping to the main library will make much of the code that follows accessible to folks working in Python 2.7. For more on the ttk submodule, see TkDocs. (Note that in Python 2.7, the tkinter module has a slightly different name. It is capitalized: Tkinter. And the ttk submodule is not available, as far as I'm aware.) With that, let's add our imports.


To aid in sanity checking, I like to configure a basic logger at the beginning of a project. If you're not familiar with the logging module, read up on it a bit in the docs. The idea is simple: we configure a logging object and then call it when we want to report info from the program while it's running. Of course, you might also consider just using print statements, but a logger can provide more information such as the time, the line number in the script, indicate levels of severity, in addition to providing a particular message. I'm also going to declare a 'title' variable with the name of the application.

So I define the title variable just underneath the import statements, then configure the logger inside the main function, and log a message indicating that the app has started up. This logger configuration logs the time, the line number, the level name and a message. For all the basic logging configuration options, see the docs.


Alright, let's get down to business. Tkinter works by running a tkinter.Tk() object in an event loop that can react to defined user inputs such as button clicks, mouse events, keyboard inputs and the like. In our application's main function, we will: 1) initialize our Tk() object as 'root', 2) configure the application's title by passing it to the root object, and then 3) pass the root object to our application's main view.

The main view will be a Python class that we use to configure, structure and build our interface. We thus add four lines to our main function. Notice that when I pass the root object to the view, I'm assigning the return to a variable. I have seen inconsistent explanation of this idiom, but it appears to be standard practice.


Of course, running the script above will result in an error since we haven't defined our View() class yet. So let's do that now. The View class will accept one argument, the root object. In the init() method, we'll define the root argument parameter, then assign the root object to a class instance variable appropriately named 'root'.


If you run the script above, you'll see your log message written to the terminal and then the program will appear to hang. But if everything's working properly, that's just what we would expect because it has launched the tkinter root window, which should be running its main loop. If you don't see the application's window, cycle through the applications that are open on your desktop to find it. By default, the tkinter root window does not take focus, and may be hidden behind them (it's nice like that). In your system's list of running applications, you should also see an icon indicating that the Python app is up and active. Here's what the empty root window looks like on my system, with a default size of about 200x220px:
If you can't seem to find the window even though the app is clearly running, it may be that the window is being drawn, but is not big enough to be seen. In that case, try setting a minimum size to the root window. Just after you pass in the title to the root object, add a line such as the following: root.minsize(300, 300).

To exit, you can just close out the window. Notice, if you press <Control-c> on the keyboard when in the terminal, the program does not stop running until focus is returned to the root window itself. With that, we've got the seed for our application in place.

In the next article, we'll begin the process of building it out, widget by widget. You can find the second article in the series at the link.

2 comments: