Barra de progreso (Progressbar) en Tcl/Tk (tkinter)



Entre los controles que introduce el módulo ttk se encuentra Progressbar, una barra de progreso para indicar el estado de una operación. El control puede indicar la evolución de un proceso determinado (por ejemplo, la descarga de un archivo de internet) o bien representar simplemente que una operación se está ejecutando, en aquellos casos en los que el tiempo de espera se desconoce.

Barra de progreso determinada

Empecemos por un pequeño código que crea una ventana con una barra de progreso horizontal.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Barra de progreso en Tk")
        
        self.progressbar = ttk.Progressbar(self)
        self.progressbar.place(x=30, y=60, width=200)
        
        self.place(width=300, height=200)
        main_window.geometry("300x200")

main_window = tk.Tk()
app = Application(main_window)
app.mainloop()

Esto se ve más o menos así:

También podemos indicar vía el parámetro orient una orientación vertical:

        self.progressbar = ttk.Progressbar(self, orient=tk.VERTICAL)
        self.progressbar.place(x=30, y=30, height=160)

Y el resultado es el siguiente:

(El valor por defecto para orient es tk.HORIZONTAL).

Nótese que, según la orientación elegida, el largo del control es determinado por el argumento width (horizontal) o height (vertical). Para establecer el largo independientemente de la orientación puede usarse el parámetro length.

        self.progressbar = ttk.Progressbar(self, orient=tk.VERTICAL, length=160)
        self.progressbar.place(x=30, y=30)

Volviendo al código anterior, por defecto la barra de progreso está configurada para representar un porcentaje. Es decir, toma valores desde 0 (totalmente vacía) a 100 (totalmente completa). Establecemos el progreso vía la función step indicando un valor entre el rango indicado.

self.progressbar.step(50)

Por lo comentado, ahora el progreso se encuentra a la mitad:

Si se omite el argumento, el aumento equivale a 1.0 (nótese que la función puede tomar números de coma flotante).

Una particularidad en Tcl/Tk es que una barra de progreso no puede mostrarse “completa”. Cuando ésta alcanza su valor máximo, el progreso regresa a 0. Por esta razón el siguiente código muestra la barra vacía:

self.progressbar.step(100)

En su lugar, para mostrarla casi completa querrá utilizarse:

self.progressbar.step(99.9)

El parámetro maximum indica el valor máximo (100 por defecto).

        self.progressbar = ttk.Progressbar(self, maximum=200)
        self.progressbar.place(x=30, y=60, width=200)
        self.progressbar.step(50)

En este caso, dado que el valor máximo es 200, step(50) establece una cuarta parte.

El valor del control también puede ser manejado por una variable, como tk.IntVar, indicándola en el parámetro variable.

        self.progress = tk.IntVar()
        self.progressbar = ttk.Progressbar(self, variable=self.progress)
        self.progressbar.place(x=30, y=60, width=200)
        self.progress.set(50)

Barra de progreso indeterminada

Esta configuración es útil cuando queremos indicarle al usuario que algo está sucendiendo, pero no sabemos exactamente cuánto tiempo va a demorar. Usando el modo indeterminado, la barra de progreso muestra una pequeña “carga” que se desplaza de un lado hacia otro.

        self.progressbar = ttk.Progressbar(self, mode="indeterminate")
        self.progressbar.place(x=30, y=60, width=200)
        self.progressbar.start()

(El parámetro mode es por defecto "determinate").

Podemos alterar la velocidad de movimiento indicando los milisegundos (50 por defecto):

self.progressbar.start(10)

También es posible detener el movimiento vía:

self.progressbar.stop()

Ejemplos

Descargar un archivo vía HTTP y mostrar el progreso:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import ttk
from threading import Thread
from urllib.request import urlretrieve, urlcleanup


