Desarrollo de GUI de Python con Tkinter

Esta es la primera entrega de nuestra serie de varias partes sobre el desarrollo de GUI en Python usando Tkinter. Echa un vistazo a los enlaces a continuación para ver las siguientes partes de esta serie:...

Esta es la primera entrega de nuestra serie de varias partes sobre el desarrollo de GUI en Python usando Tkinter. Echa un vistazo a los enlaces a continuación para ver las siguientes partes de esta serie:

Introducción

Si está leyendo este artículo, existe la posibilidad de que sea una de esas personas que aprecian el software operado a través de una interfaz de línea de comandos simple. Es rápido, fácil con los recursos de su sistema y probablemente mucho más rápido de usar para un virtuoso del teclado como usted. Sin embargo, no es ningún secreto que si queremos llegar a una base de usuarios más amplia con nuestro software, ofrecer solo una solución de línea de comandos podría asustar a una gran parte de los usuarios potenciales. Para la mayoría de las personas, la forma más obvia de interactuar con un programa es usar una GUI, una interfaz gráfica de usuario.

Mientras usa una GUI, el usuario interactúa y manipula los elementos de la interfaz llamados widgets. Algunos widgets, como botones y casillas de verificación, permiten que el usuario interactúe con el programa. Otros, como ventanas y marcos, sirven como contenedores para otros widgets.

Hay muchos paquetes para crear GUI en Python, pero solo hay uno de esos paquetes que se considera un estándar de facto y se distribuye con todas las instalaciones predeterminadas de Python. Este paquete se llama Tkinter. Tkinter es el enlace de Python a Tk - un conjunto de herramientas GUI multiplataforma de código abierto.

Creación de su primera ventana

Como se mencionó anteriormente, Tkinter está disponible con instalaciones estándar de Python, por lo que independientemente de su sistema operativo, crear su primera ventana debería ser muy rápido. Todo lo que necesitas son 3 líneas de código:

1
2
3
4
5
import tkinter

root = tkinter.Tk()

root.mainloop()

Producción:

{.img-responsive}

Después de importar el paquete tkinter en la línea 1, en la línea 3 creamos el widget de la ventana principal (raíz) de nuestra aplicación. Para que el programa funcione correctamente, solo debe haber un widget de ventana raíz en nuestra interfaz y, debido a que todos los demás widgets estarán más abajo en la jerarquía que la raíz, debe crearse antes que cualquier otro widget.

En la línea 5, inicializamos el mainloop de la raíz. Gracias a esta línea, la ventana permanece en un bucle que espera eventos (como la interacción del usuario) y actualiza la interfaz en consecuencia. El bucle finaliza cuando el usuario cierra la ventana o se llama al método quit().

Agregar widgets simples a la ventana raíz

En el siguiente ejemplo, aprenderemos la filosofía general de dos pasos para crear widgets, que se puede aplicar a todos los widgets excepto a las ventanas. El primer paso es crear una instancia de la clase de un widget específico. En el segundo paso, tenemos que usar uno de los métodos disponibles para colocar el nuevo widget dentro de otro widget ya existente (un widget principal). El widget más simple que puede poner en su interfaz Tkinter es una etiqueta, que simplemente muestra algo de texto. El siguiente ejemplo crea un widget de etiqueta simple:

1
2
3
4
5
6
7
8
9
import tkinter

root = tkinter.Tk()

simple_label = tkinter.Label(root, text="Easy, right?")

simple_label.pack()

root.mainloop()

Producción:

{.img-responsive}

Creamos la instancia de la clase Label en la línea 5 del código anterior. En el primer argumento, apuntamos al widget principal deseado de la etiqueta, que en este ejemplo es nuestra ventana raíz. En el segundo argumento especificamos el texto que queremos que muestre la etiqueta.

Luego, en la línea 7, aplicamos un método para orientar nuestra etiqueta dentro de la ventana raíz. El método más simple para orientar widgets que ofrece Tkinter es pack(). La etiqueta es el único widget dentro de la ventana, por lo que simplemente se muestra en el medio de la ventana.

