1 """
2 Busybar widget
3 Rick Lawson
4 r_b_lawson at yahoo dot com
5 Heavily borrowed from ProgressBar.py which I got off the net but can't remember where
6 Feel free to add credits.
7 Comments by Stewart Midwinter:
8 I added a Quit button so you can stop the app.
9 I also set up a timer so that the BusyBar stops after a certain period.
10 Next, I added a button to bring up a BusyBar in a top-level window, similar to what
11 you might a process to do while it was, in fact, busy.
12 The top-level window is non-modal; it's left as an exercise for you, the reader, to change that if needed.
13
14
15 config options
16 --------------
17 BusyBar is derived from frame so all frame options are fine
18 Here are the options specific to this widget
19 fill - color of the progress box (the box that bounces back and forth)
20 boxWidth - width of progress box as a fraction of total widget width
21 interval - interval in ms at which the progress box is moved
22 ie, the shorter this is the faster the box will move
23 increment - fraction of widget width that the box moves during an update
24 ie, 0.05 means that box will move 5% of the total width at an update
25 text - text of message that is displayed in the middle of the widget
26 foreground - color of text message
27 font - font of text message
28 """
29
30 from Tkinter import *
31 import time
32
33 class BusyBar(Frame):
34 def __init__(self, master=None, **options):
35 # make sure we have sane defaults
36 self.master=master
37 self.options=options
38 self.width=options.setdefault('width', 100)
39 self.height=options.setdefault('height', 10)
40 self.background=options.setdefault('background', 'gray')
41 self.relief=options.setdefault('relief', 'sunken')
42 self.bd=options.setdefault('bd', 2)
43
44 #extract options not applicable to frames
45 self._extractOptions(options)
46
47 # init the base class
48 Frame.__init__(self, master, options)
49
50 self.incr=self.width*self.increment
51 self.busy=0
52 self.dir='right'
53
54 # create the canvas which is the container for the bar
55 self.canvas=Canvas(self, height=self.height, width=self.width, bd=0,
56 highlightthickness=0, background=self.background)
57 # catch canvas resizes
58 self.canvas.bind('<Configure>', self.onSize)
59
60 # this is the bar that moves back and forth on the canvas
61 self.scale=self.canvas.create_rectangle(0, 0, self.width*self.barWidth, self.height, fill=self.fill)
62
63 # label that is in the center of the widget
64 self.label=self.canvas.create_text(self.canvas.winfo_reqwidth() / 2,
65 self.height / 2, text=self.text,
66 anchor="c", fill=self.foreground,
67 font=self.font)
68 self.update()
69 self.canvas.pack(side=TOP, fill=X, expand=NO)
70
71 def _extractOptions(self, options):
72 # these are the options not applicable to a frame
73 self.foreground=pop(options, 'foreground', 'yellow')
74 self.fill=pop(options, 'fill', 'blue')
75 self.interval=pop(options, 'interval', 30)
76 self.font=pop(options, 'font','helvetica 10')
77 self.text=pop(options, 'text', '')
78 self.barWidth=pop(options, 'barWidth', 0.2)
79 self.increment=pop(options, 'increment', 0.05)
80
81 # todo - need to implement config, cget, __setitem__, __getitem__ so it's more like a reg widget
82 # as it is now, you get a chance to set stuff at the constructor but not after
83
84 def onSize(self, e=None):
85 self.width = e.width
86 self.height = e.height
87 # make sure the label is centered
88 self.canvas.delete(self.label)
89 self.label=self.canvas.create_text(self.width / 2, self.height / 2, text=self.text,
90 anchor="c", fill=self.foreground, font=self.font)
91
92 def on(self):
93 self.busy = 1
94 self.canvas.after(self.interval, self.update)
95
96 def of(self):
97 self.busy = 0
98
99 def update(self):
100 # do the move
101 x1,y1,x2,y2 = self.canvas.coords(self.scale)
102 if x2>=self.width:
103 self.dir='left'
104 if x1<=0:
105 self.dir='right'
106 if self.dir=='right':
107 self.canvas.move(self.scale, self.incr, 0)
108 else:
109 self.canvas.move(self.scale, -1*self.incr, 0)
110
111 if self.busy:
112 self.canvas.after(self.interval, self.update)
113 self.canvas.update_idletasks()
114
115 def pop(dict, key, default):
116 value = dict.get(key, default)
117 if dict.has_key(key):
118 del dict[key]
119 return value
120
121
122 if __name__=='__main__':
123 root = Tk()
124
125 def popup():
126 win=Toplevel()
127 win.title("I'm busy too!")
128 bb1=BusyBar(win, text='Wait for me!')
129 bb1.pack()
130 for i in range(0,30):
131 time.sleep(0.1)
132 bb1.update()
133 root.update()
134 bb1.of()
135 time.sleep(1)
136 win.destroy()
137
138 t = Text(root)
139 t.pack(side=TOP)
140 bb = BusyBar(root, text='Please Wait')
141 bb.pack(side=LEFT, expand=NO)
142 but = Button(root, text= 'Pop-up BusyBar', command=popup)
143 but.pack(side=LEFT, expand=NO)
144 q = Button(root, text= 'Quit', command=root.destroy)
145 q.pack(side=LEFT, expand=NO)
146 l = Label(root, text="I'm a status bar !")
147 l.pack(side=RIGHT)
148 bb.on()
149 root.update_idletasks()
150 for i in range(0,30):
151 time.sleep(0.1)
152 root.update()
153 bb.of()
154 root.mainloop()
155
Discussion [Stewart Midwinter] : The above code, as written, won't run on Python 1.5.2 since it uses the **kw idiom. Recalling that **kw means that we are passing a dictionary called kw as an argument, we can rewrite the first few lines of the init method as follows and then the code runs on Python 1.5.2:
1 def __init__(self, master, text=''):
2 # make sure we have sane defaults
3 self.master = master
4 self.options = options = {}
5 options['text'] = text
6 options['width'] = 200
7 options['height'] = 20
8 options['background'] = 'gray'
9 options['relief'] = 'sunken'
10 options['bd'] = 2
11 self.width=options['width']
12 self.height=options['height']
13 self.background=options['background']
14 self.relief=options['relief']
15
Screenshot:
[Mention some of the many other busybars available.]
[Explain idiomatic use of after rather than update_idletasks to keep the GUI responsive.]