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

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

Actualizado el 05/04/2022.

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.

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Barra de progreso en Tk")
progressbar = ttk.Progressbar()
progressbar.place(x=30, y=60, width=200)
main_window.geometry("300x200")
main_window.mainloop()

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:

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Barra de progreso en Tk")
progressbar = ttk.Progressbar(orient=tk.VERTICAL)
progressbar.place(x=30, y=30, height=160)
main_window.geometry("300x200")
main_window.mainloop()

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, orient=tk.VERTICAL)
        self.progressbar.place(x=30, y=30, height=160)
        self.place(width=300, height=200)
        main_window.geometry("300x200")

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

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.

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Barra de progreso en Tk")
progressbar = ttk.Progressbar(orient=tk.VERTICAL, length=160)
progressbar.place(x=30, y=30)
main_window.geometry("300x200")
main_window.mainloop()

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, orient=tk.VERTICAL, length=160)
        self.progressbar.place(x=30, y=30)
        self.place(width=300, height=200)
        main_window.geometry("300x200")

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

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.

# En alguna parte del código.
progressbar.step(50)

# En alguna parte del código.
self.progressbar.step(50)

Una vez ejecutado este método, el progreso se encontrará 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 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:

# Al llegar al valor máximo la barra se reinicia.
progressbar.step(100)

# Al llegar al valor máximo la barra se reinicia.
self.progressbar.step(100)

Por ende, para mostrar la barra (casi) completa será conveniente usar un valor cercano al máximo (que por defecto es 100):

# Mostrar la barra de progreso completa.
progressbar.step(99.9)

# Mostrar la barra de progreso completa.
self.progressbar.step(99.9)

El valor máximo de una barra de progreso puede establecerse vía el parámetro maximum:

progressbar = ttk.Progressbar(maximum=200)

self.progressbar = ttk.Progressbar(self, maximum=200)

En este caso, dado que el valor máximo es 200, una llamada a step(50) establecerá una cuarta parte del progreso.

El valor de una barra de progreso también puede ser manejado por una variable, como tk.IntVar, indicándola en el parámetro variable (similar al uso de tk.StringVar en las cajas de texto).

# Variable que controla el progreso de la barra.
progress = tk.IntVar()
progressbar = ttk.Progressbar(variable=progress)

# Variable que controla el progreso de la barra.
self.progress = tk.IntVar()
self.progressbar = ttk.Progressbar(self, variable=self.progress)

En este caso, para establecer el progreso de la barra de progreso se deberá llamar al método set() de la variable asignada:

# Establece el progreso en 50.
progress.set(50)

# Establece el progreso en 50.
self.progress.set(50)

Nótese que set() establece el valor del progreso de la barra, mientras que step() aumenta el valor del progreso. Dos llamadas a step(10) dejan el progreso en 20, pero dos llamadas a set(10) simplemente lo mantienen en 10.

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.

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Barra de progreso en Tk")
progressbar = ttk.Progressbar(mode="indeterminate")
progressbar.place(x=30, y=60, width=200)
# Iniciar el movimiento de la barra indeterminada.
progressbar.start()
main_window.geometry("300x200")
main_window.mainloop()

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, mode="indeterminate")
        self.progressbar.place(x=30, y=60, width=200)
        # Iniciar el movimiento de la barra indeterminada.
        self.progressbar.start()
        self.place(width=300, height=200)
        main_window.geometry("300x200")

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

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

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

# Mover la carga cada 10 milisegundos.
progressbar.start(10)

# Mover la carga cada 10 milisegundos.
self.progressbar.start(10)

Para detener el movimiento utilícese el método stop():

# Detener el movimiento de la barra indeterminada.
progressbar.stop()

# Detener el movimiento de la barra indeterminada.
self.progressbar.stop()

Ejemplos

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

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):
        # https://recursospython.com/codigos-de-fuente/inyector-de-dll-con-interfaz-grafica/
        url = "https://www.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()

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

7 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 una respuesta