GUI programming in Python

article

There are quite a few GUI libraries for use with python, and all of them have their own pros and cons. It would not be possible to go over each of them in this blog, so we will consider only those that are prominent enough (read “mostly used”). Specifically we will be considering 2 libraries/frameworks to create GUIs in python. We will be taking a look at the following 2

  • TkInter
  • WxPython

This doesn't mean that the other frameworks available for use are not up to the mark. You can find a list of available Python GUI frameworks here: https://wiki.python.org/moin/GuiProgramming

The other available frameworks are good enough to use, but as I said, it would not be possible to touch each of them within the scope of this post. Please feel free to check the frameworks we are not considering here, and I am sure you will find more than one that fits your needs.

So, let's get going. For each of the libraries that we are going to consider, we will create a single screen application that allows a user to compute the price of some commodities that a customer buys. We will be using a MySQL database at the backend and the schema for that DB is as follows:

Table: commodities

serialId commodityName pricePerUnit

We will be using this single table to use with our application. Let us call the database 'guitest'. The SQL statements to create this database and the table are as follows. We are assuming you have some exposure to MySQL, but if that isn't the case, you can take a brief look at any one of the basic MySQL tutorials on the web, and it won't take you more than 30 mins to understand what we are doing here. Please note that in the SQL statements mentioned below, the content after the '#' character is either an explanation of what a command does, or, our intent to do something.

SQL Statements:

create database guitest; show databases; # This will display all the databases that are there in your mysql server installation. use guitest; create table commodities ( serialId int(11) auto_increment primary key, commodityName varchar(255) not null, pricePerUnit float(10,4) not null ); # Let us put some data in the table insert into commodities (commodityName, pricePerUnit) values ("bread", 20.00); insert into commodities ( commodityName, pricePerUnit) values ("milk", 23.00); insert into commodities ( commodityName, pricePerUnit) values ("yogurt", 19.50); insert into commodities ( commodityName, pricePerUnit) values ("curd", 11.00); insert into commodities ( commodityName, pricePerUnit) values ("ice cream", 15.00); # To check if your table has been populated appropriately, just execute the following SQL statement. Select * from commodities;

Please execute the above statements at the mysql prompt, and we would be good to go. Next, you will need to install the python extension for MySQL database, and we will be using the mysql_python module for this. Here I strongly suggest that you create a virtual environment for this stuff in this post. I have created one and I have named it 'guienv'. So, exit from the mysql prompt by typing \q and run the following command in the directory where you would like to place your virtual environment:

virtualenv guienv

That will create a virtual environment for you by the name 'guienv'. You may use any other name here, but for the sake of good programming practice, I would suggest you to keep a name that suits the purpose of our exercise. Next, in order to get into the virtual environment, please execute the following from the same directory where you placed the virtual environment 'guienv' (or whatever name you have given it):

source guienv/bin/activate

Furthermore, we are also assuming that you are using some flavour of linux system. In case you are using a windows computer, please replace the above command by the following:

source guienv/Scripts/activate.

From here on, I will be targetting linux machines, so windows users would require to find out the equivalent command syntax for their computers.

Once you have activated your virtualenv (named guienv), execute the following command to install the python extension for mysql database:

pip install mysql-python

That should install the MySQLdb module that you would be using in your programs. If you see any error here, you need to take care of it, may be by consulting stackoverflow. Explaining and putting a remedy for each error that might occur is not within the scope of this post, but one error that you might encounter is that the header files for python are not being found. To fix that, you would need to install python-dev(el), using either apt-get (ubuntu and debian) , yum install (for centos, fedora, suse) or download the RPM file for mysql-python and running the following command from the directory where your downloaded RPM file is stored:

rpm -ivh <python-header-file.rpm> # where <python-header-file.rpm> should be replaced by the name of the rpm file you downloaded.

TkInter:

This is python's de-facto GUI library. That is possibly the reason behind it wide usage. However, the widgets of this library do not have a very sleek look, and it is kind of odd looking (excuse me, but it is my personal opinion. You may have different opinions about it). That is also possibly the reason behind the parallel existence of all the other GUI libraries in Python Software Development. But first, let us delve a bit into some code that uses TkInter and demonstrate its basic capabilities. Let us create a TkInter window first:

use Tkinter use MySQLdb if __name__ == “__main__”: win = Tkinter.Tk() win.mainloop

The above code will create a small window. We will see how to display all the controls that we need in the window shortly.

Next, we would like to query the data from the database (guitest), and populate the window with a list widget, a text input widget, a label widget, and a button to display the cost of the item(s) selected from this list widget. The data from the database will be associated with the items in the list widget. The following code does this.

dbconn = MySQLdb.connect("localhost", "<username>", "password", "guitest") sql = “select serialId, commodityName, pricePerUnit from commodities” dbcursor = dbconn.cursor() dbcursor.execute(sql) rows = dbcursor.fetchall() # We will put the fetched data in a dictionary. The keys will be the commodity names, and # the values will be a list containing the serial Id and the commodity price field values. datadict = {} for row in rows: slId = row[0] commodity_name = row[1] price_per_unit = row[2] if not datadict.has_key(commodity_name): datadict[ commodity_name] = [ slId, price_per_unit] dbconn.close() # Done dealing with the DB, so we are closing this.

The following is the entire listing of the code to perform the desired task.

import os, sys, re, time import Tkinter as tk import MySQLdb frame, lw, num_commodities, response_label, commodities_list, commodities_dict = None, None, None, None, [], {} def compute_price(): global frame, num_commodities, response_label response_label = tk.Label(frame) selnum = lw.curselection() if not selnum or len(selnum) == 0: response_label = tk.Label(frame) response_label["text"] = "Please select a commodity" response_label.pack() return None cmdt_name = commodities_list[int(selnum[0])] cmdt_attrib_list = commodities_dict[cmdt_name] price = cmdt_attrib_list[1] count_cmdt = num_commodities.get() total_price = int(count_cmdt) * float(price) if response_label: response_label.destroy() response_label = tk.Label(frame) response_label["text"] = str(total_price) response_label.pack() if __name__ == "__main__": win = tk.Tk() # First, create a frame: frame = tk.Frame(win) scrollbar = None # Next, you could create a scrollbar if you wanted here... but for our simple case, we are not going into that trouble. #scrollbar=tk.Scrollbar(frame, bg='black') #scrollbar.pack(side='right', expand='yes', fill='y') # Manipulate the scrollbar here... I am leaving this as an exercise for you. dbconn = MySQLdb.connect("localhost", "<username>", "<password>", "guitest") sql = "select commodityName, serialId, pricePerUnit from commodities" dbcursor = dbconn.cursor() dbcursor.execute(sql) commodities_dict = {} allrecs = dbcursor.fetchall() for rec in allrecs: commodity_name = rec[0] commodity_id = rec[1] commodity_price = rec[2] commodities_dict[commodity_name] = [ commodity_id, commodity_price ] lw = tk.Listbox(frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar, height=5) indx = 0 commodities_list = commodities_dict.keys() for commname in commodities_list: lw.insert(indx, commname) indx += 1 lw.pack() prompt_label = tk.Label(frame, text="Enter Quantity") prompt_label.pack() num_commodities = tk.Entry(frame) num_commodities.pack() compute_btn = tk.Button(frame, text="Compute Price", command=compute_price) compute_btn.pack() frame.pack() win.mainloop()

Well, so what do we learn from this code?

The first thing to note here is the way each widget is handled. There are 2 steps at least for each of the widgets: i) The widget is initialized using its constructor (for example “prompt_label = tk.Label(....)”), then comes an optional processing stage, where you might want to add or modify an attribute of the widget (for example 'response_label["text"] = str(total_price)') and finally, calling the pack() method. The pack method finalizes the widget for being displayed, and beyond that, you would no longer be able to change that widget (you may be able to destroy it, though). In case you wish to change a widget after pack() has been called, you would need to use “StringVar” , which will allow the value displayed on a widget changed as soon as the variable associated with that value changes. In the above code, we could have done the following to serve our purpose:

price_var = tk.StringVar() response_label = tk.Label(frame, textvariable=price_var) response_label.pack()

In the above case, every time price_var changes, the change would be reflected in the Label widget automatically.

The rest of the code is quite simple. We basically create a DB connection, extract the data from it, create a data structure to hold the data and manipulate it using the widgets we create in the code.

By the way, the last statement, “win.mainloop()”, is a special statement and it is needed for the widgets to stay on the screen as we perform operations on them. It is called the eventloop, and is a must in order to make the whole thing work.

There are a lot of nitty-gritty's in the topic that I would have loved to cover, but I will keep that for a later post, may be focussing on only this specific library/framework.

Here is a screengrab of the widget at work:

article

WxPython:

