Desarrollo de GUI de Python con Tkinter: Parte 2

D

Introducción

En la primera parte de la serie de tutoriales Pharosh.sh Tkinter, aprendimos cómo construir rápidamente interfaces gráficas simples usando Python. El artículo explica cómo crear varios widgets diferentes y colocarlos en la pantalla utilizando dos métodos diferentes ofrecidos por Tkinter, pero aún así, apenas tocamos la superficie de las capacidades del módulo.

Prepárese para la segunda parte de nuestro tutorial, donde descubriremos cómo modificar la apariencia de nuestra interfaz gráfica durante el tiempo de ejecución de nuestro programa, cómo conectar inteligentemente la interfaz con el resto de nuestro código y cómo obtener fácilmente la entrada de texto desde nuestros usuarios.

Opciones de cuadrícula avanzadas

En el último artículo, conocimos el grid() método que nos permite orientar los widgets en filas y columnas, lo que permite obtener resultados mucho más ordenados que usar el pack() método. Sin embargo, las cuadrículas tradicionales tienen sus desventajas, que se pueden ilustrar con el siguiente ejemplo:

import tkinter

root = tkinter.Tk()

frame1 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame2 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame3 = tkinter.Frame(root, borderwidth=2, relief="ridge")

frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=1, row=0, sticky="nsew")
frame3.grid(column=0, row=1, sticky="nsew")

label1 = tkinter.Label(frame1, text="Simple label")
button1 = tkinter.Button(frame2, text="Simple button")
button2 = tkinter.Button(frame3, text="Apply and close", command=root.destroy)

label1.pack(fill="x")
button1.pack(fill="x")
button2.pack(fill="x")

root.mainloop()

Salida:

El código anterior debería ser fácilmente comprensible para usted si siguió la primera parte de nuestro tutorial de Tkinter, pero hagamos un resumen rápido de todos modos. En la línea 3, creamos nuestro principal root ventana. En las líneas 5-7 creamos tres marcos: definimos que la raíz es su widget padre y que sus bordes recibirán un sutil efecto 3D. En las líneas 9-11 los marcos se distribuyen dentro de la ventana usando el grid() método. Indicamos las celdas de la cuadrícula que debe ocupar cada widget y usamos el sticky opción para estirarlos horizontal y verticalmente.

En las líneas 13-15 creamos tres widgets simples: una etiqueta, un botón que no hace nada y otro botón que cierra (destruye) la ventana principal: un widget por cuadro. Luego, en las líneas 17-19 usamos el pack() método para colocar los widgets dentro de sus respectivos marcos principales.

Como puede ver, tres widgets distribuidos en dos filas y dos columnas no generan un resultado estéticamente agradable. Aunque frame3 tiene toda su fila para sí mismo, y el sticky La opción hace que se estire horizontalmente, solo se puede estirar dentro de los límites de su celda de cuadrícula individual. En el momento en que miramos a la ventana, sabemos instintivamente que el marco que contiene button2 debe abarcar dos columnas, especialmente considerando la importante función que ejecuta el botón.

Bueno, afortunadamente, los creadores del grid() El método predijo este tipo de escenario y ofrece una opción de tramo de columnas. Después de aplicar una pequeña modificación a la línea 11:

import tkinter

root = tkinter.Tk()

frame1 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame2 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame3 = tkinter.Frame(root, borderwidth=2, relief="ridge")

frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=1, row=0, sticky="nsew")
frame3.grid(column=0, row=1, sticky="nsew", columnspan=2)

label1 = tkinter.Label(frame1, text="Simple label")
button1 = tkinter.Button(frame2, text="Simple button")
button2 = tkinter.Button(frame3, text="Apply and close", command=root.destroy)

label1.pack(fill="x")
button1.pack(fill="x")
button2.pack(fill="x")

root.mainloop()

Podemos hacer nuestro frame3 estirar todo el camino a lo largo de todo el ancho de nuestra ventana.

Salida:

El método place ()

Por lo general, al crear interfaces agradables y ordenadas basadas en Tkinter, place() y grid() Los métodos deben satisfacer todas sus necesidades. Aún así, el paquete ofrece uno más gerente de geometría – la place() método.

