lunes, 21 de marzo de 2022

Diseñando ventanas gráficas. Gestor de geometría Pack.


Imagen de entrada







Diseñando ventanas gráficas. Gestores de geometría.

Lo más recomendable cuando diseñes una aplicación GUI es coger un papel y un lápiz y dibujar a mano lo que quieres que haya en tu aplicación. El tamaño de la ventana, el texto que va dentro, los diferentes widgets que vayan en ella etc. 

Una vez dicho esto, existen tres formas de colocar los diferentes elementos o widgets dentro de una ventana. Cada ventana de la aplicación puede estar construida indistintamente con cada uno de estos gestores. 

- Gestor de Geometría Pack.

- Gestor de Geometría Grid.

- Gestor de Geometría Place.

En los siguientes capítulos vamos a ver cada uno de ellos en Tkinter y Guizero y aplicarlos a una ventana que contendrá un widget para que el usuario pueda introducir su nombre y otra para introducir la contraseña. 

Gestor de Geometría Pack.

En este gestor lo que se tiene en cuenta para colocar el widget es la  posición en lo que lo hayamos puesto dentro de la ventana:

- Arriba (TOP)

- Abajo (BOTTOM)

- Izquierda (LEFT)

- Derecha (RIGHT)

Por defecto los widgets se agregan a la ventana en el orden en el que se crean, comenzando por la parte superior del contenedor y aparecen en el centro. No obstante se pueden alinear a la izquierda, a la derecha o en la parte de abajo utilizando el parámetro determinado. 

Cuando se agregan varios widgets a un mismo contenedor estos aparecen en el orden en que se han introducido. 

Este es el ejemplo de lo que vamos a programar.





Empezamos con Tkinter.


Creamos un archivo de Python con el nombre que quieras y comenzamos importando las bibliotecas que vamos a necesitar.

from tkinter import *
from tkinter import ttk, font
import getpass

Importamos todo de tkinter y también importamos tkk para poder utilizar widgets más modernos, que se han creado en versiones superiores a la 8.5 de tkinter. Importamos también la biblioteca font para poder hacer futuras modificaciones en las fuentes.

Una vez importadas las bibliotecas necesarias comenzamos creando la ventana principal utilizando la programación orientada a objetos. 

# Gestor de geometría (pack)

class Aplicacion():
    def __init__(self):
        self.raiz = Tk()
        self.raiz.title("Acceso")

Empezamos creando la clase Aplicación donde se recogerán las diferentes propiedades y métodos que utilizaremos. Luego creamos la ventana principal a la que llamamos raiz y con el método title le ponemos el título "Acceso".

            fuente = font.Font(weight='bold')

Para que destaquen las letras que acompañan a las cajas de entradas de los datos (Usuario y Contraseña) con el código superior cambiamos a negrita la letra usada actualmente. Por eso primeramente habíamos importado el módulo font al comienzo del programa.

                 self.etiq1 = ttk.Label(self.raiz, text="Usuario:", 
                               font=fuente)
        self.etiq2 = ttk.Label(self.raiz, text="Contraseña:", 
                               font=fuente)

Utilizamos dos widgets "Label" para crear dos etiquetas de texto "Usuario:" y "Contraseña". Usamos ttk.Label porque queremos utilizar el widget más actual de tkinter que nos permite usar el módulo "font" para usar la negrita. Puedes probar a utilizar solo Label en vez de ttk.Label para ver las diferencias. 

Observa como ambos elementos se sitúan en la ventana principal ya que al método ttk.Label el primer argumento que le pasamos es en donde se colocará ese widget. En nuestro caso en la ventana self.raiz.

            self.usuario = StringVar()
        self.clave = StringVar()

Aquí definimos dos variables de control de Tkinter que recogerán texto y que contendrán el usuario y su contraseña. 

                self.usuario.set(getpass.getuser())

