The WCK ProgressBar Widget

Updated September 7, 2003 | June 15, 2003 | Fredrik Lundh

This widget implements a simple progress bar, using the WCK toolkit. Call the set method with a value between 0.0 and 1.0 to set the current progress. The widget redraws itself immediately, so you don’t have to call update or update_idletasks, if you don’t want to.

 
A simple progress bar
from WCK import Widget, FONT, BACKGROUND
import sys

if sys.platform == "win32":
    FOREGROUND = "systemhighlight"
else:
    FOREGROUND = "dark blue"

class ProgressBar(Widget):

    ui_option_width = 0 # 0=autosize
    ui_option_height = 0

    ui_option_foreground = FOREGROUND
    ui_option_background = BACKGROUND

    ui_option_font = FONT

    ui_option_padx = 2
    ui_option_pady = 2

    ui_option_relief = "sunken"
    ui_option_borderwidth = 2

    def __init__(self, master, **options):
        self.value = 0 # current value (0-100)
        self.ui_init(master, options)

    def ui_handle_config(self):
        # calculate default size
        w = int(self.ui_option_width)
        if w <= 0:
            w = 100
        h = int(self.ui_option_height)
        if h <= 0:
            f = self.ui_font(self.ui_option_foreground, self.ui_option_font)
            h = f.height
        w = w + 2*int(self.ui_option_padx)
        h = h + 2*int(self.ui_option_pady)
        return w, h

    def ui_handle_clear(self,  draw, x0, y0, x1, y1):
        pass

    def ui_handle_repair(self, draw, x0, y0, x1, y1):
        w, h = self.ui_size()
        bar = int(w*self.value) / 100
        draw.rectangle(
            (0, 0, bar, h), self.ui_brush(self.ui_option_foreground)
        )
        draw.rectangle(
            (bar, 0, w, h), self.ui_brush(self.ui_option_background)
        )

    def set(self, value):
        "Set a new progress value (0.0 to 1.0, wrapping)"

        value = int(value * 100 + 0.5)
        if value < 0 or value > 100:
            value = value % 100
        self.value = value
        # update immediately
        self.ui_handle_repair(self.ui_draw, 0, 0, 0, 0)

    def get(self):
        "Get the current progress value"

        return self.value / 100.0

Usage

Create the progress bar as usual. To update the bar, call the set method with a floating point value between 0.0 and 1.0 (100%).

progress = ProgressBar(master)
progress.pack()

count = 1000

for i in range(count):
    # do something...
    progress.set(float(i+1) / count)

Displaying a Progress Percentage

Here’s an enhanced version of the ProgressBar widget. This version displays the current progress over the bar, using contrasting colors.

 
An enhanced progress bar
from WCK import Widget, FONT, BACKGROUND
import sys

if sys.platform == "win32":
    FOREGROUND = "systemhighlight"
else:
    FOREGROUND = "dark blue"

class ProgressBar(Widget):

    ui_option_width = 0 # 0=autosize
    ui_option_height = 0

    ui_option_format = "%d%%" # None or empty=no label

    ui_option_foreground = FOREGROUND
    ui_option_background = BACKGROUND

    ui_option_font = FONT

    ui_option_padx = 2
    ui_option_pady = 2

    ui_option_relief = "sunken"
    ui_option_borderwidth = 2

    def __init__(self, master, **options):
        self.value = 0 # current value (0-100)
        self.size = None
        self.ui_init(master, options)

    def ui_handle_config(self):

        # calculate widget size
        w = int(self.ui_option_width)
        if w <= 0:
            w = 100
        h = int(self.ui_option_height)
        if h <= 0:
            f = self.ui_font(self.ui_option_foreground, self.ui_option_font)
            h = f.height

        w = w + 2*int(self.ui_option_padx)
        h = h + 2*int(self.ui_option_pady)

        return w, h

    def ui_handle_resize(self, width, height):

        self.size = width, height

    def ui_handle_clear(self,  draw, x0, y0, x1, y1):

        pass

    def ui_handle_repair(self, draw, x0, y0, x1, y1):

        if not self.size:
            return
        w, h = self.size

        bar = int(w*self.value) / 100

        try:
            text = self.ui_option_format % int(self.value)
        except TypeError:
            # malformed format string: use a reasonable default
            text = str(int(self.value)) + "%"

        if text:
            # calculate text geometry
            fgfont = self.ui_font(
                self.ui_option_foreground, self.ui_option_font
                )
            bgfont = self.ui_font(
                self.ui_option_background, self.ui_option_font
                )
            textwidth, textheight = fgfont.measure(text)
            tx0 = (w - textwidth) / 2
            tx1 = tx0 + textwidth
            ty0 = (h - textheight) / 2
        else:
            tx0 = tx1 = w # no text

        fg = self.ui_brush(self.ui_option_foreground)
        bg = self.ui_brush(self.ui_option_background)

        # draw the part to the left of the text
        if bar < tx0:
            draw.rectangle((0, 0, bar, h), fg)
            draw.rectangle((bar, 0, tx0, h), bg)
        else:
            draw.rectangle((0, 0, tx0, h), fg)

        if tx0 >= w:
            return # nothing left to draw

        # draw the text label, using a pixmap
        if tx0 < tx1:
            label = self.ui_pixmap(tx1 - tx0, h)
            if bar <= tx1:
                label.rectangle((0, 0, tx1 - tx0, h), bg)
                label.text((0, ty0), text, fgfont)
                if bar > tx0:
                    # text covers end of bar
                    bglabel = self.ui_pixmap(bar - tx0, h)
                    bglabel.rectangle((0, 0, bar - tx0, h), fg)
                    bglabel.text((0, ty0), text, bgfont)
                    label.paste(bglabel)
            else:
                label.rectangle((0, 0, tx1 - tx0, h), fg)
                label.text((0, ty0), text, bgfont)
            draw.paste(label, (tx0, 0))

        # draw the part to the right of the text
        if bar > tx1:
            draw.rectangle((tx1, 0, bar, h), fg)
            draw.rectangle((bar, 0, w, h), bg)
        else:
            draw.rectangle((tx1, 0, w, h), bg)

    def set(self, value):
        "Set a new progress value (0.0 to 1.0, wrapping)"

        value = int(value * 100 + 0.5)
        if value < 0 or value > 100:
            value = value % 100
        self.value = value

        # update immediately
        self.ui_handle_repair(self.ui_draw, 0, 0, 0, 0)

    def get(self):
        "Get the current progress value"

        return self.value / 100.0

The ui_handle_repair function contains a lot of code, but most of that code is there to draw the text label on top of the bar, and to do that without flicker. The widget solves this by drawing the progress bar in three steps:

  1. The portions to the left of the text label.
  2. The text label. This is drawn into a pixmap, which is copied to the screen when done. If the label covers the end of the progress bar, we’ll use an extra pixmap in order to draw different parts of the label in different colors.
  3. The portions to the right of the text label.
 

A Django site. rendered by a django application. hosted by webfaction.