Aprenderemos más sobre cómo funciona en el siguiente ejemplo, cuando agreguemos otro widget a la ventana. Tenga en cuenta que el tamaño de la ventana se ajusta automáticamente al widget colocado dentro de ella.

Adición de un botón funcional

Ahora, agreguemos algo con lo que el usuario pueda interactuar. La opción más obvia es un simple botón. Pongamos un botón en nuestra ventana que nos brinde una forma adicional de cerrar nuestra ventana.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import tkinter

root = tkinter.Tk()

root.title("Hello!")

simple_label = tkinter.Label(root, text="Easy, right?")
closing_button = tkinter.Button(root, text="Close window", command=root.destroy)

simple_label.pack()
closing_button.pack()

root.mainloop()

Producción:

{.img-responsive}

En la línea 8, creamos nuestra instancia de clase Button de una manera muy similar a la que creamos nuestra etiqueta. Sin embargo, como probablemente pueda ver, agregamos un argumento de comando donde le decimos al programa lo que debería suceder después de hacer clic en el botón. En este caso, se llama al método destroy() que suena dramáticamente root, que cerrará nuestra ventana cuando se ejecute.

En las líneas 10 y 11 usamos de nuevo el método pack(). Esta vez podemos entenderlo un poco mejor, ya que ahora lo usamos para colocar dos widgets dentro de la ventana. Dependiendo del orden en que empaquemos nuestros widgets, el método simplemente los arroja uno encima del otro, centrados horizontalmente. La altura y el ancho de la ventana se ajustan a los tamaños de los widgets.

Probablemente hayas notado otra línea nueva. En la línea 5, especificamos el título de la ventana raíz. Desafortunadamente, el widget más ancho de nuestra interfaz no es lo suficientemente ancho para que el título de la ventana sea visible. Hagamos algo al respecto.

Controlar el tamaño de la ventana

Echemos un vistazo a tres nuevas líneas que nos permitirán cambiar el tamaño de nuestra ventana fácilmente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import tkinter

root = tkinter.Tk()

root.title("Hello!")

root.resizable(width="false", height="false")

root.minsize(width=300, height=50)
root.maxsize(width=300, height=50)

simple_label = tkinter.Label(root, text="Easy, right?")
closing_button = tkinter.Button(root, text="Close window", command=root.destroy)

simple_label.pack()
closing_button.pack()

root.mainloop()

Producción:

{.img-responsive}

En la línea 7 definimos si el usuario del programa debería poder modificar el ancho y el alto de la ventana. En este caso, ambos argumentos se establecen en "false", por lo que el tamaño de la ventana depende únicamente de nuestro código. Si no fuera por las líneas 9 y 10, dependería de los tamaños de los widgets orientados dentro de la ventana.

Sin embargo, en este ejemplo, usamos los métodos minsize y maxsize de root para controlar los valores máximos y mínimos del ancho y alto de nuestra ventana. Aquí definimos exactamente qué ancho y alto se supone que debe ser la ventana, pero te animo a que juegues con estas tres líneas para ver cómo funciona el cambio de tamaño según el tamaño de nuestros widgets y qué valores mínimos y máximos definimos.

Más información sobre la orientación de los widgets

Como probablemente ya haya notado, usar el método pack() no nos da demasiado control sobre dónde terminan los widgets después de empaquetarlos en sus contenedores principales. No quiere decir que el método pack () no sea predecible; es solo que, obviamente, a veces arrojar widgets en la ventana en una sola columna, donde un widget se coloca encima del anterior, no es necesariamente consistente. con nuestro sofisticado sentido de la estética. Para esos casos, podemos usar pack() con algunos argumentos ingeniosos, o usar grid(), otro método para orientar los widgets dentro de los contenedores.

Primero, démosle una oportunidad más a pack(). Modificando las líneas 15 y 16 del ejemplo anterior, podemos mejorar ligeramente nuestra interfaz:

1
2
simple_label.pack(fill="x")
closing_button.pack(fill="x")