A una de estas variables, que hemos creado antes, a la variable "usuario" le vamos a asignar por defecto el nombre del usuario que ha iniciado sesión en el sistema. Para ello importamos el módulo getpass al comienzo del programa.

             self.ctext1 = ttk.Entry(self.raiz, 
                                textvariable=self.usuario, 
                                width=30)
        self.ctext2 = ttk.Entry(self.raiz, 
                                textvariable=self.clave, 
                                width=30, show="*")
        self.separ1 = ttk.Separator(self.raiz, orient=HORIZONTAL)

A continuación creamos dos cajas de entrada que recogerán tanto el nombre del usuario como su contraseña. Su longitud máxima será de 30 caracteres. A la primera de ellas "self.ctext1", que recogerá el nombre del usuario, se le asigna la variable "self.usuario" a la opción "textvariable". De esta forma cualquier valor que tome la variable durante la ejecución del programa quedará reflejada automáticamente en la caja de entrada. La segunda caja, (self.ctext2) la de la contraseña, hace lo mismo. En ella añadimos la opción show con un "*" (asterisco) para ocultar los caracteres al escribir la contraseña.

En este bloque también definimos un widget separador para poner espacio entre las cajas de texto y los botones cuando realicemos el diseño.

                 self.boton1 = ttk.Button(self.raiz, text="Aceptar", 
                                 command=self.aceptar)
        self.boton2 = ttk.Button(self.raiz, text="Cancelar", 
                                 command=quit)

Vamos con los botones Aceptar y Cancelar. Si se presiona el botón Aceptar llamará a la función "self.aceptar" y si se presiona cancelar se cerrará la ventana. 

Ahora vamos con el meollo de la cuestión. Puesto que ya tenemos definidos todos los widgets que vamos a necesitar para crear la ventana (dos LABELS, dos Entry´s, un separador y dos botones) vamos a ver como colocarlos usando el gestor PACK

                 self.etiq1.pack(side=TOP, fill=BOTH, expand=True, 
                        padx=5, pady=5)
        self.ctext1.pack(side=TOP, fill=X, expand=True, 
                         padx=5, pady=5)
        self.etiq2.pack(side=TOP, fill=BOTH, expand=True, 
                        padx=5, pady=5)
        self.ctext2.pack(side=TOP, fill=X, expand=True, 
                         padx=5, pady=5)
        self.separ1.pack(side=TOP, fill=BOTH, expand=True, 
                         padx=5, pady=5)
        self.boton1.pack(side=LEFT, fill=BOTH, expand=True, 
                         padx=5, pady=5)
        self.boton2.pack(side=RIGHT, fill=BOTH, expand=True, 
                         padx=5, pady=5)


RECUERDA que los elementos se van colocando en el orden en el que se declaran. Todos los widgets se les manda colocarse en la parte de arriba excepto los botones que ocuparan la posición izquierda y derecha respectivamente después del último elemento TOP ( y estarán en la misma línea horizontal porque así lo permite el tamaño de la ventana)

Como ya comentamos los valores posibles para la opción side son:

- TOP, LEFT, RIGHT, BOTTOM.

Si se omite el valor por defecto será TOP. 