Firstly, in order to use this library, you would need to install it. Now, how do we do it? Well, I can think of 2 ways on a linux system, and one of them is applicable to the windows environment as well. One way to do it is to install it using pip. However, this method doesn't always succeed. During writing this post, I made a python virtual environment (called blogenv) and I was trying to install this using pip, but it was failing pretty nastily. The issue was some incompatibility between certain libraries in my virtual env. Hence, what I did was download the wxWidgets-3.0.4.tar.bz2 entity, ran bunzip2 on it (bunzip2 wxWidgets-3.0.4.tar.bz2), which opened it up as a tar archive.

Next I ran “tar xvf wxWidgets-3.0.4.tar” to untar it, and then entered the source directory. The following steps were simple - “./configure”, followed by “make” and finally “make install”. That set up the library alright, but the flipside of it is that it was installed in my global python environment, not on my virtual environment.

However, it serves the purpose. I could have taken some more time to figure out the incompatible libraries in my virtualenv and eliminate them to install it using pip, but since it is not a production level project, I simply didn't do it.

WxWidgets is a very powerful cross platform library for GUI programming using C++ and wxPython is an extension of it. The richness of this library partly lies in the fact that the widgets created are native to the platform on which it is run. Basically, it means that if you run your wxWidgets/wxPython program on a Windows system, the widgets will be Microsoft Windows style, but if you run the same program on a Ubuntu or Debian Linux system (or, for that matter, any other flavour of linux or unix system), your widgets will look like normal Ubuntu/Debian widgets

I have done multiple projects using this library (using both python and C++), and the clients were always happy with the product. This makes it very popular among developers as a happy client means more business.

Anyway, now let us get our hands dirty with some code. We will be creating the same application we created using Tkinter, and so we will need a widget that can hold all those components.

“wx” has an “Application” class and in order to create any widget, we need to instantiate it. Next, we have “Frame”s and “Panel”s to utilize space in widgets. Let us look at the following code:

import os, sys import wx app = wx.App() frm = wx.Frame(None, title = "Comodities List", size = (600,400)) '''

The first argument to the Frame widget is the parent widget of the frame. In our case here, the Frame doesn't have a parent widget, so it is “None”.

The arguments to the Frame widget are keyword arguments. There is an argument named Id: however, it is not necessary to assign any value to it. In case you do not assign any value, it will take -1 as a default value.

The “title” argument is very important, however. This will contain the text that will be displayed on the widget's title bar. So we supply it with the string “Commodities List”.

The “size” parameter sets the size of the widget. The format is (length,breadth). For our purposes, 600 by 400 should be enough. '''

I am putting most of my explanations as comments in the code because that is the context in which you need to understand what we are doing. I will follow this method in this post, but occasionally, I will come out of it and discuss the logic behind what we are trying to do. I will also give you a high level view of the entire problem, so that you have the right perspective.

So now that we have created a “Frame” widget, what do we do with it? Of course, we would like to add some more widgets in it so that we can compute the price of commodities, as we did in our last example. I understand it is not a very interesting prospect, but let us just do it once for the sake of being initiated to this amazing library. Later on, after you have read through this post (I hope you will, though some might start snoring while reading this), you can take up more interesting projects wherein you can use the little knowledge that you might have gained by reading this.

The next thing that we are going to do is to add a “Panel” element. However, this is not necessary as we can add widgets to the “Frame” widget directly. The “Panel” widget actually allows us to arrange the other widgets and manage their alignment, something that the “Frame” widget” is not so good at.

pnl = wx.Panel(frm) ''' The “pnl” object is the Panel widget and we created it inside the Frame widget so that we can put other widgets in it. If you are in a hurry, you may jump to the next chunk of the code below. '''

Now, inside the panel widget (“pnl”), we need to put a list or dropdown list, a text entry widget, a button widget with some code to execute on clicking it, and a label widget. Let's do that in code now:

lstbox = wx.ListBox(pnl, name='commoditieslist') # The parent widget is the “Panel” widget referred to as “pnl”.

The final version of the entire code is as follows. Please take a good look at it before reading any further. Later, I will go over some parts of the code that are critical to the understanding of wxPython, and I will try to explain exactly what is going on.

import os, sys import wx import MySQLdb lbl_answer, lstbox, txt_ctrl, commodities_dict, response_label, commodities_list, commodities_dict = None, None, None, None, None, [], {} def get_data_from_db(username, password, hostname, dbname): dbconn = MySQLdb.connect(hostname, username, password, dbname) sql = "select commodityName, serialId, pricePerUnit from commodities" dbcursor = dbconn.cursor() dbcursor.execute(sql) commodities_dict = {} allrecs = dbcursor.fetchall() for rec in allrecs: commodity_name = rec[0] commodity_id = rec[1] commodity_price = rec[2] commodities_dict[commodity_name] = [ commodity_id, commodity_price ] return commodities_dict def compute_price(event): global lstbox, txt_ctrl, commodities_dict, response_label, commodities_dict, lbl_answer selected_entry = lstbox.GetStringSelection() commodity_price = commodities_dict[selected_entry][1] count_commodity = txt_ctrl.GetValue() total_cost = float(commodity_price) * float(count_commodity) lbl_answer.SetLabel(str(total_cost))