Producción:

{.img-responsive}

De esta manera simple le decimos al método pack() que estire la etiqueta y el botón a lo largo del eje horizontal. También podemos cambiar la forma en que pack() arroja nuevos widgets dentro de la ventana. Por ejemplo, usando el siguiente argumento:

1
2
simple_label.pack(side="left")
closing_button.pack(side="left")

Producción:

{.img-responsive}

Podemos empaquetar widgets en la misma fila, comenzando desde el lado izquierdo de la ventana. Sin embargo, pack() no es el único método para orientar los widgets dentro de sus widgets principales. El método que da los mejores resultados es probablemente el método grid(), que nos permite ordenar los widgets en filas y columnas. Echa un vistazo al siguiente ejemplo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import tkinter

root = tkinter.Tk()

simple_label = tkinter.Label(root, text="Easy, right?")
another_label = tkinter.Label(root, text="More text")
closing_button = tkinter.Button(root, text="Close window", command=root.destroy)
another_button = tkinter.Button(root, text="Do nothing")

simple_label.grid(column=0, row=0, sticky="ew")
another_label.grid(column=0, row=1, sticky="ew")
closing_button.grid(column=1, row=0, sticky="ew")
another_button.grid(column=1, row=1, sticky="ew")

root.mainloop()

Producción:

{.img-responsive}

Para que este ejemplo sea un poco más claro, eliminamos las líneas que cambiaban el título y el tamaño de la ventana raíz. En las líneas 6 y 8 agregamos una etiqueta más y un botón más (tenga en cuenta que hacer clic en él no hará nada ya que no le hemos adjuntado ningún comando).

Sin embargo, lo más importante es que pack() fue reemplazado por grid() en todos los casos. Como probablemente pueda darse cuenta fácilmente, los argumentos columna y fila nos permiten definir qué celda de la cuadrícula ocupará nuestro widget. Tenga en cuenta que si define las mismas coordenadas para dos widgets diferentes, el que se representa más adelante en su código se mostrará encima del otro.

El argumento “pegajoso” probablemente no sea tan obvio. Con esta opción, podemos pegar los bordes de nuestros widgets a los bordes de sus respectivas celdas de cuadrícula: nnorte (superior), ssur (inferior), eeste (derecha) y Ooeste (izquierda). Lo hacemos pasando una cadena simple que contiene una configuración de letras n, s, e y w.

En nuestro ejemplo, pegamos los bordes de los cuatro widgets a los bordes este y oeste de sus celdas, por lo tanto, la cadena es ew. Esto da como resultado que los widgets se estiren horizontalmente. Puedes jugar con diferentes configuraciones de esas cuatro letras. Su orden en la cadena no importa.

Ahora que conoce dos métodos diferentes para orientar los widgets, tenga en cuenta que nunca debe mezclar grid() y pack() dentro del mismo contenedor.

Marcos

Las ventanas no son los únicos widgets que pueden contener otros widgets. Para que sus interfaces complejas sean más claras, generalmente es una buena idea segregar sus widgets en marcos.

Intentemos hacer eso con nuestros cuatro widgets simples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import tkinter

root = tkinter.Tk()

frame_labels = tkinter.Frame(root, borderwidth="2", relief="ridge")
frame_buttons = tkinter.Frame(root, borderwidth="2", relief="ridge")

simple_label = tkinter.Label(frame_labels, text="Easy, right?")
another_label = tkinter.Label(frame_labels, text="More text")

closing_button = tkinter.Button(frame_buttons, text="Close window", command=root.destroy)
another_button = tkinter.Button(frame_buttons, text="Do nothing")

frame_labels.grid(column=0, row=0, sticky="ns")
frame_buttons.grid(column=1, row=0)

simple_label.grid(column=0, row=0, sticky="ew")
another_label.grid(column=0, row=1, sticky="ew")

closing_button.pack(fill="x")
another_button.pack(fill="x")

root.mainloop()

Producción:

{.img-responsive}