class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Barra de progreso en Tk")
        
        self.progressbar = ttk.Progressbar(self)
        self.progressbar.place(x=30, y=60, width=200)
        
        self.download_button = ttk.Button(
            self, text="Descargar", command=self.download_button_clicked)
        self.download_button.place(x=30, y=20)
        
        self.place(width=300, height=200)
        main_window.geometry("300x200")
    
    def download(self):
        url = "http://recursospython.com/inyector-de-dll-exe.rar"
        urlretrieve(url, "inyector-de-dll-exe.rar", self.download_status)
        urlcleanup()
    
    def download_button_clicked(self):
        # Descargar el archivo en un nuevo hilo.
        Thread(target=self.download).start()
    
    def download_status(self, count, data_size, total_data):
        if count == 0:
            # Establecer el máximo valor para la barra de progreso.
            self.progressbar.configure(maximum=total_data)
        else:
            # Aumentar el progreso.
            self.progressbar.step(data_size)


main_window = tk.Tk()
app = Application(main_window)
app.mainloop()



5 comentarios.

    • Le he seguido dando vueltas al step…
      Una sutileza sobre la barra de progreso con hilos, que siempre es confuso y me he vuelto medio loco. Pongo una solución que funciona, por si le puede servir a alguien:

      supongamos que tenemos una ttk.progressbar asi:

      self.mpb = ttk.Progressbar(self.ventana_derecha,orient =”horizontal”, length = 600, mode =”determinate”)
      self.mpb.pack(fill=”none”, expand=True)
      self.mpb[“maximum”] = 50

      ahora generamos un hilo de un proceso largo, y la manera de intercalar la visualización de la barra de progreso. (No lo he conseguido de otra manera):

      hilo = threading.Thread(target=lee_sarchivo, args=(str(archivo), ))
      hilo.start()
      while hilo.is_alive():
      …. self.mpb.step(1)
      …. self.frame.update_idletasks() # o tambien se podria self.raiz.update()
      …. #self.raiz.update()
      …. hilo.join(0.1)

      # si el hilo ha terminado paramos y reseteamos todo
      self.mpb.stop()
      self.mpb.pack_forget()

  1. un añadido a mi comentario anterior.

    probando con hilos:

    asi funciona mal:

    def progreso_barra(self):
    for i in range(self.num_elem):
    self.mpb.step(i)

    def hilo_barra(self):
    self.hilo = threading.Thread(target=self.progreso_barra)
    self.hilo.start()

    así funciona bien:

    def progreso_barra(self):
    for i in range(self.num_elem):
    self.mpb[‘value’] = i

    def hilo_barra(self):
    self.hilo = threading.Thread(target=self.progreso_barra)
    self.hilo.start()

    Probado en python 3.6.1 windows 10

  2. hola,

    una pregunta… Es sobre el metodo progressbar.step().
    En mis pruebas siempre se me queda el progreso de la barra sin completar. En cambio si utilizo el metodo self.progressbar[‘value’], sí me completa la barra.

    Pongo mi codigo de prueba:

    from tkinter import *
    from tkinter import ttk

    class ClasePrueba:
    def __init__(self, raiz):
    self.raiz = raiz
    self.num_elem = 5000

    self.inicia_frames()

    Button(self.ventana, text=”Inicia”, padx=30, pady=10, font=”size 12″,\
    command=self.progreso_barra). pack(anchor=CENTER)

    # generamos una barra de progreso
    self.mpb = ttk.Progressbar(self.ventana,orient =”horizontal”,\
    length = 600, mode =”determinate”)
    self.mpb.pack(fill=”none”, expand=True)
    self.mpb[“maximum”] = self.num_elem

    def inicia_frames(self):
    # creamos un frame central donde mostraremos el progreso
    self.ventana = Frame(self.raiz, background=”#F0F0F0″, pady=10, cursor = “arrow”)
    self.ventana.pack(expand=’yes’, fill=’both’)

    def progreso_barra(self):
    for i in range(self.num_elem):
    self.mpb[‘value’] = i
    #self.mpb.step(i)

    • Recursos Python says:

      Hola. Probablemente la confusión radica en que la función step() difiere ligeramente de asignar manualmente un valor vía la propiedad value. La primera indica incrementar el progreso en determinada cantidad mientras que la segunda implica establecer el valor de la barra de progreso. De modo que dos llamadas consecutivas a step(10) resultan en un progreso del 20% (por defecto), mientras que dos llamadas a configure(value=10) mantienen el progreso en 10%, porque aquélla incrementa y ésta establece.

      Un saludo.

Deja un comentario