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:

busyBar2.png


[Mention some of the many other busybars available.]

[Explain idiomatic use of after rather than update_idletasks to keep the GUI responsive.]

tkinter: BusyBar (last edited 2010-07-26 11:59:12 by localhost)