Please DO NOT write global code. It becomes very cumbersome, if not absolutely impossible, to import such code elsewhere.

if __name__ == "__main__": app = wx.App() frm = wx.Frame(None, title = "Comodities List", size = (600,400)) pnl = wx.Panel(frm) lbl = wx.StaticText(pnl, label = "Select a Commodity", pos=(280,60), style=wx.ALIGN_CENTER) lstbox = wx.ListBox(pnl, name='commoditieslist', pos=(250,100), size=(160, 120)) commodities_dict = get_data_from_db("root", "spmprx", "localhost", "guitest") commodities_list = [] for k in commodities_dict.keys(): commodities_list.append(k) lstbox.InsertItems(commodities_list, 0) lbl = wx.StaticText(pnl, label = "", pos=(280,250), style=wx.ALIGN_CENTER) lbl = wx.StaticText(pnl, label = "Enter Quantity", pos=(280,260), style=wx.ALIGN_CENTER) txt_ctrl = wx.TextCtrl(pnl, value="", pos=(280,280)) btn_compute = wx.Button(pnl, label="Compute", pos=(280,310)) btn_compute.Bind(wx.EVT_BUTTON, compute_price) lbl_answer = wx.StaticText(pnl, label = "", pos=(280,340), style=wx.ALIGN_CENTER) frm.Show(True) app.MainLoop()

The widget looks like the following:

article

Well, first we create the “app”object that drives the event loop. Hope you are already familiar with this. Even in TkInter, we were doing something similar. Next, we create a frame and a panel widget within the frame for the reasons that we have already discussed above. Within the panel widget we include a TextCtrl, a Button and a couple of StaticText widgets. The StaticText widgets allow us to write text that will be displayed on the GUI but it cannot be changed by the user of the application. It is mainly used to write instructions and labels. The TextCtrl is a widget that provides an editable text field and it allows the user to enter inputs. A word of caution here: The programmer should write code that scans the user input and sanitizes it before using it anywhere in the program. You wouldn't want to trust everyone in this world.

The line “btn_compute.Bind(wx.EVT_BUTTON, compute_price)” actually binds a callback function with the Button widget. This allows the application to do some work when the user clicks on the button.

A Brief look at Kivy and PyGtk:

I once did a very small project using Kivy, and what fascinated me most is the richness of features it provides and the number of platform it supports. Kivy practically support all the well known and widely used platforms, both desktop as well as hand-held device interfaces (like android and iOS). Kivy is written in C, and it provides a python extension, and the calls that you make from python invoke the appropriate C routines. Hence it is fast. It is a very good candidate if you want to start a small to medium GUI project.

However, there are some drawbacks to Kivy too. Firstly, I normally prefer the UI/widgets to be native looking (i.e, widgets on MS Windows systems should look like MS windows widgets, etc), but Kivy doesn't provide you with that. True, that the widgets are not dull, but still, when a user is using an application on some platform, she/he would expect the app to display widgets that look native to that specific platform. Another drawback of Kivy is the application size. This can be a showstopper when you are creating apps for mobile phones which are android or iOS driven. The size of the application matters a lot in those scenarios, and hence, even though Kivy supports both platforms, you would not want to develop a large application using Kivy for those platforms.

PyGtk is a very stable and a veteran candidate in the world of GUI programming. It is built on top of the GTK framework (basically python binding of the Gtk library). It is object oriented and easy to start using it, but since it is a veteran, it has got a lot of nifty features and requires time to learn it. That is to say that the learning curve for it is a bit steep. Personally, I have tinkered with PyGtk (and Gtk in general using C++), and I have found it very suitable for large multi-interface projects which are enterprise class. You wouldn't bother to use it for small or medium projects, as it would be an overkill.

If you want to know more about it, you can find all about it at https://wiki.python.org/moin/PyGtk

Well, that's all I could cover here. May be, I will write another blog on python GUI programming using other libraries (which I didn't cover here, plus include PyGtk in it) some time later. I sincerely hope this helps someone.

Thanks for your patience if you have read this and have come to this line.