





















































(For more resources related to this topic, see here.)
The topics covered here include:
When you specify a Tkinter variable as a textvariable for a widget (textvariable = myvar), the widget automatically gets updated whenever the value of the variable changes. However, there might be times when, in addition to updating the widget, you need to do some extra processing at the time of reading or writing (or modifying) the variable.
Tkinter provides a method to attach a callback method that would be triggered every time the value of a variable is accessed. Thus, the callback acts as a variable observer. The callback method is named trace_variable(self, mode, callback), or simply trace(self, mode, callback).
The mode argument can take any one of 'r', 'w', 'u' values, which stand for read, write, or undefined. Depending upon the mode specifications, the callback method is triggered if the variable is read or written.
The callback method gets three arguments by default. The arguments in order of their position are:
Note that the triggered callback function may also modify the value of the variable. This modification does not, however, trigger any additional callbacks.
Let's see a small example of variable tracing in Tkinter, where writing into the Tkinter variable into an entry widget triggers a callback function (refer to the 8.01 trace variable.py Python file available in the code bundle):
from Tkinter import * root = Tk() myvar = StringVar() def trace_when_myvar_written(var,indx,mode): print"Traced variable %s"%myvar.get() myvar.trace_variable("w", trace_when_myvar_written) Label(root, textvariable=myvar).pack(padx=5, pady=5) Entry(root, textvariable=myvar).pack(padx=5, pady=5) root.mainloop()
The description of the preceding code is as follows:
Now, every time you write into the entry widget, it modifies the value of myvar. Because we have set a trace on myvar, it triggers the callback method, which in our example, simply prints the new value into the console.
The code creates a GUI window similar to the one shown here:
It also produces a console output in IDLE, which shows like the following once you start typing in the GUI window:
Traced variable T Traced variable Tr Traced variable Tra Traced variable Trac Traced variable Traci Traced variable Tracin Traced variable Tracing
The trace on a variable is active until it is explicitly deleted. You can delete a trace using:
trace_vdelete(self, mode, callbacktobedeleted) The trace method returns the name of the callback method. This can
be used to get the name of the callback method
that is to be deleted.
When a GUI has more than one widget, a given widget can come under focus by an explicit mouse-click on the widget. Alternatively, the focus can be shifted to another given widget by pressing the Tab key on the keyboard in the order the widgets were created in the program.
It is therefore vital to create widgets in the order we want the user to traverse through them, or else the user will have a tough time navigating between the widgets using the keyboard.
Different widgets are designed to behave differently to different keyboard strokes. Let's therefore spend some time trying to understand the rules of traversing through widgets using the keyboard.
Let's look at the code of the 8.02 widget traversal.py Python file to understand the keyboard traversal behavior for different widgets. Once you run the mentioned .py file, it shows a window something like the following:
The code is simple. It adds an entry widget, a few buttons, a few radio buttons, a text widget, and a scale widget. However, it also demonstrates some of the most important keyboard traversal behaviors for these widgets.
Here are some important points to note (refer to 8.02 widget traversal.py):
Let's now discuss input data validation.
Most of the applications we have developed in this article are point and click-based (drum machine, chess, drawing application), where validation of user input is not required.
However, data validation is a must in programs like our phonebook application, where the user enters some data, and we store it in a database.
Ignoring the user input validation can be dangerous in such applications because input data can be misused for SQL injection. In general, any application where an user can enter textual data, is a good candidate for validating user input. In fact, it is almost considered a maxim not to trust user inputs.
A wrong user input may be intentional or accidental. In either case, if you fail to validate or sanitize the data, you may cause unexpected error in your program. In worst cases, user input can be used to inject harmful code that may be capable of crashing a program or wiping out an entire database.
Widgets such as Listbox, Combobox, and Radiobuttons allow limited input options, and hence, cannot normally be misused to input wrong data. On the other hand, widgets such as Entry widget, Spinbox widget, and Text widget allow a large possibility of user inputs, and hence, need to be validated for correctness.
To enable validation on a widget, you need to specify an additional option of the form validate = 'validationmode' to the widget.
For example, if you want to enable validation on an entry widget, you begin by specifying the validate option as follows:
Entry( root, validate="all", validatecommand=vcmd)
The validation can occur in one of the following validation modes:
Validation Mode |
Explanation |
none |
This is the default mode. No validation occurs if validate is set to "none" |
focus |
When validate is set to "focus", the validate command is called twice; once when the widget receives focus and once when the focus is lost |
focusin |
The validate command is called when the widget receives focus |
focusout |
The validate command is called when the widget loses focus |
key |
The validate command is called when the entry is edited |
all |
The validate command is called in all the above cases |
The code of the 8.03 validation mode demo.py file demonstrates all these validation modes by attaching them to a single validation method. Note the different ways different Entry widgets respond to different events. Some Entry widgets call the validation method on focus events while others call the validation method at the time of entering key strokes into the widget, while still others use a combination of focus and key events.
Although we did set the validation mode to trigger the validate method, we need some sort of data to validate against our rules. This is passed to the validate method using percent substitution. For instance, we passed the mode as an argument to our validate method by performing a percent substitution on the validate command, as shown in the following:
vcmd = (self.root.register(self.validate), '%V')
We followed by passing the value of v as an argument to our validate method:
def validate(self, v)
In addition to %V, Tkinter recognizes the following percent substitutions:
Percent substitutions |
Explanation |
%d |
Type of action that occurred on the widget-1 for insert, 0 for delete, and -1 for focus, forced, or textvariable validation. |
%i |
Index of char string inserted or deleted, if any, else it will be -1. |
%P |
The value of the entry if the edit is allowed. If you are configuring the Entry widget to have a new textvariable, this will be the value of that textvariable. |
%s |
The current value of entry, prior to editing. |
%S |
The text string being inserted/deleted, if any, {} otherwise. |
%v |
The type of validation currently set. |
%V |
The type of validation that triggered the callback method (key, focusin, focusout, and forced). |
%W |
The name of the Entry widget. |
These validations provide us with the necessary data we can use to validate the input.
Let's now pass all these data and just print them through a dummy validate method just to see the kind of data we can expect to get for carrying out our validations (refer to the code of 8.04 percent substitutions demo.py):
Take particular note of data returned by %P and %s, because they pertain to the actual data entered by the user in the Entry widget.
In most cases, you will be checking either of these two data against your validation rules.
Now that we have a background of rules of data validation, let's see two practical examples that demonstrate input validation.
Let's assume that we have a form that asks for a user's name. We want the user to input only alphabets or space characters in the name. Thus, any number or special character is not to be allowed, as shown in the following screenshot of the widget:
This is clearly a case of 'key' validation mode, because we want to check if an entry is valid after every key press. The percent substitution that we need to check is %S, because it yields the text string being inserted or deleted in the Entry widget. Accordingly, the code that validates the entry widget is as follows (refer to 8.05 key validation.py):
import Tkinter as tk class KeyValidationDemo(): def __init__(self): root = tk.Tk() tk.Label(root, text='Enter your name').pack() vcmd = (root.register(self.validate_data), '%S') invcmd = (root.register(self.invalid_name), '%S') tk.Entry(root, validate="key", validatecommand=vcmd,
invalidcommand=invcmd).pack(pady=5, padx=5) self.errmsg = tk.Label(root, text= '', fg='red') self.errmsg.pack() root.mainloop() def validate_data (self, S): self.errmsg.config(text='') return (S.isalpha() or S =='') # always return True or False def invalid_name (self, S): self.errmsg.config(text='Invalid characters n name canonly have
alphabets'%S) app= KeyValidationDemo()
The description of the preceding code is as follows:
Let's look at the code register(self, func, subst=None, needcleanup=1).
The register method returns a newly created Tcl function. If this function is called, the Python function func is executed. If an optional function subst is provided it is executed before func.
The previous example demonstrated validation in 'key' mode. This means that the validation method was called after every key press to check if the entry was valid.
However, there are situations when you might want to check the entire string entered into the widget, rather than checking individual key stroke entries.
For example, if an Entry widget accepts a valid e-mail address, we would ideally like to check the validity after the user has entered the entire e-mail address, and not after every key stroke entry. This would qualify as validation in 'focusout' mode.
Check out the code of 8.06 focus out validation.py for a demonstration on e-mail validation in the focusout mode:
import Tkinter as tk import re class FocusOutValidationDemo(): def __init__(self): self.master = tk.Tk() self.errormsg = tk.Label(text='', fg='red') self.errormsg.pack() tk.Label(text='Enter Email Address').pack() vcmd = (self.master. register(self.validate_email), '%P' ) invcmd = (self.master. register(self.invalid_email), '%P' ) self.emailentry = tk.Entry(self.master, validate ="focusout", validatecommand=vcmd , invalidcommand=invcmd ) self.emailentry.pack() tk.Button(self.master, text="Login").pack() tk.mainloop() def validate_email(self, P): self.errormsg.config(text='') x = re.match(r"[^@]+@[^@]+.[^@]+", P) return (x != None)# True(valid email)/False(invalid email) def invalid_email(self, P): self.errormsg.config(text='Invalid Email Address') self.emailentry.focus_set() app = FocusOutValidationDemo()
The description of the preceding code is as follows:
The code has a lot of similarities to the previous validation example. However, note the following differences:
This concludes our discussion on input validation in Tkinter. Hopefully, you should now be able to implement input validation to suit your custom needs.
Several input data such as date, time, phone number, credit card number, website URL, IP number, and so on have an associated display format. For instance, date is better represented in a MM/DD/YYYY format.
Fortunately, it is easy to format the data in the required format as the user enters them in the widget (refer to 8.07 formatting entry widget to display date.py). The mentioned Python file formats the user input automatically to insert forward slashes at the required places to display user-entered date in the MM/DD/YYYY format.
from Tkinter import * class FormatEntryWidgetDemo: def __init__(self, root): Label(root, text='Date(MM/DD/YYYY)').pack() self.entereddata = StringVar() self.dateentrywidget =Entry(textvariable=self.entereddata) self.dateentrywidget.pack(padx=5, pady=5) self.dateentrywidget.focus_set() self.slashpositions = [2, 5] root.bind('<Key>', self.format_date_entry_widget) def format_date_entry_widget(self, event): entrylist = [c for c in self.entereddata.get() if c != '/'] for pos in self.slashpositions: if len(entrylist) > pos: entrylist.insert(pos, '/') self.entereddata.set(''.join(entrylist)) # Controlling cursor cursorpos = self.dateentrywidget.index(INSERT) for pos in self.slashpositions: if cursorpos == (pos + 1): # if cursor is on slash cursorpos += 1 if event.keysym not in ['BackSpace', 'Right', 'Left','Up', 'Down']: self.dateentrywidget.icursor(cursorpos) root = Tk() FormatEntryWidgetDemo(root) root.mainloop()
The description of the preceding code is as follows:
Note that this method does not validate the date value and the user may add any invalid date. The method defined here will simply format it by adding forward slash at third and sixth positions. Adding date validation to this example is left as an exercise for you to complete.
This concludes our brief discussion on formatting data within widgets. Hopefully, you should now be able to create formatted widgets for a wide variety of input data that can be displayed better in a given format.
Many Tkinter widgets let you specify custom font specifications either at the time of widget creation or later using the configure() option. For most cases, default fonts provide a standard look and feel. However, should you want to change font specifications, Tkinter lets you do so. There is one caveat though.
When you specify your own font, you need to make sure it looks good on all platforms where the program is intended to be deployed. This is because a font might look good and match well on a particular platform, but may look awful on another. Unless you know what you are doing, it is always advisable to stick to Tkinter's default fonts.
Most platforms have their own set of standard fonts that are used by the platform's native widgets. So, rather than trying to reinvent the wheel on what looks good on a given platform or what would be available for a given platform, Tkinter assigns these standard platform-specific fonts into its widget, thus providing a native look and feel on every platform.
Tkinter assigns nine fonts to nine different names, which you can therefore use in your programs. The font names are as follows:
Accordingly, you can use them in your programs in the following way:
Label(text="Sale Up to 50% Off !", font="TkHeadingFont 20") Label(text="**Conditions Apply", font="TkSmallCaptionFont 8")
Using these kinds of fonts mark up, you can be assured that your font will look native across all platforms.
In addition to the above method on handling fonts, Tkinter provides a separate Font class implementation. The source code of this class is located at the following link: <Python27_installtion_dir>Liblib-tktkfont.py.
To use this module, you need to import tkFont into your namespace.(refer to 8.08 tkfont demo.py):
from Tkinter import Tk, Label, Pack import tkFont root=Tk() label = Label(root, text="Humpty Dumpty was pushed") label.pack() currentfont = tkFont.Font(font=label['font']) print'Actual :' + str(currentfont. actual ()) print'Family :' + currentfont. cget ("family") print'Weight :' + currentfont.cget("weight") print'Text width of Dumpty : %d' %currentfont. measure ("Dumpty") print'Metrics:' + str(currentfont. metrics ()) currentfont.config(size=14) label.config (font=currentfont) print'New Actual :' + str(currentfont. actual ()) root.mainloop()
The console output of this program is as follows:
Actual :{'family': 'Segoe UI', 'weight': 'normal', 'slant': 'roman',
'overstrike': 0, 'underline': 0, 'size': 9} Family : Segoe UI Weight : normal Text width of Dumpty : 43 Metrics:{'fixed': 0, 'ascent': 12, 'descent': 3, 'linespace': 15}
As you can see, the tkfont module provides a much better fine-grained control over various aspects of fonts, which are otherwise inaccessible.
Now that we have seen the basic features available in the tkfont module, let's use it to implement a font selector. The font selector would look like the one shown here:
The code for the font selector is as follows (refer to 8.09 font selector.py):
from Tkinter import * import ttk import tkFont class FontSelectorDemo (): def __init__(self): self.currentfont = tkFont.Font(font=('Times New Roman',12)) self.family = StringVar(value='Times New Roman') self.fontsize = StringVar(value='12') self.fontweight =StringVar(value=tkFont.NORMAL) self.slant = StringVar(value=tkFont.ROMAN) self.underlinevalue = BooleanVar(value=False) self.overstrikevalue= BooleanVar(value=False) self. gui_creator ()
The description of the preceding code is as follows:
The code represented here is a highly abridged version of the actual code (refer to 8.09 font selector.py). Here, we removed all the code that creates basic widgets, such as Label and Checkbuttons, in order to show only the font-related code:
def gui_creator(self): # create the top labels – code removed fontList = ttk.Combobox(textvariable=self.family) fontList.bind('<<ComboboxSelected>>', self.on_value_change) allfonts = list(tkFont.families()) allfonts.sort() fontList['values'] = allfonts # Font Sizes sizeList = ttk.Combobox(textvariable=self.fontsize) sizeList.bind('<<ComboboxSelected>>', self.on_value_change) allfontsizes = range(6,70) sizeList['values'] = allfontsizes # add four checkbuttons to provide choice for font style # all checkbuttons command attached to self.on_value_change #create text widget sampletext ='The quick brown fox jumps over the lazy dog' self.text.insert(INSERT,'%sn%s'% (sampletext,sampletext.upper()), 'fontspecs' ) self.text.config(state=DISABLED)
The description of the preceding code is as follows:
def on_value_change(self, event=None): try: self.currentfont.config(family=self.family.get(), size=
self.fontsize.get(), weight=self.fontweight.get(), slant=self.slant.get(),
underline=self.underlinevalue.get(), overstrike=self.overstrikevalue.get()) self.text.tag_config('fontspecs', font=self.currentfont) except ValueError: pass ### invalid entry - ignored for now. You can use a
tkMessageBox dialog to show an error
The description of the preceding code is as follows:
Computers only understand binary numbers. Therefore, all that you see on your computer, for example, texts, images, audio, video, and so on need to be expressed in terms of binary numbers.
This is where encoding comes into play. An encoding is a set of standard rules that assign unique numeral values to each text character.
Python 2.x default encoding is ASCII (American Standard Code for Information Interchange). The ASCII character encoding is a 7-bit encoding that can encode 2 ^7 (128) characters.
Because ASCII encoding was developed in America, it encodes characters from the English alphabet, namely, the numbers 0-9, the letters a-z and A-Z, some common punctuation symbols, some teletype machine control codes, and a blank space.
It is here that Unicode encoding comes to our rescue. The following are the key features of Unicode encoding:
Say you want to display a Hindi character on a Tkinter Label widget. You would intuitively try to run a code like the following:
from Tkinter import * root = Tk() Label( root, text = " भारतमेंआपकास्वागतहै " ).pack() root.mainloop()
If you try to run the previous code, you will get an error message as follows:
SyntaxError: Non-ASCII character 'xe0' in file 8.07.py on line 4, but no
encoding declared; see http://www.Python.org/peps/pep-0263.html for details.
This means that Python 2.x, by default, cannot handle non-ASCII characters. Python standard library supports over 100 encodings, but if you are trying to use anything other than ASCII encoding you have to explicitly declare the encoding.
Fortunately, handling other encodings is very simple in Python. There are two ways in which you can deal with non-ASCII characters.
The first way is to mark a string containing Unicode characters with the prefix u explicitly, as shown in the following code snippet (refer to 8.10 line encoding.py):
from Tkinter import * root = Tk() Label(root, text = u"भारतमेंआपकास्वागतहै").pack() root.mainloop()
When you try to run this program from IDLE, you get a warning message similar to the following one:
Simply click on Ok to save this file as UTF-8 and run this program to display the Unicode label.
In this article, we discussed some vital aspects of GUI programming form a common theme in many GUI programs.
Further resources on this subject: