Binding widgets to dynamic data

Tkinter provides a simple mechanism to bind a variable to a widget. However, it not possible to use an arbitrary variable. The variable must be subclassed from the Variable class; several are predefined and you could define your own, if necessary. Whenever the variable changes, the widget's contents are updated with the new value. Look at this simple example:

Example_6_4.py from Tkinter import * root = Tk()

class Indicator:

self.var = BooleanVar()

self.i = Checkbutton(master, text=label, variable = self.var, command=self.valueChanged) self.var.set(value) self.i.pack()

def valueChanged(self):

print 'Current value = %s' % ['Off','On'][self.var.get()]

ind = Indicator(root, label='Furnace On', value=1) root.mainloop()

This example defines self.var and binds it to the widget's variable; it also defines a callback to be called whenever the value of the widget changes. In this example the value is changed by clicking the checkbutton—it could equally be set programmatically.

Setting the value as a result of an external change is a reasonable scenario, but it can introduce performance problems if the data changes rapidly. If our GUI contained many widgets that displayed the status and values of components of the system, and if these values changed asynchronously (for instance, each value arrived in the system as SNMP traps), the overhead of constantly updating the widgets could have an adverse effect on the application's performance. Here is a possible implementation of a simple GUI to monitor the temperature reported by ten sensors.

Example_6_5.py from Tkinter import * import random root = Tk()

class Indicator:

def _init_(self, master=None, label='', value=0.0):

self.var = DoubleVar()

o

self.s = Scale(master, label=label, variable=self.var,

o

from_=0.0, to=300.0, orient=HORIZONTAL,

length=300)

self.var.set(value)

o

self.s.pack()

def setTemp():

slider = random.choice(range(10))

J4

value = random.choice(range(0, 300))

slist[slider].var.set(value)

O

root.after(5, setTemp)

6

slist = []

for i in range(10):

slist.append(Indicator(root, label='Probe %d % (i+1)))

setTemp() root.mainloop()

O

Code comments

o

First we create a Tkinter variable. For this example we store a real value: self.var = DoubleVar()

o

We then bind it to the Tk variable:

self.s = Scale(master, label=label, variable=self.var,

3

Then we set its value. This immediately updates the widget to display the

new value:

self.var.set(value)

4

The purpose of the setTemp function is to create a value randomly for one of the "sensors" at 5 millisecond intervals.

O

The variable is updated for each change: slist[slider].var.set(value)

6

Since after is a one-shot timer, we must set up the next timeout: root.after(5, setTemp)'

O

The call to setTemp starts the simulated stream of sensor information.

The display for this example is not reproduced here (the code is course). However, the display's behavior resembles Brownian motion

available online, of with widgets con-

PUTTING EVENTS TO WORK

109

stantly displaying new values. In a "real" application, the update rate would be annoying to the user, and it requires throttling to create a reasonable update rate. Additionally, constantly redrawing the widgets consumes an exceptionally high number of CPU cycles. Compare Example_6_5.py with the code for Example_6_6.py.

Example_6_6.py from Tkinter import * import random root = Tk()

class Indicator:

def _init_(self, master=None, label='', value=0.0):

self.var = DoubleVar()

self.s = Scale(master, label=label, variable=self.var, from_=0.0, to=300.0, orient=HORIZONTAL, length=300) self.value = value Q

self.var.set(value) self.s.pack()

self.s.after(1000, self.update) 2

def set(self, value):

self.value = value def update(self):

self.var.set(self.value) self.s.update_idletasks() self.s.after(1000, self.update)

def setTemp():

slider = random.choice(range(10)) value = random.choice(range(0, 300)) slist[slider].set(value)

root.after(5, setTemp)

slist.append(Indicator(root, label='Probe %d setTemp() root.mainloop()

Code comments

O In addition to the Tkinter variable, we create an instance variable for the widget's current value:

self.value = value

2 An after timeout arranges for the update method to be called in one second:

self.s.after(1000, self.update)

3 The class defines a set method to set the current value.

4 The update method sets the Tkinter variable with the current value, updating the widget's display. To redraw the widgets, we call update_idletasks which processes events waiting on the event queue.

self.s.update_idletasks()

5 Now, when the value changes, we set the instance variable:

slist[slider].set(value)

The display now updates the widgets once a second, which results in a more relaxed display and noticeably lowers the CPU overhead. You can optimize the code more, if you wish, to further reduce the overhead. For example, the widgets could be updated from a single update timeout rather than from a one-per-widget call.

Figure 6.2 Validating entry fields (Example_6_7.py)

+1 0

Post a comment