Analicemos cuidadosamente el ejemplo que se muestra arriba. En las líneas 5 y 6 definimos dos nuevos widgets Frame. Obviamente, en el primer argumento apuntamos a su widget principal, que es la ventana raíz.

De forma predeterminada, los bordes de los marcos son invisibles, pero digamos que nos gustaría ver dónde están colocados exactamente. Para mostrar sus bordes, tenemos que darles un cierto ancho (en nuestro ejemplo, 2 píxeles) y el estilo de relieve (una especie de efecto 3D) en el que se dibujará el borde. Hay 5 estilos de relieve diferentes para elegir; en nuestro ejemplo, usamos ridge.

Las definiciones de Etiqueta y Botón también se modificaron ligeramente (líneas 8-12). Queríamos colocar nuestras etiquetas en nuestro marco frame_labels y nuestros botones en nuestro marco frame_buttons. Por lo tanto, tuvimos que reemplazar su padre anterior, root, con sus respectivos padres de marco nuevos.

En las líneas 14 y 15, orientamos los marcos dentro de la ventana raíz usando el método grid(). Luego, usamos el método grid() para orientar las etiquetas (líneas 17-18), y el método pack() para orientar los botones (líneas 20-21). Las etiquetas y los botones ahora están en contenedores separados, por lo que nada nos impide orientar los widgets usando diferentes métodos.

Ventanas de nivel superior {#ventanas de nivel superior}

Su interfaz no debe contener más de una ventana raíz, pero puede crear muchas ventanas que sean hijas de la ventana raíz. La mejor forma de hacerlo es usando la clase Toplevel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import tkinter

root = tkinter.Tk()

new_window = tkinter.Toplevel()
new_window.withdraw()

frame_labels = tkinter.Frame(root, borderwidth="2", relief="ridge")
frame_buttons = tkinter.Frame(root, borderwidth="2", relief="ridge")

simple_label = tkinter.Label(frame_labels, text="Easy, right?")
another_label = tkinter.Label(frame_labels, text="More text")

closing_button = tkinter.Button(frame_buttons, text="Close window", command=root.destroy)
window_button = tkinter.Button(frame_buttons, text="Show new window", command=new_window.deiconify)

frame_labels.grid(column=0, row=0, sticky="ns")
frame_buttons.grid(column=1, row=0)

simple_label.grid(column=0, row=0, sticky="ew")
another_label.grid(column=0, row=1, sticky="ew")

closing_button.pack(fill="x")
window_button.pack(fill="x")

root.mainloop()

En el ejemplo anterior, creamos nuestra nueva ventana en la línea 5. Debido a que una ventana es una entidad que no está anclada dentro de ningún otro widget, no tenemos que apuntar a su elemento principal ni orientarlo dentro de un elemento principal.

Nos gustaría mostrar la nueva ventana después de presionar un botón. La línea 5 lo muestra de inmediato, así que usamos el método withdraw() en la línea 6 para ocultarlo. Luego modificamos la definición del botón en la línea 15.

Además del nuevo nombre de variable y texto, el botón ahora ejecuta un comando: el método del objeto nueva_ventana, deiconify, que hará que la ventana vuelva a aparecer después de que el usuario haga clic en el botón ventana_botón.

Interfaz gráfica de usuario de Python: de la A a la Z ¿Quiere obtener más información sobre la creación de GUI en Python y las herramientas disponibles para usted, como Tkinter? ¡Intente tomar un curso de capacitación paso a paso para enseñarle cómo crear rápidamente GUI para sus aplicaciones!

Conclusiones

Como puede ver, con Tkinter puede crear GUI de manera fácil y rápida para usuarios no expertos de su software. La biblioteca está incluida en todas las instalaciones de Python, por lo que construir su primera ventana simple está a solo un par de líneas de código. Los ejemplos que se muestran arriba apenas arañan la superficie de las capacidades del paquete.

Sigue leyendo y mira la segunda parte de este tutorial de Tkinter, que te enseñará a crear gráficos complejos, intuitivos y bonitos. Interfaces de usuario. o.