La opción "fill" se utiliza para decirle al gestor de ventanas como expandir o reducir el widget si la ventana cambia de tamaño. Tiene tres posibles valores:

  1. BOTH ( se expandirá o reducirá Vertical y Horizontalmente.
  2. X    (sólo Horizontalmente)
  3. Y    (sólo Verticalmente) 

En la práctica puedes probar a poner solo el parámetro "fill" y por defecto expand será igual a True y no hará falta poner este comando. Si no existe el parámetro "fill" el expand por defecto se considerará como False.

Finalmente las opciones padx y pady hacen referencia al espaciado exterior entre el widget y otros elementos o la propia ventana. Para modificar el espaciado interno entre las letras y los bordes del widget se utilizan los comando ipadx e ipady.

Para terminar la aplicación vamos a poner el foco en la caja de la contraseña para que esté ya seleccionada y podamos teclear en ella directamente. 

                 self.ctext2.focus_set()
        
        self.raiz.mainloop()

y con la última instrucción iniciamos el bucle de representación de la ventana principal con todos sus widgets.

Solamente nos queda rematar la parte lógica. Que es lo que pasará cuando se pulse el botón aceptar. Cuando diseñamos el botón, establecimos que al pulsarse ejecutará la función "aceptar". 

           def aceptar(self):
         if self.clave.get() == 'herodoto':
                    print("Acceso permitido")
                    print("Usuario:   ", self.ctext1.get())
                    print("Contraseña:", self.ctext2.get())
         else:
                    print("Acceso denegado")

Esta simplemente muestra un mensaje en pantalla si el usuario teclea la contraseña "herodoto" y en caso contrario deniega el acceso. Si no se pone la contraseña correcta se borra la variable que la contiene y se vuelve a poner el foco en la caja donde se teclea.

                                        self.clave.set("")
                   self.ctext2.focus_set()
Ya para acabar se crea una instancia de la aplicación y se ejecuta.

def main():
    mi_app = Aplicacion()
    return 0

if __name__ == '__main__':
    main()

Puedes encontrar el código completo en el siguiente enlace.

En guizero.


Ejemplo en Guizero del gesto PACK



Aunque se pueden mezclar elementos de tkinter con otros de guizero el siguiente ejemplo esta hecho usando solo elementos que se pueden encontrar en Guizero. El funcionamiento del programa es exactamente el mismo. 

from guizero import *
import getpass


def aceptar():
    clave = ctext2.value # Asigna a la variable clave el value que tenga el 
    objeto ctext2
    
    if clave == 'herodoto':
        print('Acceso Permitido')
        print('Usuario: ', usuario)
        print('Contraseña', clave)
    else:
        print('Accesos Denegado')
        ctext2.value = ""
usuario = getpass.getuser() raiz = App(title='Acceso', width=255, height=170) # SI NO QUISIERAMOS QUE SE PUDIERA MODIFICAR EL TAMAÑO DE LA VENTANA. raiz.tk.resizable(width=False, height=False) # Diseñamos y establecemos los diferentes widgets. box3 = Box(raiz, width="fill",height="fill") # font= permite elegir una fuente instalada en el sistema etiq1 = Text(box3, text="Usuario", align="left", font='padmaa-Bold.1.1') # width or height ='fill' Expande todo el objeto dentro de la ventana ctext1 = TextBox(raiz, width='fill') ctext1.value=getpass.getuser() box4 = Box(raiz, width="fill",height="fill") etiq2 = Text(box4, text="Contraseña", align="left", font='padmaa-Bold.1.1') ctext2 = TextBox(raiz, width='fill', hide_text=True) # centramos el foco en esta caja. ctext2.focus() # Como no encontre el objeto Separator de tkk he usado Drawing en su lugar. # para dibujar una recta dentro de un box que haga de separador. box10=Box(raiz, width='fill', height=11, visible=True, border=0) recta1 = Drawing(box10, align='left', width='fill') recta1.line(0,6,1000,6, color='grey') bt1 = PushButton(raiz,text='Aceptar', command=aceptar, align='left', padx=5, pady=5, width='fill', height='fill') bt2 = PushButton(raiz,text='Cancelar', command=quit, align='right', padx=5, pady=5, width='fill', height='fill') raiz.display()

Puedes encontrar el código completo en el siguiente enlace.

Comentarios.

En este ejemplo he usado programación lineal en vez de POO como en el ejemplo anterior.

A diferencia de tkinter en el que diseñamos los widgets y después los empaquetamos, en Guizero se hace esto mismo en un único paso, cuando los definimos. Aquí he agrupado el texto "usuario" y su caja de entrada dentro de un contenedor Box y lo mismo para el texto "contraseña" y su caja de entrada.

No hay comentarios:

Publicar un comentario