los place() El método se basa en los principios más simples de los tres administradores de geometría de Tkinter. Utilizando place() puede especificar explícitamente la posición de su widget dentro de la ventana, ya sea proporcionando directamente sus coordenadas exactas o haciendo que su posición sea relativa al tamaño de la ventana. Eche un vistazo al siguiente ejemplo:

import tkinter

root = tkinter.Tk()

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

button1 = tkinter.Button(root, text="B")
button1.place(x=30, y=30, anchor="center")

root.mainloop()

Salida:

En las líneas 5 y 6 especificamos que queremos que las dimensiones de nuestra ventana sean exactamente 300 por 300 píxeles. En la línea 8 creamos un botón. Finalmente, en la línea 9, usamos el place() método para colocar el botón dentro de nuestra ventana raíz.

Aportamos tres valores. Utilizando el x y y parámetros, definimos las coordenadas exactas del botón dentro de la ventana. La tercera opción, anchor, nos permite definir qué parte del widget terminará en el punto (x, y). En este caso, queremos que sea el píxel central de nuestro widget. De manera similar a la sticky opción de grid(), podemos utilizar diferentes combinaciones de n, s, e y w para anclar el widget por sus bordes o esquinas.

los place() al método no le importa si cometemos un error aquí. Si las coordenadas apuntan a un lugar fuera de los límites de nuestra ventana, el botón no se mostrará. Una forma más segura de usar este administrador de geometría es usar coordenadas relativas al tamaño de la ventana.

import tkinter

root = tkinter.Tk()

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

button1 = tkinter.Button(root, text="B")
button1.place(relx=0.5, rely=0.5, anchor="center")

root.mainloop()

Salida

En el ejemplo anterior, modificamos la línea 9. En lugar de coordenadas absolutas xey, ahora usamos coordenadas relativas. Configurando relx y rely a 0.5, nos aseguramos de que, independientemente del tamaño de la ventana, nuestro botón se colocará en su centro.

De acuerdo, hay una cosa más sobre el place() método que probablemente encontrará interesante. Combinemos ahora los ejemplos 2 y 4 de este tutorial:

import tkinter

root = tkinter.Tk()

frame1 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame2 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame3 = tkinter.Frame(root, borderwidth=2, relief="ridge")

frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=1, row=0, sticky="nsew")
frame3.grid(column=0, row=1, sticky="nsew", columnspan=2)

label1 = tkinter.Label(frame1, text="Simple label")
button1 = tkinter.Button(frame2, text="Simple button")
button2 = tkinter.Button(frame3, text="Apply and close", command=root.destroy)

label1.pack(fill="x")
button1.pack(fill="x")
button2.pack(fill="x")

button1 = tkinter.Button(root, text="B")
button1.place(relx=0.5, rely=0.5, anchor="center")

root.mainloop()

Salida:

En el ejemplo anterior, simplemente tomamos el código del ejemplo 2 y luego, en las líneas 21 y 22, creamos y colocamos nuestro pequeño botón del ejemplo 4 dentro de la misma ventana. Es posible que se sorprenda de que este código no cause una excepción, aunque claramente mezclamos grid() y place() métodos en la ventana raíz. Bueno, debido a la naturaleza simple y absoluta de place(), puedes mezclarlo con pack() y grid(). Pero solo si es necesario.

El resultado, en este caso, es obviamente bastante feo. Si el botón centrado era más grande, afectará la usabilidad de la interfaz. Ah, y como ejercicio, puede intentar mover las líneas 21 y 22 por encima de las definiciones de los marcos y ver qué sucede.

Por lo general, no es una buena idea usar place() en sus interfaces. Especialmente en GUI más grandes, establecer coordenadas (incluso relativas) para cada widget es mucho trabajo y su ventana puede volverse desordenada muy rápidamente, ya sea si su usuario decide cambiar el tamaño de la ventana, o especialmente si decide agregar más contenido a eso.

Configurar los widgets

La apariencia de nuestros widgets se puede cambiar mientras se ejecuta el programa. La mayoría de los aspectos cosméticos de los elementos de nuestras Windows se pueden modificar en nuestro código con la ayuda del configure opción. Echemos un vistazo al siguiente ejemplo:

import tkinter

root = tkinter.Tk()

def color_label():
    label1.configure(text="Changed label", bg="green", fg="white")

frame1 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame2 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame3 = tkinter.Frame(root, borderwidth=2, relief="ridge")

frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=1, row=0, sticky="nsew")
frame3.grid(column=0, row=1, sticky="nsew", columnspan=2)

label1 = tkinter.Label(frame1, text="Simple label")
button1 = tkinter.Button(frame2, text="Configure button", command=color_label)
button2 = tkinter.Button(frame3, text="Apply and close", command=root.destroy)

label1.pack(fill="x")
button1.pack(fill="x")
button2.pack(fill="x")

root.mainloop()

Salida:

En las líneas 5 y 6 agregamos una definición simple de una nueva función. Nuestro nuevo color_label() función configura el estado de label1. Las opciones que el configure() Las tomas de método son las mismas opciones que usamos cuando creamos nuevos objetos de widget y definimos aspectos visuales iniciales de su apariencia.

En este caso, al presionar el “botón Configurar” recién renombrado, cambia el texto, el color de fondo (bg) y el color de primer plano (fg – en este caso es el color del texto) de nuestro label1.

Ahora, digamos que agregamos otro botón a nuestra interfaz que queremos que se use para colorear otros widgets de manera similar. En este punto, el color_label() La función es capaz de modificar solo un widget específico que se muestra en nuestra interfaz. Para modificar varios widgets, esta solución requeriría que definamos tantas funciones idénticas como el número total de widgets que nos gustaría modificar. Esto sería posible, pero obviamente una solución muy pobre. Por supuesto, existen formas de alcanzar ese objetivo de una manera más elegante. Ampliemos un poco nuestro ejemplo.

import tkinter

root = tkinter.Tk()

def color_label():
    label1.configure(text="Changed label", bg="green", fg="white")

frame1 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame2 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame3 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame4 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame5 = tkinter.Frame(root, borderwidth=2, relief="ridge")

frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=0, row=1, sticky="nsew")
frame3.grid(column=1, row=0, sticky="nsew")
frame4.grid(column=1, row=1, sticky="nsew")
frame5.grid(column=0, row=2, sticky="nsew", columnspan=2)

label1 = tkinter.Label(frame1, text="Simple label 1")
label2 = tkinter.Label(frame2, text="Simple label 2")
button1 = tkinter.Button(frame3, text="Configure button 1", command=color_label)
button2 = tkinter.Button(frame4, text="Configure button 2", command=color_label)

button3 = tkinter.Button(frame5, text="Apply and close", command=root.destroy)

label1.pack(fill="x")
label2.pack(fill="x")
button1.pack(fill="x")
button2.pack(fill="x")
button3.pack(fill="x")

root.mainloop()

Salida:

Bien, ahora tenemos dos etiquetas y tres botones. Digamos que queremos “Configurar botón 1” para configurar “Etiqueta simple 1” y “Configurar botón 2” para configurar “Etiqueta simple 2” exactamente de la misma manera. Por supuesto, el código anterior no funciona de esta manera: ambos botones ejecutan el color_label() función, que todavía solo modifica una de las etiquetas.

Probablemente la primera solución que te viene a la mente es modificar el color_label() función para que tome un objeto widget como un argumento y lo configura. Luego podríamos modificar la definición del botón para que cada uno de ellos pase su etiqueta individual en la opción de comando:

# ...

def color_label(any_label):
    any_label.configure(text="Changed label", bg="green", fg="white")

# ...

button1 = tkinter.Button(frame3, text="Configure button 1", command=color_label(label1))
button2 = tkinter.Button(frame4, text="Configure button 2", command=color_label(label2))

# ...

Desafortunadamente, cuando ejecutamos este código, el color_label() se ejecuta la función, en el momento en que se crean los botones, lo que no es un resultado deseable.

Entonces, ¿cómo lo hacemos funcionar correctamente?

Pasar argumentos a través de expresiones Lambda

Las expresiones lambda ofrecen una sintaxis especial para crear los llamados funciones anónimas, definido en una sola línea. Entrar en detalles sobre cómo funcionan las lambdas y cuándo se utilizan normalmente no es el objetivo de este tutorial, así que centrémonos en nuestro caso, en el que las expresiones lambda definitivamente son útiles.

import tkinter

root = tkinter.Tk()

def color_label(any_label):
    any_label.configure(text="Changed label", bg="green", fg="white")

frame1 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame2 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame3 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame4 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame5 = tkinter.Frame(root, borderwidth=2, relief="ridge")

frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=0, row=1, sticky="nsew")
frame3.grid(column=1, row=0, sticky="nsew")
frame4.grid(column=1, row=1, sticky="nsew")
frame5.grid(column=0, row=2, sticky="nsew", columnspan=2)

label1 = tkinter.Label(frame1, text="Simple label 1")
label2 = tkinter.Label(frame2, text="Simple label 2")
button1 = tkinter.Button(frame3, text="Configure button 1", command=lambda: color_label(label1))
button2 = tkinter.Button(frame4, text="Configure button 2", command=lambda: color_label(label2))

button3 = tkinter.Button(frame5, text="Apply and close", command=root.destroy)

label1.pack(fill="x")
label2.pack(fill="x")
button1.pack(fill="x")
button2.pack(fill="x")
button3.pack(fill="x")

root.mainloop()

Salida:

Modificamos el color_label() funcionan de la misma manera que lo hicimos en el ejemplo abreviado anterior. Hicimos que aceptara un argumento, que en este caso puede ser cualquier etiqueta (otros widgets con texto también funcionarían) y lo configuramos cambiando su texto, color de texto y color de fondo.

La parte interesante son las líneas 22 y 23. Aquí, realmente definimos dos nuevas funciones lambda, que pasan diferentes argumentos a la color_label() función y ejecutarlo. De esta forma, podemos evitar invocar la color_label() funcionan en el momento en que se inicializan los botones.

Obtener información del usuario

Nos estamos acercando al final del segundo artículo de nuestra serie de tutoriales de Tkinter, por lo que en este punto, sería bueno mostrarle una forma de obtener información del usuario de su programa. Para hacerlo, el Entry El widget puede ser útil. Mira el siguiente guión:

import tkinter

root = tkinter.Tk()

def color_label(any_label, user_input):
    any_label.configure(text=user_input, bg="green", fg="white")

frame1 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame2 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame3 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame4 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame5 = tkinter.Frame(root, borderwidth=2, relief="ridge")
frame6 = tkinter.Frame(root, borderwidth=2, relief="ridge")

frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=0, row=1, sticky="nsew")
frame3.grid(column=1, row=0, sticky="nsew")
frame4.grid(column=1, row=1, sticky="nsew")
frame5.grid(column=0, row=2, sticky="nsew", columnspan=2)
frame6.grid(column=0, row=3, sticky="nsew", columnspan=2)

label1 = tkinter.Label(frame1, text="Simple label 1")
label2 = tkinter.Label(frame2, text="Simple label 2")
button1 = tkinter.Button(frame3, text="Configure button 1", command=lambda: color_label(label1, entry.get()))
button2 = tkinter.Button(frame4, text="Configure button 2", command=lambda: color_label(label2, entry.get()))

button3 = tkinter.Button(frame5, text="Apply and close", command=root.destroy)

entry = tkinter.Entry(frame6)

label1.pack(fill="x")
label2.pack(fill="x")
button1.pack(fill="x")
button2.pack(fill="x")
button3.pack(fill="x")
entry.pack(fill="x")

root.mainloop()

Salida:

Observe las líneas 5 y 6. Como puede ver, color_label() El método acepta un nuevo argumento ahora. Este argumento, una cadena, se utiliza para modificar la etiqueta configurada. text parámetro. Además, en la línea 29 creamos un nuevo Entry widget (y en la línea 36 lo empaquetamos dentro de un nuevo marco creado en la línea 13).

En las líneas 24 y 25, podemos ver que cada una de nuestras funciones lambda también pasa un argumento adicional. los get() método del Entry class devuelve una cadena que es lo que el usuario escribió en el campo de entrada. Entonces, como probablemente ya sospecha, después de hacer clic en los botones “configurar”, el texto de las etiquetas asignadas a ellos se cambia a cualquier texto que el usuario haya escrito en nuestro nuevo campo de entrada.

Conclusión

Espero que esta parte del tutorial haya llenado algunos vacíos en su comprensión del módulo Tkinter. Aunque algunas características avanzadas de Tkinter pueden parecer un poco complicadas al principio, la filosofía general de construir interfaces usando el paquete GUI más popular para Python es muy simple e intuitiva.

Estén atentos a la última parte de nuestro tutorial básico de Tkinter, donde descubriremos algunos atajos muy inteligentes que nos permiten crear interfaces de usuario complejas con código muy limitado.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad