Most useful programs will need to interact with their users. The program will ask the user for some data. The user will supply it and the program will give feedback to the user. An easier interface would show the user all that is expected in a single form and let the user fill it up and inform the program once he is done. The form is a rectangular area which is displayed on a monitor. We call it a 'window' which is managed by the window manager of the operating system but the user can control some aspects of it as per the designer of the program. A window will be composed of the three types of elements – display information, accept input and provide feedback. These elements are often called 'widgets'. The first type of widget is the 'label'. This is the information given by the program to let the user know what is expected. You can use different fonts for the labels and the labels can internationalised, that is, you may show the label in Hindi, Punjabi, Tamil or, even, English, depending on the user preference. The program is not bothered about the labels. The second type of widget is the box in which a user will supply the data to the program. Beauty of graphical user interfaces (GUI's) is that a large variety of widgets are available to make the data entry easier for the user or, often, to make the program look attractive. Common type of boxes include text box, buttons, radio buttons, check buttons, list boxes, each with its behaviour defined. E.g. only one button from a group of radio buttons can be selected while any number of buttons in a group of check boxes can be ticked. A text box allows any string to be entered while a list box will allow only a string from the list to be selected. Since there is no obviously best way to design and implement a GUI, a number of them are available. The 'standard' GUI included in Python is the 'Tkinter' module. It is relatively very easy and functional. A very interesting project was the 'anygui' which would choose the best GUI available on the user's machine and allow a common programming interface to be used. The project did not get beyond the proof of concept stage. So, one of the hardest decisions remains that which GUI do we use. It is not easy to switch even though all the concepts and the widgets may be similar. Getting Started with TkinterWe will use Tkinter as it is among the easiest. Let us create a simple form which has a label and a text box. The recent versions of Python allow interactive usage of Tkinter and that makes it even more fun. So, start typing in the Python interpreter. >>>from Tkinter import * >>>root = Tk() You should see a Tk window, with the usual window controls. (If the import statement fails, you will need to install Tkinter package.) >>>label= Label(root, text='Enter Your Note') >>>label.pack() >>>text = Text(root) >>>text.pack() After the packing, the window should show you the text widget, in which you can enter text. You can specify the background when creating the widget or later as follows: >>>text['background']='yellow' >>> done=Button(root, text='Done', background='green') >>>done.pack(side=RIGHT) >>>cancel=Button(root, text='Cancel', background='red') >>>cancel.pack(side=LEFT) You have now added two buttons – a green and a red one. We can give hints to Tkinter when packing for placement of the widgets. Try the mouse and you will notice that the buttons respond to mouse clicks. If only a simple string is needed, the widget is 'Entry' instead of 'Text'. Often one of more 'Frame' widgets are used to group various other widgets logically. You can play around with the gui layout. You can guess that 'foreground' is also an attribute of a widget. Width and length are also attributes whose interpretation can depend on the widget, e.g. for a text widget, it may be rows and columns while for a frame it may be in pixels. But what does our program do with all the data we have entered? Exactly what we have programmed, which is nothing! It now becomes more convenient to put the code in a file. Continuing the application discussed earlier, create a form which allows the entry of the item code and the borrower's short name. The result is then displayed on the form. Enter the following code in 'gui.py'. It is similar to the code above, except that you have created a class called 'App' which will contain the application code and 'inherits' the class Frame, i.e. it will have all the properties of a predefined class Frame in addition to what you define. The constructor, '__init__' creates the required window. You have introduced 2 frames to control the layout. You have also created the widgets as class variables by using the prefix 'self' as this will allow you to control the widgets as long as the window is active. The critical call is the 'mainloop' in the last line, which will wait for keystrokes or mouse events and process them. from Tkinter import * class App(Frame): def __init__(self,parent=None): 'Create the window layout' self.frame1 = Frame(parent) self.frame1.pack() self.draw_frame1(self.frame1) self.frame2=Frame(parent) self.frame2.pack() self.draw_frame2(self.frame2)
def draw_frame2(self, parent): self.done = Button(parent, text='Done') self.done.pack(side=LEFT) self.cancel = Button(parent, text='Exit', bg='red') self.cancel.pack(side=LEFT)
def draw_frame1(self, parent): self.item_code_label = Label(parent, text='Item Code') self.item_code_label.pack() self.item_code_entry = Entry(parent, width=40) self.item_code_entry.pack() self.borrower_label = Label(parent, text='Borrower Short Name') self.borrower_label.pack() self.borrower_entry = Entry(parent, width=40) self.borrower_entry.pack() self.message = Message(parent) self.message.pack()
app = App() app.mainloop() The program can then be run on the command line: $ python gui.py Adding Logic to the WidgetsIn your application of keeping track of the item borrowed, the user will enter an item code, the borrower's short name and when he is finished, he will click on the 'Done' button. The program will save the transaction. So, it seems reasonable that you will have to tell the 'Done' button that it should perform some logic and similarly for the 'Exit' button. The keyword 'command' allows you to do that. So, add the following code in gui.py in the 'App' class:
def save(self): print 'We should process the data here'
Our revised draw_frame2 will be: def draw_frame2(self, parent): self.done = Button(parent, text='Done',command=self.save) self.done.pack(side=LEFT) self.cancel = Button(parent, text='Exit', bg='red', command=self.quit) self.cancel.pack(side=LEFT) So far so good. But how do we access the data which has been entered? Using Variables in TkinterUsage of widgets is very similar to the objects and classes we have been using. So, you need to add a suitable property which will keep the data being entered. This is done by creating Tkinter variables, e.g. using StringVar. Create one for each of the two text widgets and associate each with the corresponding 'Entry' widget using 'textvariable' property. Do the same for the message widget. Then, it is simple. You use 'variable.get' and 'variable.set' methods to access the data in the code. The revised 'save' and 'draw_frame1' are:
def save(self): values = 'Item ' + self.item_code.get() + ', Borrower ' + self.borrower.get() self.msg.set(values)
def draw_frame1(self, parent): self.item_code_label = Label(parent, text='Item Code') self.item_code_label.pack() self.item_code = StringVar() self.item_code_entry = Entry(parent, textvariable=self.item_code) self.item_code_entry.pack() self.borrower_label = Label(parent, text='Borrower Short Name') self.borrower_label.pack() self.borrower = StringVar() self.borrower_entry = Entry(parent, textvariable=self.borrower) self.borrower_entry.pack() self.msg = StringVar() self.message = Message(parent,textvariable=self.msg ) self.message.pack() Now, you will use the code written earlier to store the entry in the database. Extending the Loans Application to Include GUIYou need to keep the user interface code separate from the database code. This is convenient and you can re-use the 'loans_v2.py' that you wrote last time. You will need to import the module and create the loans object: from Tkinter import * from loans_v2 import * <The App Class> my_loans = loans('friends.db','items.db') app = App() app.mainloop()
def save(self): icode = self.item_code.get() short_name = self.borrower.get() result = my_loans.loan_item(icode, short_name) self.msg.set(result)
def quit(self): my_loans.close() Frame.quit(self) Very few lines were affected in extending your code. Note that the 'quit' method closes the database and then calls the 'quit' method of the parent. What you have done so far is just a beginning. Enter the wrong data and python will throw a page full of error messages. You need to add exception handling to display a helpful message to the user. So, revise your 'save' method. It is simple.
def save(self): try: icode = self.item_code.get() short_name = self.borrower.get() result = my_loans.loan_item(icode, short_name) self.msg.set(result) except:
self.msg.set('Values
are not correct')
OK. You have caught the error, but how do we help the user find and enter the right values? You will need to add some more options. It will be a continuation of what you have already done. For finer control over the layout, you may opt for the 'grid' method instead of 'pack'. The following references can help you learn a lot more about Tkinter: http://www.pythonware.com/library/tkinter/introduction/index.htm http://www.python.org/doc/life-preserver The Complexity of GUI ProgrammingWhile it is easy to get started with GUI programming, it starts to get complex. This is not merely the aesthetic effort in making all the fields, variables, colours look nice. The number of events possible is phenomenal – mouse press, mouse release, key press, key release, combination of those events, dependence on the location of cursor, dependence on the movement of mouse, etc. Selecting and using an appropriate widget is also not easy. I have often been frustrated by sites insisting I use a calendar widget and will not allow the date information to entered by the keyboard or insisting that I use a virtual keyboard and not the real keyboard. There is still another complexity – that of our expectations. Consider a very simple case of 2 fields – a title and sex. It is reasonable to give a list of valid options. So, the first field can be one of [Mr, Ms, Dr] and the second field one of [M, F]. A good user interface will not allow inconsistent choices to be made. So, ideally, if a person has already chosen Mr or Ms, there should be no option for the sex field. However, the GUI does not compel a person to follow a particular sequence. So, a person may specify the sex first. In which case, the choices for the title field should be altered. The number of permutation/combinations become phenomenally large. Ensuring that each path works correctly can be extremely difficult. A GUI solution worth investigating is avoiding having to program the GUI. We can, instead, use a spreadsheet, whose interface is very well understood by users, to exchange data with the user. So, next month, we will investigate how to exchange data with OpenOffice using a Python program. |
Python For Friends >