Botón (Button) en Tcl/Tk (tkinter)

Botón (Button) en Tcl/Tk (tkinter)



El botón es típicamente el control más común en una aplicación con interfaz gráfica. Un botón es un recuadro con un texto y/o una imagen que puede ser presionado por el usuario para ejecutar una operación. En Tk está representado por las clases tk.Button y ttk.Button (para la diferencia entre estas dos clases véase este artículo). Desde el punto de vista del código su utilización es bastante sencilla.

Como cualquier otro control de Tk, para obtener un botón debemos crear una instancia de la clase correspondiente:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.config(width=300, height=200)
root.title("Botón en Tk")

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

root.mainloop()

Aquí creamos una ventana con un botón cuyo texto, representado por el parámetro text, es ¡Hola, mundo!. El resultado es el siguiente:

Al posicionar el botón vía el método place(), si no especificamos, como en este caso, un ancho y alto (con los parámetros width y height), las dimensiones del botón son calucladas automáticamente por Tk para ajustarse al texto que le hemos asignado. (Para las dimensiones y posiciones de los controles en general en Tk véase Posicionar elementos en Tcl/Tk).

Dijimos que un botón puede contener un texto y/o una imagen. Por el momento trabajemos únicamente con texto, más abajo veremos cómo asignar una imagen. Pero un botón no es de mucha utilidad si al ser presionado por el usuario no ejecuta ninguna operación. Para darle funcionalidad a un botón, debemos crear una función y luego asignársela vía el argumento command. Por ejemplo, el siguiente código imprime ¡Hola, mundo! un mensaje en la consola cuando el usuario presiona el botón:

import tkinter as tk
from tkinter import ttk

def saludar():
    print("¡Hola, mundo!")

root = tk.Tk()
root.config(width=300, height=200)
root.title("Botón en Tk")

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

root.mainloop()

A través del argumento command indicamos en la creación del botón el nombre de la función que queremos que se ejecute cuando el usuario acciona el botón (en seguida veremos que el botón también puede accionarse por el teclado). Puesto que estamos pasando una referencia a la función, no se indican los paréntesis.

Resulta un tanto extraño que una aplicación de escritorio utilice print() para mostrar un mensaje. Cambiemos, mejor, el código para que utilice un cuadro de diálogo:

import tkinter as tk
from tkinter import messagebox, ttk

def saludar():
    messagebox.showinfo(message="¡Hola, mundo!", title="Saludo")

root = tk.Tk()
root.config(width=300, height=200)
root.title("Botón en Tk")

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

root.mainloop()

Es importante tener en cuenta que si ejecutamos una operación más o menos «pesada» (como descargar un archivo de la web) dentro de una función invocada por un botón, es probable que nuestra interfaz se congele mientras que dure la operación. Para sortear este inconveniente, véase Tareas en segundo plano con Tcl/Tk.

La función asociada a un botón no debe requerir ningún argumento. Sin embargo, en algunas ocasiones es útil pasar información a dicha función: por ejemplo, si se encuentra en otro módulo, para que este tenga a acceso a objetos del módulo principal. Una solución posible para esto es usar functools.partial().

import tkinter as tk
from functools import partial
from tkinter import messagebox, ttk

def saludar(ventana):
    messagebox.showinfo(message="¡Hola, mundo!", title="Saludo")
    ventana.title("Nuevo título")

root = tk.Tk()
root.config(width=300, height=200)
root.title("Botón en Tk")

boton = ttk.Button(text="¡Hola, mundo!", command=partial(saludar, root))
boton.place(x=50, y=50)

root.mainloop()

En este código, la función saludar(), además de mostrar un cuadro de diálogo, cambia el título de la ventana, cuya referencia recibe como argumento. En este caso, partial() es innecesario, porque root es un objeto global y por consiguiente accesible desde el anterior de la saludar(). No obstante, téngase en cuenta este patrón al dividir una aplicación grande en múltiples clases o archivos de Python.

Por defecto las funciones asociadas a los botones son invocadas por Tk cuando el usuario presiona con el mouse sobre ellos, pero podemos indicar asimismo que un botón debe accionarse con la tecla Enter.

import tkinter as tk
from tkinter import messagebox, ttk

def saludar():
    messagebox.showinfo(message="¡Hola, mundo!", title="Saludo")

def saludar_enter(event):
    saludar()

root = tk.Tk()
root.config(width=300, height=200)
root.title("Botón en Tk")

boton = ttk.Button(text="¡Hola, mundo!", command=saludar)
boton.bind("<Return>", saludar_enter)
boton.place(x=50, y=50)

root.mainloop()

Aquí lo fundamental es el método bind(), que sirve para sociar un evento con una función; en este caso, el evento <Return> (así llama Tk a la tecla Enter) con la función saludar_enter(). Nótese que esta última función simplemente hace una llamada a nuestra original saludar(). ¿Por qué no usar la misma función para ambos eventos, mouse y teclado? El problema es que las funciones asociadas vía command no deben recibir ningún argumento, mientras que las asociadas a <Return> reciben un argumento con información del evento, del tipo tk.Event. Sin embargo, podríamos simplificar el código usando un argumento con valor por defecto en la función saludar():

import tkinter as tk
from tkinter import messagebox, ttk

def saludar(event=None):
    messagebox.showinfo(message="¡Hola, mundo!", title="Saludo")

root = tk.Tk()
root.config(width=300, height=200)
root.title("Botón en Tk")

boton = ttk.Button(text="¡Hola, mundo!", command=saludar)
boton.bind("<Return>", saludar)
boton.place(x=50, y=50)

root.mainloop()

Ahora respondemos a ambos eventos con la misma función, pero podemos determinar desde el interior de saludar() si se ha ejecutado en respuesta a una presión del mouse (event is None) o a una del teclado (event is not None).

Para que el botón pueda ser accionado presionando la tecla Enter, debe tener el foco del teclado. En una aplicación de escritorio podemos cambiar el foco del teclado entre los controles de la ventana usando la tecla Tab. Si por alguna razón queremos que nuestro botón no sea capaz de recibir el foco, deshabilitamos la opción takefocus:

# Este botón no puede recibir el foco del teclado.
boton = ttk.Button(text="¡Hola, mundo!", command=saludar, takefocus=False)

Un botón puede deshabilitarse durante la ejecución del programa (de modo que no permita al usuario presionarlo) vía la propiedad state, asignándole el valor tk.DISABLED.

def saludar():
    messagebox.showinfo(message="¡Hola, mundo!", title="Saludo")
    # Deshabilita el botón luego de mostrar el mensaje.
    boton["state"] = tk.DISABLED

O bien puede iniciar en estado deshabilitado, al crear la instancia:

boton = ttk.Button(text="¡Hola, mundo!", command=saludar, state=tk.DISABLED)

Para reaundar el botón luego de que ha sido deshabilitado, utilícese el valor tk.NORMAL:

boton["state"] = tk.NORMAL

Apariencia y estilos

Un botón, además de un texto, puede tener una imagen. Puede incluso tener una imagen sola, sin texto. Ya vimos que el texto del botón se indica vía el argumento text al momento de crear una instancia del mismo. Asimismo, para establecer una imagen se utiliza el argumento image, cuyo valor debe ser una instancia de tk.PhotoImage.

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.config(width=300, height=200)
root.title("Botón en Tk")

img_boton = tk.PhotoImage(file="buscar.png")
boton = ttk.Button(image=img_boton)
boton.place(x=50, y=50)

root.mainloop()

Botón con imagen en Tcl/Tk

Al crear una instancia de tk.PhotoImage, se indica el nombre o la ruta del archivo de imagen vía el argumento file. El formato recomendado es PNG. Téngase en cuenta que es menester mantener una referencia en memoria a la imagen (img_buton) para que Tk pueda mostrarla correctamente en el botón. Por eso, el siguiente código falla en mostrar la imagen:

# ¡Error! No se mantiene una referencia a la imagen en memoria.
boton = ttk.Button(image=tk.PhotoImage(file="buscar.png"))

Si se quiere mostrar simultáneamente un texto y una imagen en un botón, es necesario indicar en qué lugar debe aparecer la imagen respecto del texto. Para ello se emplea el argumento compbound.

img_boton = tk.PhotoImage(file="buscar.png")
boton = ttk.Button(text="Buscar archivo", image=img_boton, compound=tk.TOP)
boton.place(x=50, y=50)

tk.TOP indica que la imagen debe aparecer arriba del texto. Los otros valores posibles son tk.BOTTOM (abajo), tk.RIGHT (derecha) y tk.LEFT (izquierda).

Para cambiar otros aspectos de la apariencia de un botón es necesario crear un estilo. Véase Apariencia y estilos de los controles en Tcl/Tk para una explicación sobre el mecanismo de estilos en Tk. Así, para indicar el color del texto del botón, creamos un estilo y configuramos la propiedad foreground.

s = ttk.Style()
s.configure(
    "MyButton.TButton",
    foreground="#ff0000",
)
boton = ttk.Button(text="¡Hola, mundo!", style="MyButton.TButton")
boton.place(x=50, y=50)

Botón rojo en Tcl/Tk

Las propiedades del botón que pueden configurarse a través de estilos son:

  • foreground, color del texto;
  • background, color del fondo;
  • padding, espacio entre el texto y los extremos del botón;
  • font, tipo y tamaño de la fuente;
  • anchor, alineación del texto;
  • image, imagen del botón.

(La propiedad image puede estar definida en un estilo o pasarse como argumento en la creación de un botón).

Por ejemplo:

s = ttk.Style()
s.configure(
    "MyButton.TButton",
    foreground="#ff0000",
    background="#000000",
    padding=20,
    font=("Times", 12),
    anchor="w"
)
boton = ttk.Button(text="¡Hola, mundo!", style="MyButton.TButton")
boton.place(x=50, y=50)

Este código crea un botón con texto rojo (foreground="#ff0000"), fondo negro (background="#000000"), espaciado entre el texto y los extremos de 20 píxeles (padding=20), fuente Times New Roman 12 (font=("Times", 12)) y el texto alineado a la izquierda (anchor="w"). Otros valores posibles para anchor son "e", "s", y "n", que representan respectivamente derecha, abajo y arriba.

Con todo, las propiedades background y anchor son ingoradas por muchos sistemas operativos. En Windows, el texto de un botón solo puede estar en el centro. En cuanto a la propiedad background, que controla el color de fondo, para aquellos sistemas operativos donde es ignorada (en Windows solo altera el color del borde del botón) considérese como alternativa usar el control clásico tk.Button (en lugar de ttk.Button):

# Fondo negro y texto rojo.
boton = tk.Button(text="¡Hola, mundo!", foreground="#ff0000",
                  background="#000000")
boton.place(x=50, y=50)

Los controles clásicos no soportan estilos vía ttk.Styles, por lo cual las propiedades se indican directamente en la creación de la instancia.



Deja una respuesta