Mostrando entradas con la etiqueta StringVar(). Mostrar todas las entradas
Mostrando entradas con la etiqueta StringVar(). Mostrar todas las entradas

miércoles, 27 de abril de 2022

¿Cómo funcionan las variables de control en Tkinter y en Guizero?

imagen de entrada del post

Variables de control en Tkinter y Guizero.

Para ver el tema de las variables de control vamos a ver como se tratan primero en Tkinter y luego en Guizero.

Empecemos con Tkinter.

Las variables de control son unos objetos especiales que se asocian a los widgets para almacenar los valores que estos puedan tomar y poder así luego usarlos en otras partes del programa. Estos valores pueden ser numéricos, texto u opciones Boolenas.

Lo bueno de esto es que cuando una variable de control cambia de valor, esto queda automáticamente reflejado en el widget que lo utiliza o también puede ocurrir al revés. 

Estas variables también se utilizan para conectar varios widgets del mismo tipo, por ejemplo varios controles del tipo RadioButton (en este caso tomarán uno de los varios posibles.)

¿Cómo declaramos estas variables de control?

Las declaración de las variables de control va a estar en función del tipo de dato que almacenan. 

entero = IntVar() -> Para números enteros
flotante = DoubleVar() -> Para número reales.
cadena = StringVar() -> Para cadenas.
booleano = BooleanVar() -> Para datos de tipo Booleano.

Además en el momento de declararlas también podemos establecer un valor inicial, con el argumento "value". Por ejemplo:

numero_pizzas = IntVar(value=1) -> Para números enteros


Para establecer, leer o detectar cambios en las variables de control utilizamos los siguientes métodos.

Método set()

Asigna un valor a una variable de control. Se utiliza para modificar el valor o estado de un widget.

Por ejemplo:

uso de variables de control en Tkinter


from tkinter import *
import tkinter.ttk as ttk
raiz = Tk()
nombre = StringVar()
numero = DoubleVar()
nombre.set("El número pi es: ")
numero.set(3.1416)
# Dibuja cuadro de entrada de texto con el texto 
blog = ttk.Entry(raiz, textvariable=nombre, width=25)
blog.pack()
# Dibuja una etiqueta con el numero pi
arti = ttk.Label(raiz, textvariable=numero)
arti.pack()
raiz.mainloop()

Método get()

El método get() nos facilita el valor que tenga en un momento dado una variable de control.  Si te fijas en el ejemplo anterior creamos dos variables de control "nombre" y "número". Si quisiésemos saber el valor de ambas utilizaríamos 

print(f"Texto cuadro = '{nombre.get()}'")
print(f"Cuadro númerico = {numero.get()}")

lo que tendría una salida similar a esta:

Texto cuadro= 'El número pi es: '
Cuadro númerico = 3.1416


método trace()

Este método se utiliza para detectar cambios en el valor de una variable. Podemos detectar cuando es leída, cambia de valor o es borrada.

Su estructura es:

variable.trace(suceso, función)

En donde suceso puede tomar alguno de los siguientes valores:

"r" - Lectura de la variable

"w" - Escritura de la variable

"u" - Borrado de la variable.

El segundo elemento hace referencia a que función se ejecutará cuando se desate el suceso.

Para verlo con un ejemplo vamos a usar un Entry con un texto inicial y verás como si borras o escribes en el estas modificando la variable y así se mostrará en el terminal.

from tkinter import *
import tkinter.ttk as ttk
raiz = Tk()


def cambia(*args):
    print("Ha cambiado su valor")

variable = StringVar()
variable.set("texto de Prueba")
entrada = ttk.Entry(raiz, textvariable=variable, width=25)
entrada.pack()

variable.trace("w", cambia)


raiz.mainloop()


método trace



Estrategias de control de cambios en la aplicación: validación posterior o inmediata.

Cuando construimos una aplicación con varios widgets podemos usar dos estrategias para controlar los datos que se introducen en la ejecución de un programa.

- Introducir primero todos los datos y posteriormente realizar las operaciones, presionando por ejemplo un botón.

- Haciendo uso del método trace() y/o de la opción 'command' para calcular y validar la información justo en el momento en el que un widget y su variable asociada cambien de valor. 

Vamos a practicar estos conceptos con un caso práctico en el que veremos ambas opciones.

Supongamos un programa que calcule el precio de un billete de autobús, teniendo en cuenta el número de pasajeros, el tipo de billete (solo ida o ida y vuelta), la clase en la que se viaja (normal, supra o gran lujo), la distancia entre el lugar y el destino en Kilómetros y el precio por kilometro (0,20 € / Km)

El precio total se calcula como el numero de pasajeros por km y precio por Kilometro pero teniendo en cuenta que:

- si el billete es solo de ida se multiplica el total por 1.5

- si la clase del asiento es supra se multiplica por 1,2 y si es gran lujo se multiplica por 2.

Primera opción. Introducción de los datos y validación posterior.

El calculo del importe a pagar se realizará después de pulsar el botón "Calcular"

ejemplo de validación posterior


Puedes encontrar el código de la aplicación en el siguiente enlace.

Segunda Opción. Introducción de datos y calculo inmediato.

En este caso no hay un botón "Calcular" ya que el calculo se hace instantáneamente cada vez que modificamos algún dato. Para los widgets de entradas de datos 'entry()' se definen dos "trace" para detectar cualquier cambio en los datos. En el resto de widgets se utiliza la opción "command".

ejemplo calculo inmediato


Puede encontrar el detalle del código en el enlace.

Con Guizero.

Primera opción. Introducción de los datos y validación posterior.

Al igual que anteriormente realizamos primero todo el diseño de la ventana y los widgets. En guizero para asignar un valor o texto al widget usamos la propiedad value del mismo. Así si creamos por ejemplo un cuadro de texto y lo llamamos Caja, podemos poner el texto dentro se la siguiente forma:

Caja.value = "El texto que queramos poner"

y si lo que queremos es ver el valor que ya esta escrito en un widget tenemos que leer esta propiedad del widget. Por ejemplo si queremos guardar en una variable algo que ya esta escrito en una caja de texto podemos usar:

variable = Caja.value

ejemplo con guizero


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

Segunda Opción. Actualización automática del resultado al introducir los datos.

ejemplo con guizero

Aquí la estrategia es un poco distinta, pero muy sencilla. Cada vez que modificamos uno de los widgets que admiten variación de datos, en el propio elemento se utiliza el parámetro command para ejecutar la función que calcula el resultado.

Pej:

idavu = CheckBox(box, text='Ida y vuelta', grid=[0,2], align='left', command=calculo)

o

Text(box, text="Distancia en Km.", grid=[0,5], align="left")

dist = TextBox(box, grid=[0,6], width=20, command=calculo)

dist.value = 1

Puedes verlo en más detalle en el código del programa en el siguiente enlace.



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.