Apariencia y estilos de los controles en Tcl/Tk (tkinter)

Apariencia y estilos de los controles en Tcl/Tk (tkinter)

Ya tenemos nuestra aplicación de escritorio escrita en Python con Tk y ahora queremos hacer algunos cambios a la apariencia de la interfaz (botones, etiquetas, cajas de texto, etc.). Por defecto, todos los controles de una aplicación de tkinter tienen una apariencia determinada, que en la mayoría de los casos es bastante aceptable ya que conforma con los estándares del sistema operativo. No obstante, tenemos la posibilidad de alterar la apariencia por defecto de cualquier control (por ejemplo, poner en rojo el color del texto de un botón), de dos formas diferentes.

El hecho de que existan dos métodos distintos para modificar la estética de la interfaz radica en que en Tcl/Tk hay dos tipos de controles (widgets) que, un poco arbitrariamente, llamaremos controles clásicos y controles temáticos. Los controles clásicos son los controles originales de Tk, a los cuales desde Python accedemos vía el módulo tkinter (que generalmente se abrevia tk). Los controles temáticos se introdujeron en la versión 8.5 de Tk y están contenidos en el submódulo de Python ttk. La diferencia entre los dos tipos de controles es principalmente estética: los controles temáticos tienen una apariencia más moderna e incluyen un sistema de personalización de esa apariencia (que explicaremos en este artículo) mejor que el sistema tradicional. Véase la diferencia entre un botón creado a partir de la clase tk.Button (izquierda) y otro a partir de ttk.Button (derecha).

Botón clásico vs. temático

(La apariencia de los botones varía, desde luego, de un sistema operativo a otro).

Cuando queremos crear un botón podemos elegir cualquiera de las dos clases; pero, además, el módulo ttk (de themed tk) incluye controles nuevos que no tienen correlativos en los controles clásivos, y viceversa. He aquí la lista completa de controles:

Control clásico Control temático
tk.Button ttk.Button
tk.Canvas
ttk.Combobox
ttk.Checkbutton
tk.Entry ttk.Entry
tk.Frame ttk.Frame
tk.Label ttk.Label
ttk.LabeledScale
tk.LabelFrame ttk.LabelFrame
tk.Listbox
tk.Menu
tk.Menubutton ttk.Menubutton
tk.Message
ttk.Notebook
tk.OptionMenu ttk.OptionMenu
tk.PanedWindow ttk.PanedWindow
ttk.Progressbar
tk.Radiobutton ttk.Radiobutton
tk.Scale ttk.Scale
tk.Scrollbar ttk.Scrollbar
ttk.Separator
ttk.Sizegrip
tk.Spinbox ttk.Spinbox
tk.Text
ttk.Treeview

Cuando un control está disponible tanto en su versión clásica como temática, siempre conviene la temática. Las razones ya las mencionamos: la estética y el sistema de estilos son mejores y más modernos.

Veamos, ahora sí, cómo cambiar la apariencia de ambos tipos de controles. Empecemos por los controles temáticos.

Controles temáticos

El concepto central para comprender cómo cambiar la estética de los controles temáticos es el de estilo. Un estilo es una clase que contiene la información de la apariencia de un tipo de control. Para que un control en particular reciba la apariencia de un estilo, debemos indicar el nombre del estilo al momento de crear el control. Por defecto todos los controles temáticos tienen asignado un estilo.

Por ejemplo, creemos una ventana con un botón temático:

from tkinter import ttk
import tkinter as tk

ventana = tk.Tk()
ventana.config(width=300, height=200)
ventana.title("Estilos en Tk")

boton = ttk.Button(text="¡Hola, mundo!")
boton.place(x=40, y=50)

ventana.mainloop()

La apariencia del botón está determinada por el estilo que por defecto Tk aplica a todos los botones, razón por la cual el botón luce así:

Botón temático

Fondo gris, color de texto negro, contorno gris oscuro, espaciado de dos o tres píxeles, etc., son todas propiedades del estilo que tiene aplicado por defecto este botón. El nombre del estilo (todos los estilos deben tener un nombre) aplicado por defecto a los botones es TButton. Por lo general, los nombres de los estilos por defecto están constituidos por una «T» seguida del nombre del control, aunque hay ciertas excepciones. He aquí la lista completa:

Control temático Estilo por defecto
ttk.Button TButton
ttk.Checkbutton TCheckbutton
ttk.Combobox TCombobox
ttk.Entry TEntry
ttk.Frame TFrame
ttk.Label TLabel
ttk.LabeledScale TFrame
ttk.LabelFrame TLabelframe
ttk.Menubutton TMenubutton
ttk.Notebook TNotebook
ttk.PanedWindow TPanedwindow
ttk.Progressbar TProgressbar
ttk.Spinbox TSpinbox
ttk.Treeview Treeview (sin prefjio)

Desde nuestro código de Python podemos cambiar las propiedades de cada estilo. Así, por ejemplo, si modificamos el estilo TButton para que el color del texto sea rojo, todos los botones creados a partir de la clase ttk.Button tendrán el texto rojo.

s = ttk.Style()
s.configure("TButton", foreground="#ff0000")

boton = ttk.Button(text="¡Hola, mundo!")
boton.place(x=40, y=50)

Aquí primero creamos una instancia de ttk.Style, a través de la cual podremos configurar estilos existentes (como en este caso, TButton) o crear nuestros propios estilos. Vía el método configure() alteramos las propiedades de los estilos indicando sus nombres como argumentos. Así, especificamos el argumento foreground (color del texto) con el valor "#ff0000", que es el código hexadecimal para el color rojo. (Podemos obtener el código hexadecimal de cualquier color desde esta página). El resultado es el siguiente:

Botón rojo

No obstante, hacer cambios directamente en los estilos por defecto (TButton, TLabel, TEntry, etc.) de modo que todas las instancias creadas a partir de las clases correspondientes (ttk.Button, ttk.Label, ttk.Entry) hereden sus apariencias, es una práctica poco común. Por lo general queremos modificar el aspecto de controles particulares. Para esto, debemos crear primero un nuevo estilo y luego especificarlo en la creación de un control particular. Por ejemplo, si queremos que solamente un botón tenga el texto rojo:

s = ttk.Style()
s.configure("Peligro.TButton", foreground="#ff0000")

boton = ttk.Button(text="¡Hola, mundo!", style="Peligro.TButton")
boton.place(x=40, y=50)

boton2 = ttk.Button(text="Botón por defecto")
boton2.place(x=40, y=100)

Nótese que estamos creando un nuevo estilo: Peligro.TButton. Los nombres de los estilos son importantes: no pueden contener espacios y siguen la convención de nombramiento de las clases de Python (primera letra de cada palabra en mayúscula). Aquí elegimos el nombre Peligro para el nuevo estilo, pero agregamos el sufijo .TButton para indicar que se trata de un estilo que se aplicará a los botones (ttk.Button). De esta manera, los botones que tengan aplicado el estilo Peligro.TButton tendrán la apariencia de base del estilo TButton, pero se sumarán (o reemplazarán) las propiedades del nuevo estilo (por ejemplo, el estilo Peligro.TButton no define ningún color de fondo, por lo cual se aplicará aquel definido por defecto en TButton).

Para aplicar un estilo nuevo a una instancia particular de un control, indicamos el nombre del estilo vía el argumento style (línea 4 del código anterior). Nótese que el segundo botón, en el cual no hemos especificado el estilo, mantiene su color de texto por defecto:

Dos botones temáticos

Los nombres de las propiedades que pueden personalizarse (esto es, los argumentos que pueden pasarse al método configure()) con un estilo varían según el tipo de control de que se trate. foreground, como vimos, controla el color del texto; background controla el color del fondo; ya se trate de un botón, ya de cualquier otro control que tenga texto o fondo. Otras opciones comunes entre los controles son anchor, bordercolor, font, padding, justify. Consúltese la documentación de cada control para conocer las opciones particulares.

Además de configure(), existe el método map(), que permite personalizar la apariencia de un control en sus diversos estados. Los controles en Tk pueden estar en múltiples estados: por ejemplo, un botón puede estar deshabilitado, puede tener el mouse encima, puede estar siendo presionado o puede estar en un estado normal (ninguno de los anteriores). Cada uno de estos estados tiene un nombre: disabled, active, pressed, normal, respectivamente. La apariencia de un control en su estado normal o neutral se especifica vía configure(), como acabamos de hacer con el color del botón, mientras que para modificar el aspecto de un control en un estado en particular empleamos map(). Así, por ejemplo, vía el siguiente código hacemos que los botones que tengan nuestro estilo Peligro.TButton muestren el texto en naranja cuando el mouse está encima.

s = ttk.Style()
s.configure("Peligro.TButton", foreground="#ff0000")
s.map("Peligro.TButton", foreground=[("active", "#FFA500")])

boton = ttk.Button(text="¡Hola, mundo!", style="Peligro.TButton")
boton.place(x=40, y=50)

Este método, igual que configure(), toma como primer argumento el nombre de un estilo, y el resto de los argumentos por nombre indican las propiedades, pero cada propiedad debe ser una lista de tuplas en las cuales el primer elemento es el nombre de un estado ("active") y el segundo el valor que debe tener esa propiedad en tal estado (en este caso, un color, "#FFA500").

Botón temático rojo y naranja

Estos son los conceptos principales del sistema de estilos de Tk. Existen otros que se podrán estudiar en el caso particular de cada control. Veamos ahora cómo modificar la apariencia de los controles clásicos.

Controles clásicos

Modificar la apariencia de un control clásico es considerablemente más sencillo que usar el sistema de estilos que acabamos de ver, pero se trata de un método más desordenado y que no incentiva la reutilización de código. Sencillamente se pasan como argumento las propiedades que regulan la apariencia de un control al momento de crearlo.

import tkinter as tk

ventana = tk.Tk()
ventana.config(width=300, height=200)
ventana.title("Apariencia de controles clásicos")

boton = tk.Button(text="¡Hola, mundo!", foreground="#ff0000")
boton.place(x=40, y=50)

ventana.mainloop()

Botón clásico rojo

La deficiencia de este método es evidente: la configuración de la apariencia de la interfaz está desperdigada por todo el código. En cambio, el sistema de estilos permite centrar la personalización de la estética de todo un conjunto de controles en un único estilo.

Los nombres de las propiedades que controlan la apariencia son las mismas que para el sistema de estilos. Solo se agregan algunas relacionadas con estados particulares, que en los controles clásicos es mucho menos configurable. Así, por ejemplo, si queremos modificar el color del texto del botón cuando se encuentra en el estado active, usamos el argumento activeforeground.

boton = tk.Button(text="¡Hola, mundo!", foreground="#ff0000",
                  activeforeground="#FFA500")
boton.place(x=40, y=50)

Botón clásico activo

Se observará que aquí el estado active tiene un sentido diferente: es, más bien, cuando el botón está siendo presionado.

Cuando es necesario aplicar la misma estética a múltiples controles clásicos, puesto que no podemos usar los estilos (ttk.Styles solo se aplica a controles temáticos), lo mejor es crear una clase. Por ejemplo, podríamos emular el estilo Peligro.TButton de la sección anterior con una nueva clase BotonPeligro:

import tkinter as tk

class BotonPeligro(tk.Button):

    def __init__(self, *args, **kwargs):
        # Procurar no reemplazar los argumentos provistos.
        if "foreground" not in kwargs:
            kwargs["foreground"] = "#ff0000"
        if "activeforeground" not in kwargs:
            kwargs["activeforeground"] = "#FFA500"
        super().__init__(*args, **kwargs)

ventana = tk.Tk()
ventana.config(width=300, height=200)
ventana.title("Apariencia de controles clásicos")

boton = BotonPeligro(text="¡Hola, mundo!")
boton.place(x=40, y=50)

ventana.mainloop()

La personalización de controles clásicos no tiene mucha más complejidad. Siempre que sea posible es mejor usar los controles temáticos con sus respectivos estilos.

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.

12 comentarios.

  1. Yill Sanchez says:

    La información la hay en muchas partes, pero definitivamente es la forma como se presenta esa misma información lo que hace la gran diferencia. Excelentemente escrito, bien organizado y muy claro. Estoy comenzando y he aprendido un monto con este articulo.

Deja una respuesta