Desarrollo de GUI de Python con Tkinter: Parte 2

    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.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *