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.



martes, 26 de abril de 2022

Expresiones Regulares

Las expresiones regulares son secuencias de caracteres que conforman un patrón de búsqueda, las cuales son formalizadas por medio de una sintaxis especifica. Estos patrones se interpretan como un conjunto de instrucciones, que luego se aplican sobre un texto de entrada para producir un subconjunto o versión modificada del texto original. Muchos problemas de procesamiento de texto se pueden resolver fácilmente con ellas.

Lo primero que tenemos que hacer es importar el modulo de expresión regular. Ejemplo básico de como se crean y utilizan expresiones regulares.
import re

cadena_re = "{{(.*?)}}"
una_cadena = "Esta es una cadena con {{palabras}} metidas\
entre {{llaves}} para ver un {{ejemplo}} de {{expresion regular}}"

for coincidencia in re.findall(cadena_re, una_cadena):
	print(f'Coincidencia: {coincidencia}')
Coincidencia: palabras
Coincidencia: llaves
Coincidencia: ejemplo
Coincidencia: expresion regular

Lo primero que hicimos fue importar el modulo re. (regular expresion). A continuación creamos una cadena con el patrón que buscamos en el ejemplo. Este patrón coincidirá con dos llaves abiertas consecutivas ({{) seguidas por cualquier texto (o no texto) seguidos por dos llaves cerradas consecutivas (}}). A continuación, creamos una cadena de texto, "una_cadena" que contiene una mezcla de grupos de palabras englobados en llaves dobles y palabras que no lo están. Por ultimo pasamos por los resultados de la función findall() del módulo re que va buscando en "una_cadena" el patrón establecido en "cadena_re"

Dos formas de trabajar con expresiones regulares en Python.

La primera es utilizar las funciones en el módulo re directamente, como en el ejemplo enterior.

La segunda es crear un objeto de expresión regular compilada y utilizar los métodos en ese objeto. Una expresión regular compilada es la que se crea al pasar un patrón a re.compile() 

Veamos un ejemplo:

import re

re_obj = re.compile("{{(.*?)}}")

una_cadena = "Esta es una cadena con {{palabras}} metidas\
entre {{llaves}} para ver un {{ejemplo}} de\
{{expresion regular}}"

for coincidencia in re_obj.findall(una_cadena):
	print(f'Coincidencia: {coincidencia}')
Coincidencia: palabras
Coincidencia: llaves
Coincidencia: ejemplo
Coincidencia: expresion regular

El uso de un método y otro es cuestión de gusto, sin embargo es preferible usar la segunda ya que es un método más eficiente y rápido especialmente cuando se busca un patrón en un archivo de gran tamaño (Por ejemplo 500.000 líneas). 

Componentes de las expresiones regulares.

- literales. Cualquier caracter que se encuentre a si mismo, a menos que se trate de un metacaracter el cual tendrá un significado especial.  Por ejemplo el patrón "hola" encontrará todas las veces que "hola" aparezca en el texto que procesemos. 

- Secuencias de escape. En la sintexis de las expresiones regulares podemos utilizar cadenas de escape (\ la barra inclinada más otro caracter) para ciertos casos particulares. (saltos de linea, tabuladores etc)

Secuencia de escapeSignificado
\nNueva línea (new line). El cursor pasa a la primera posición de la línea siguiente.
\tTabulador. El cursor pasa a la siguiente posición de tabulación.
\\Barra diagonal inversa
\vTabulación vertical.
\oooCarácter ASCII en notación octal.
\xhhCarácter ASCII en notación hexadecimal.
\xhhhhCarácter Unicode en notación hexadecimal.

- Clases de caracteres.  Se crean poniendo los caracteres a buscar entre corchetes. []  Por ejemplo si usamos el patrón r'aeiou' en un texto encuentra todas las vocales que haya en el mismo.

import re

patron = r'[aeiou]'
texto = "me gusta el abecedario"

print(re.findall(patron, texto))
['e', 'u', 'a', 'e', 'a', 'e', 'e', 'a', 'i', 'o']

Cada corchete equivale a un caracter en el texto de búsqueda. Para que quede más claro. Si ponemos, por ejemplo, dos corchetes juntos uno con el caracter a y otro con el b nos encontrará en el texto esos dos caracteres donde estén situados de forma consecutiva. 

import re

patron = r'[a][b]'
texto = "me gusta el abecedario"

print(re.findall(patron, texto))
['ab']

rangos de caracteres.

Sirven para escribir de forma más fácil un patron. Por ejemplo si quisiéramos encontrar en un texto cualquier letra minúscula tendríamos que usar algo como [abcdefghijklmnñopqrstuvwxyz]. Sin embargo usando los rangos de caracteres esto se simplifica ya que solo tenemos que escribirlo de esta forma: [a-z] en donde le estamos diciendo al ordenador que busque cualquier caracter entre la a y la z.

Nota: pero no localiza directamente caracteres locales, como puede ser la ñ en nuestro caso. Pero se puede hacer de otra forma lo vemos más adelante.

algunos ejemplos:

[a-z]   Cualquier caracter alfabético en minúsculas.

[G-P] Letras mayúsculas de la la G  a la P

[0-9] cualquier dígito

Incluso se pueden anidar:

[a-zA-Z] cualquier letra minúscula o mayúscula

[^a-z] al poner el circunflejo dentro de los corchetes significa lo contrario, es decir, en este caso que no tenga ninguna letra minuscula o lo que viene a ser lo mismo que encuentre los caracteres en mayúscula [A-Z]

Metacaracteres. 

Son caracteres especiales que son la esencia de las expresiones regulares.  Dentro de ellos podemos encontrar:

- metacaracteres - delimitadores o también llamados anclas.

Nos permiten especificar en que posición de la cadena debe encontrarse el elemento buscado:

Metacaracter	Descripción
^	        Permite buscar al inicio de una cadena o línea (opción -MULTILINE).
$	        Permite buscar al final de una cadena o linea (opción - MULTILINE).
\A	        Permite buscar al inicio de un texto.
\Z	        Permite busca al final del texto.
.	        cualquier carácter en la línea.
\b	        Permite buscar una cadena al principio o final de una palabra o como 
                coincidencia completa de la palabra.
\B	        Permite buscar una coincidencia completa en una cadena que no se encuentra
                en el límite de una palabra.

- metacaracteres - clases predefinidas.

MetacaracterDescripción
\wun caracter alfanumérico (incluye "_").
\Wun caracter no alfanumérico.
\dun caracter numérico, un dígito.
\Dun caracter no numérico.
\scualquier espacio (lo mismo que [ \t\n\r\f]).
\Sun no espacio.

- metacaracteres - iteradores o cuantificadores. Cualquier elemento de una expresión regular puede utilizarse seguido este tipo de metacarter. Usando los mismo podemos definir el número de ocurrencias de caracter previo o dicho de otra forma multiplican el patrón que les precede.

Metacaracter	Descripción
*	        cero o más repeticiones de la cosa previa ya sea un caracter, un rango de
                caracteres [], o un grupo de caracteres entre paréntesis, similar a {0,}.
+	        una o más. Igual que el anterior pero aqui con una o más repeticiones
                , similar a {1,}.
?	        Lo mismo que el anterior pero con cero o una repeticiones, similar a {0,1}.
{n}	        exactamente n veces.
{n,}	        por lo menos n veces.
{n,m}	        por lo menos n pero no más de m veces.
*?	        cero o más, similar a {0,}?.
+?	        una o más, similar a {1,}?.
??	        cero o una, similar a {0,1}?.
{n}?	        exactamente n veces.
{n,}?	        por lo menos n veces.
{n,m}?	        por lo menos n pero no más de m veces.


- metacaracteres - alternativas. Se usa para ello el caracter | que es un equivalente a un or. Por ejemplo en el texto 'Pedro monta un mueble en el monte', el patrón r'mont[a|e]'  nos devolveriá tanto 'monta' como 'monte'. O por ejemplo el patron pre(fijo|facio) encontrara las palabras prefijo o prefacio.

Otros metacaracteres:

- metacaracteres- subexpresiones. Definen subexpresiones dentro de una expresión regular. 

-metacaracteres - memorias.

Puedes encontrar una guia rápida sobre lenguaje de expresiones regulares en el siguiente enlace de Python.


Las cadenas raw, aquellas en las que los caracteres precedidos por una barra invertida no se sustituyen por su contrapartida, se pueden utilizar para denotar cadenas que no interpretan secuencias de escape, lo cual es muy útil cuando utilizamos expresiones regulares. Veámoslo como un ejemplo:

import re

patron_raw = r'\b[a-z]+\b'
sin_patron_raw = '\b[a-z]+\b'

una_cadena = "unas pocas palabras"

print(re.findall(patron_raw, una_cadena))

print(re.findall(sin_patron_raw, una_cadena))
['unas', 'pocas', 'palabras']
[]

Como hemos visto la expresión regular \b nos delimita una palabra. Por lo tanto tanto lo que estamos buscando son palabras individuales en minúsculas. Como vemos en el ejemplo, en donde hemos usado patron_raw los resultados nos muestran correctamente las palabras del texto ya que interpreto \b como dos caracteres son un significado especial y no como retrocesos, sin embargo en sin_patron_raw no nos sale nada en absoluto ya que se interpretó los caracteres \b como una secuencia de escape que representaba el caracter de retroceso.

Los cuatro principales métodos  de expresión regular que más se utilizan son:

1.- re.findall(patron, cadena) = Genera una lista con todas las ocurrencias del patrón.

2.- re.finditer(patron, cadena) = Similar al findall pero en vez de generar una lista nos genera un objeto.

3.- re.match(patron, cadena) = Busca al principio de la cadena hay una coincidencia con el patron especificado y devuelve un objeto. Si no encuentra nada devuelve None.

4.- re.search(patron, cadena) = Busca en la cadena la primera coincidencia con el patrón especificado.

Otros métodos son:

- re.split(patron, cadena, maxsplit) = Divide la cadena utilizando el patron especificado y devuelve una lista con las particiones. Por defecto maxsplit=0 implica realizar todas las particiones posibles.

- re.sub(patron, cadena_de_remplazo, cadena de texto, count=0) = Sustituye por defecto todos los patrones por la cadena de remplazo, salvo que se indique que count sea diferente de cero.

- re.subn(patron, cadena_de_remplazo, cadena de texto, count=0) = lo mismo que el anterior pero devuelve también el número de reemplazos realizados.



jueves, 7 de abril de 2022

Cuadros de Diálogo ya Definidos.





Cuadros de Diálogo ya Definidos.


Como vimos en el capítulo anterior, las ventanas de diálogo son aquellas ventanas hijas que surgen de la principal o ventana de la aplicación. Pueden ser modales (las que bloquean las demás hasta que se cierre el diálogo) o no Modales (Permanecen abiertas sin interferir con las demás y se puede interactuar independiente con ellas.

También vimos ejemplos de como crear ventanas de dialogo de cada tipo.

En este capitulo vamos a ver como usar ventanas de dialogo que ya están presentes en los sistemas operativos, como por ejemplo un cuadro de diálogo, y que a diferencia de lo que hicimos en el capítulo anterior ya están definidas con lo que solo nos queda utilizarlas a nuestro gusto.

1.- Cuadro de diálogo Estándar.

Un cuadro de diálogo es una ventana con un título, un mensaje, un ícono y uno o más botones. Se utilizan para informar al usuario sobre alguna cuestión o bien exhortarlo a que tome alguna decisión.

Las funciones para generar cuadros de diálogo en una aplicación Tkinter están en el módulo tkinter.messagebox y son las siguientes:

  • showinfo()
  • showwarning()
  • showerror()
  • askquestion()
  • askyesno()
  • askokcancel()
  • askyesnocancel()
  • askretrycancel()

Todos reciben los argumentos message y title, que indican el mensaje y el título del cuadro de diálogo.

Por ejemplo:

  1. from tkinter import messagebox
  2. messagebox.showinfo(message="Mensaje", title="Título")

En Guizero también podemos usar directamente estos módulos (si previamente has importado messsagebox) aunque también incorpora diferentes métodos propios en el menú del widget window que hacen lo mismo. Puedes encontrar una relación de todos (Pop-ups) los que se pueden usar, así como ejemplos en su manual.

Veamos un ejemplo en tkinter:

''' Cuadro de diálogo standard  con tkinter'''

from tkinter import *
from tkinter.messagebox import *

def callback():
    if askyesno('Confirmación', '¿Estás seguro de que quieres salir?'):
        showwarning('Si', 'Salir aún no esta implementado')
    else:
        showinfo('No', 'Salir ha sido cancelado')

msg_error = '¡Lo siento, no se permite el Spam!'
Button(text='Salir', command=callback).pack(fill=X)
Button(text='Spam', command=lambda: showerror('Error', msg_error)).pack(fill=X)
mainloop()

El cual muestra las siguientes ventanas de diálogo:

Ejemplos de cuadro de diálogo

En Guizero.

''' Cuadro de diálogo standard con Guizero '''
# Importamos la librería Guizero
from guizero import *

def call_back():
    # Ventana de confirmación si o no
    if yesno('Confirmación', '¿Estás seguro de que quieres salir?'):
        # Ventana de advertencia
        warn('Si', 'Salir aún no esta implementado')
    else:
        # Ventana de información
        info('No', 'Salir ha sido cancelado')

raiz = App(height=100, width=60)

# Creamos una ventana principal con dos botones. (Salir y Error)
b1 = PushButton(raiz, command=call_back, text='Salir')
# Ventana de error
b2 = PushButton(raiz, text="Spam", command=lambda: raiz.error("Error", "¡Lo siento no se permite el Spam!"))

raiz.display()

Mientras que el cuadro de diálogo está abierto el resto de los widgets no responden a ningún evento, como por ejemplo pulsar un botón. (ventana modal)

El icono mostrado en cada ventana es provisto por el sistema operativo donde lo ejecutemos, aunque siempre está en relación con el mensaje que quiere mostrar el cuadro. Por ejemplo el icono de showinfo() es por lo general azul e indica información, el de showwarning() es amarillo o naranja denotando advertencia y el showerror() es rojo para mostrar un error.

En estas tres funciones showinfo(), showwarning() y showerror() el valor de retorno es siempre el string "ok", independientemente de si se presiono el botón o se cerró la ventana. 

Las funciones de interrogación retornan True o False dependiendo del botón que se pulse. Veámoslo con un ejemplo.

Tkinter.

from tkinter import *
from tkinter import messagebox

# Si no creas la ventana principal por defecto se utiliza la master en donde si no se 
especifica se colocan el resto de las ventanas de diálogo.

# Retornan True o False.
print(messagebox.askyesno(message="¿Desea continuar?", title="Título"))
print(messagebox.askokcancel(message="¿Desea continuar?", title="Título"))
print(messagebox.askretrycancel(message="¿Desea reintentar?", title="Título"))

ventanas interrogativas


Dará como resultado.

True
False
True

Guizero.

Para que veas que en Guizero se pueden usar los elementos de Tkinter vamos a usar el mismo código pero importando todos los elementos de Guizero (que en realidad importa todos los de Tkinter)

from guizero import *


# Retornan True o False.
print(messagebox.askyesno(message="¿Desea continuar?", title="Título"))
print(messagebox.askokcancel(message="¿Desea continuar?", title="Título"))
print(messagebox.askretrycancel(message="¿Desea reintentar?", title="Título"))

El resultado es el mismo.

askquestion es similar a askyesno(), pero el resultado al ejecutarse, dependiendo del botón que se pulse es "yes" or "no"

from guizero import *
# Retorna "yes" o "no".
print(messagebox.askquestion(message="¿Desea continuar?", title="Título"))


cuadro de dialogo askyesno


Dependiendo del botón pulsado la salida será "yes" or "no"


Por último, askyesnocancel() despliega los botones «Sí», «No» y «Cancelar», y los valores de retorno posibles son True, False y None, respectivamente.

from guizero import *
# Retorna "True" o "False" o "None".
print(messagebox.askyesnocancel(message="¿Desea continuar?", title="Título"))

ventana askyesnocancel

Si cerramos la ventana el valor retornado será "None" ya que equivale al botón cancelar.


Otros Cuadros de diálogo de tkinter.

Como estos cuadros de dialogo se importan desde  librerías de Tkinter solo utilizaremos código de este.


Tkinter Open File.

- askopenfilename o Abrir Archivo:

from tkinter import *
from tkinter.filedialog import askopenfilename

print(askopenfilename(title='Abrir archivo', initialdir="/home/chema",
                      filetypes=[('All files', '*'),('jpeg files', '*.jpg')]))

ventana de dialogo abrir archivo



Si seleccionamos un archivo y presionamos el botón Abrir nos devolverá la ruta completa del mismo. Por el contrario si presionamos cancelar nos devolverá una cadena vacía.

Algunas opciones que le podemos poner son:

initialdir -> Establece el directorio inicial que se mostrará en el cuadro de diálogo al ejecutarlo.

filetypes -> que archivos apararecerán en la casilla Archivos de Tipo. Es decir que tipo de archivos nos permitirá abrir. Se especifican en una lista con tuplas en su interior. Cada tupla tendrá dos string, el primero indicará el título y el segundo la extensión a buscar:

filetypes=[('All Filers', '*')] -> Para cualquier tipo de archivo

filetypes=[('jpeg files', '*,jpg')] -> Para archivos con formato jpg


Tkinter Save File.

- asksaveasfilename o Guardar Archivo.

from tkinter import *
from tkinter.filedialog import asksaveasfilename


print(asksaveasfilename(initialdir="/", title="Guardar Archivo", 
                    filetypes=[("jpeg files", "*.jpg"), ("all files", "*.*")]))

asksaveasfilename ventana de diálogo


Muestra al usuario un cuadro clásico de guardar archivo.  Al introducir el nombre del archivo nos devolverá la ruta completa del mismo. Si le damos al botón cancelar nos devolverá una cadena vacia.


Tkinter Open Directory.

askdirectory o Selección de directorio.

Funciona igual que los cuadros anteriores. Las salida que nos devolverá es el directorio seleccionado.

from tkinter import filedialog
from tkinter import *

print(filedialog.askdirectory(initialdir = "/",title = "Selecciona Directorio"))

askdirectory ventana de diálogo


Tkinker, otras ventanas de diálogo.

Igual que hemos visto anteriormente, cada cuadro de diálogo hay que importarlo previamente usando la correspondiente librería. 

- askfloat, askinteger, askstring. 

askfloat nos muestra un cuadro de dialogo que permite introducir únicamente un número entero o flotante, ya que si no dará un error. La salida es un número de punto flotante.

from tkinter import *
from tkinter.simpledialog import askfloat

raiz=Tk()
print(askfloat('Entry', 'Introduce un número: '))



askinteger nos permite también mostrar una ventana de diálogo para introducir únicamente un número entero y devuelve un número entero.

from tkinter import *
from tkinter.simpledialog import askinteger

raiz=Tk()
print(askinteger('Entry', 'Introduce un número: '))
askinteger ventana de diálogo

askstring,  muestra un cuadro de diálogo que nos permite introducir una cadena de caracteres y lo que nos devuelve es precisamente eso, un string de Python.




from tkinter import *
from tkinter.simpledialog import askstring

raiz=Tk()
# .withdraw() oculta la ventana.
raiz.withdraw()
print(askstring('Entry', 'Introduce una frase: '))


askcolor. Devuelve el color RGB y la cadena hexadecimal.

ventana de diálogo askcolor


from tkinter import *
from tkinter.colorchooser import askcolor

raiz=Tk()
raiz.withdraw()
print(askcolor())
Salida:

((102, 153, 92), '#66995c')

Puedes encontrar el código de los ejemplos en el siguiente enlace.

domingo, 27 de marzo de 2022

Tipos de Ventanas en Tkinter y Guizero.

 

ventana de un programa











Tipos de Ventanas en Tkinter y Guizero.

Ahora que ya hemos visto los  diferentes tipos de widgets y de gestores de geometría existentes vamos a centrarnos en las ventanas.

Existen diferentes ventanas. Por una parte tenemos las ventanas de aplicación y por otra las de diálogos.

Ventanas de aplicación.

Suelen ser las que inician y finalizan las aplicaciones gráficas.

Ventanas de diálogos.

Surgen de las anteriores.

El conjunto de ambas, junto con los widgets que contienen, configuran la interfaz del usuario.

En el siguiente ejemplo desarrollaremos una ventana de aplicación con un botón abrir, que cada vez que se presione abra una ventana hija de dialogo diferente y en una posición distinta de la pantalla. 

Estas ventanas que se van generando se sitúan en un plano superior con respecto a las generadas anteriormente. Además podemos interactuar indistintamente con cada una de las ventanas creadas. Podemos cambiar sus posiciones, cerrarlas etc.

Para el ejemplo, usaremos el gestor de geometría pack, aunque podríamos haber utilizado cualquiera de los anteriormente vistos. 

Tkinter.

ventana de aplicación y de dialogo


from tkinter import *
from tkinter import ttk

class Aplicacion():
    
    # Declara una variable de clase para contar ventanas
    
    ventana = 0
    
    # Declara una variable de clase para usar en el
    # calculo de la posición de la ventana
    
    posx_y = 0
    
    # Declara una ventana de aplicación
    
    def __init__(self):
        
        self.raiz=Tk()
        
        # Define la dimensión de la ventana 300x200
        # que se situara en la coordenada x=500, y=50
        
        self.raiz.geometry('300x200+500+50')
        
        self.raiz.resizable(0,0)
        self.raiz.title("Ventana de aplicación")
        
        # Define botón 'Abrir' que se utilizará para
        # abrir las ventanas de diálogo. El botón
        # está vinculado con el método 'self.abrir'
        
        boton = ttk.Button(self.raiz, text="Abrir", command=self.abrir)
        boton.pack(side=BOTTOM, padx=20, pady=20)
        self.raiz.mainloop()
        
    def abrir(self):
        ''' Construye una ventana de dialogo '''
        
        # Define una nueva ventana de diálogo '''
        
        # Las ventanas de dialogo hijas se definen con TopLevel()
        self.dialogo = Toplevel()
        
        # incrementa en 1 el contador de ventanas
        # en la variable de clase a la que se puede acceder
        # desde cualquier método de la clase con la notación
        # del punto.
        Aplicacion.ventana+=1
        
        # Recalcula la posición de la ventana


        Aplicacion.posx_y += 50
        # el \ al final de la línea era para hacer un salto y que siga leyendo el código
        tamypos ='200x100+'+str(Aplicacion.posx_y)+ \
                  '+'+ str(Aplicacion.posx_y)
        print(tamypos)
        self.dialogo.geometry(tamypos)
        self.dialogo.resizable(0,0)
        
        # obtiene el identificador de la nueva ventana
        
        ident = self.dialogo.winfo_id()
        
        # Construye el mensaje de la barra de titulo
        
        titulo = str(Aplicacion.ventana)+": "+str(ident)
        print(titulo)
        self.dialogo.title(titulo)
        
        # Define el botón 'Cerrar' que cuando sea presionado
        # cerrara (destruirá) la ventana 'self.dialogo' llamando
        # al método 'self.dialogo.destroy'
        
        boton = ttk.Button(self.dialogo, text='Cerrar', command=self.dialogo.destroy)
        boton.pack(side=BOTTOM, padx=20,pady=20)
        
        # Cuando la ejecución del programa llega a este
        # punto se utiliza el método wait.window() para
        # esperar que la ventana 'self.dialogo' sea
        # destruida.
        
        # Mientras tanto se atiende a los eventos locales
        # que se produzcan, por lo que otras partes de la
        # aplicación seguirán funcionando con normalidad.
        # Si hay código después de esta línea se ejecutará
        # cuando la ventana 'self.dialogo' sea cerrada.
        
        self.raiz.wait_window(self.dialogo)
        
def main():
    mi_app = Aplicacion()
    return 0

if __name__=='__main__':
    main()   

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


Guizero

ventana principal y de dialogo en Guizero

'''ejemplo de ventanas no modales porque mientras existen es posible interactuar libremente con ellas,
con ellas, sin ningún límite, excepto que si cerramos la ventana principal se cerrarán todas
las ventanas hijas abiertas.'''

from guizero import *

def dialogo():
    ''' Crea una nueva ventana de dialogo. Cada vez que se ejecuta se dibuja una
nueva ventana, con una nueva posición y un nuevo titulo de ventana'''
    global ventana, posx_y
    # Incrementa en 1 el numero de ventanas abiertas
    ventana +=1
    # Incrementa en 50 los pixeles de la variable q controla la posición
    posx_y +=50
    # variable de texto que contendrá el tamaño y posición de la ventana en geometry
    #tama_y_posc = "200x100"+"+"+str(posx_y)+"+"+str(posx_y)
    tama_y_posc = f"200x100+{posx_y}+{posx_y}"
    print(tama_y_posc)
        
    # En guizero se usa Window() para definir una ventana de dialogo.
    window = Window(raiz)
    # obtiene el identificador de la nueva ventana
    ident = window.tk.winfo_id()
    titulo = f"{ventana}:{ident}"
    print(ident)
    window.title=titulo
    window.hide()
    window.tk.geometry(tama_y_posc)
    window.tk.resizable(0,0)
    PushButton(window, text="Cancelar", align="bottom", command=window.destroy)
    window.show()

# Variable genérica para contar las ventanas.
ventana = 0
# Variable genérica para el calculo de la posición de la ventana
posx_y = 0

#---------------------------- Ventana Principal------------------------------------    
raiz = App(title="Ventana de Aplicación")
# La geometría del objeto raiz.tk es "(tamaño_x)x(tamaño_y)+posc_x+posc_y"
raiz.tk.geometry("300x200+500+50")
raiz.tk.resizable(0,0)

# En guizero el parámetro padding afecta a la distancia del texto del botón
# respecto al borde del mismo, hace mas grande o mas pequeño al boton
boton = PushButton(raiz, text="Abrir", align="bottom", padx=20, pady=5, command=dialogo)

# Sin embargo si lo pones en el método tk afecta a la distancia al siguiente widget.
# En este caso a la distancia a la parte inferior de la ventana
boton.tk.pack(padx=20, pady=20)
#-----------------------------------------------------------------------------------


raiz.display()

Puedes encontrar el código del ejemplo en el siguiente enlace.


Ventanas Modales y No modales.

A parte de que las ventanas pueden ser principales o de dialogo, también como ya hemos visto en el ejemplo anterior aunque no lo hayamos dicho, pueden ser modales y no modales. 

Las ventanas hijas creadas en los ejemplos anteriores son "no modales" porque mientras que existan es posible interactuar independientemente con cada una de ellas, sin limite alguno, excepto que si cerramos la ventana principal se cerrarán todas ellas.

El ejemplo típico es el de las ventanas de cualquier aplicación ofimática que te permiten trabajar con varios documentos mientras están abiertas.

En las ventanas con modales esto no pasa, ya que la ventana activa, bloquea a las demás hasta que se cierre el diálogo.

El ejemplo típico es el de algunas ventanas de dialogo en la que se establecen preferencias de las aplicaciones que tienen que ser cerradas antes de poder acceder a otras.


Tkinter. Ventana Modal.

ventana modal en tkinter


Aquí la peculiaridad es que se emplea el método grab_set() para crear la ventana modal y transiet() para convertir la ventana de dialogo en una ventana transitoria, haciendo que esta se oculte cuando la ventana de la aplicación se minimice.

'''Ejemplo de ventana modal. El siguiente ejemplo sólo es posible
mantener abierta sólo una ventana hija, aunque si la cerramos podremos abrir otra.

El método grab_set() se utiliza para crear la ventana modal y el método transiet()
se emplea para convertir la ventana de diálogo en ventana transitoria,
haciendo que se oculte cuando la ventana de aplicación sea minimizada.'''

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *
from tkinter import ttk

class Aplicacion():
    
    #variables de la clase
    ventana = 0
    posx_y = 0
    
    def __init__(self):
        self.raiz=Tk()
        self.raiz.geometry('300x200+500+50')
        self.raiz.resizable(0,0)
        self.raiz.title("Ventana de aplicación")
        boton = ttk.Button(self.raiz, text="Abrir", command=self.abrir)
        boton.pack(side=BOTTOM, padx=20, pady=20)
        self.raiz.mainloop()
        
    def abrir(self):
        '''Construye una ventana de dialogo'''
        
        self.dialogo = Toplevel() #equivale a Window en guizero
        Aplicacion.ventana+=1
        Aplicacion.posx_y+=50
        tamypos = f'200x100+{Aplicacion.posx_y}+{Aplicacion.posx_y}'
        self.dialogo.geometry(tamypos)
        self.dialogo.resizable(0,0)
        ident = self.dialogo.winfo_id()
        titulo = str(Aplicacion.ventana)+": "+str(ident)
        self.dialogo.title(titulo)
        boton=ttk.Button(self.dialogo,text='Cerrar', command=self.dialogo.destroy)
        boton.pack(side=BOTTOM, padx=20, pady=20)
        
        # Convierte la ventana ''self.dialogo' en
        # transitoria con respecto a su ventana maestra
        # 'self.raiz'.
        # Una ventana transitoria siempre se dibuja sobre
        # su maestra y se ocultará cuando la maestra sea
        # minimizada. Si el argumento 'master' es
        # omitido el valor, por defecto, será la ventana
        # madre
        
        self.dialogo.transient(master=self.raiz)
        
        # El método grab_set() asegura que no haya eventos
        # de ratón o teclado que se envien a otra ventana
        # diferente a 'self.dialogo'. Se utilizará para
        # crear una ventana de tipo modal que será
        # necesario cerrar para poder trabajar con otra
        # diferente. Con ello, también se impide que la
        # misma ventana se abra varias veces.
        
        self.dialogo.grab_set()

        # Para esperar a que la ventana self.dialogo sea cerrada.
        self.raiz.wait_window(self.dialogo)
        
def main():
    mi_app = Aplicacion()
    return 0

if __name__=='__main__':
    main()        

El código de este ejemplo se encuentra en el siguiente enlace.

Guizero. Ventana Modal.

ventana modal en guizero



Por defecto en Guizero las ventanas añadidas de dialogo que se creen son no modales, funcionan de forma independiente, así que para transformarlas en modales hay que usar componentes de Tkinter.

from guizero import *
# Las listas de un solo elemento tienen que tener una coma ,
# después del elemento.
contador=[0,]
posx_y=[0,]

def dialogo():
    # En vez de variables globales (enteros, tuplas) vamos a utilizar listas que se modifican
    # por si solas a nivel global de la aplicación.
    contador[0]=contador[0]+1
    posx_y[0]=posx_y[0]+50
    ventana=Window(raiz)
    ident = ventana.tk.winfo_id()
    ventana.title=f"{contador[0]} :{ident}"
    ventana.hide()
    ventana.tk.resizable(0,0)
    dimension = f"200x100+{posx_y[0]}+{posx_y[0]}"
    ventana.tk.geometry(dimension)
    #-----------------------------------------------------------------------
    PushButton(ventana, text="Cancelar", align="bottom", command=ventana.destroy)
        # Convierte la ventana ''self.dialogo' en
        # transitoria con respecto a su ventana maestra
        # 'self.raiz'.
        # Una ventana transitoria siempre se dibuja sobre
        # su maestra y se ocultará cuando la maestra sea
        # minimizada. Si el argumento 'master' es
        # omitido el valor, por defecto, será la ventana
        # madre
    ventana.tk.transient(master=raiz.tk)
        # El método grab_set() asegura que no haya eventos
        # de ratón o teclado que se envíen a otra ventana
        # diferente a 'self.dialogo'. Se utilizará para
        # crear una ventana de tipo modal que será
        # necesario cerrar para poder trabajar con otra
        # diferente. Con ello, también se impide que la
        # misma ventana se abra varias veces.
    ventana.tk.grab_set()
    ventana.show()

raiz=App(title="Ventana de Aplicación")
raiz.tk.geometry("300x200+500+50")
raiz.tk.resizable(0,0)

boton=PushButton(raiz, text="Abrir", padx=20, pady=5, align="bottom", command=dialogo)
boton.tk.pack(pady=20)

raiz.display()

Puedes encontrar en este enlace el código del ejemplo anterior.