Ejemplos y patrones de programación con PySimpleGUI¶
PySimpleGUI¶
Analicemos y definamos entre todos las partes de un programa con PySimpleGUI
- Layout: Es una lista. Cada uno de sus elementos es una lista con objetos de PySimpleGUI (botones, inputs, imágenes, listas de opciones, etc...)
- Window: Tiene un título, contiene botones, inputs, etc... Se le puede poner un tamaño y configurar otras opciones.
- window.read(): Dibuja la ventana, espera un evento y retorna una tupla con el evento y los valores de los inputs, listas de opciones, etc...
- Event loop / bucle de eventos: Es bucle (normalmente infinito) que en el que proceso los eventos.
- window.read(timeout=500) con timeout: Permite que el read no quede trabado más de X milisegundos. Por ejemplo para hacer animaciones, mostrar cuentas regresivas, etc...
import PySimpleGUI as sg
# En layout armamos la ventana.
# layout es una lista que contiene una lista por cada fila de la ventana.
layout = [
[sg.Input()],
[sg.Button("Aceptar")],
]
# Creamos el objeto ventana
window = sg.Window("Título de la ventana", layout)
# Un loop infinito para procesar los eventos de la ventana
while True:
# Esperamos un evento
event, values = window.read()
print(f"Evento: {event}, valores: {values}")
# sg.WIN_CLOSED es el evento de cerrar la ventana
if event == sg.WIN_CLOSED:
break
# Cerramos la ventana
window.close()
Key¶
Podemos identificar los elementos de la ventana con nombres únicos usando el argumento key
.
Por convención de PySimpleGUI normalmente las key
son strings en mayúsculas rodeados de -
.
Los objetos creados con sg.Window()
se comportan como diccionarios. Cada elemento está almacenado con la clave indicada en el argumento key
.
import PySimpleGUI as sg
# Agregamos una key para el campo de texto y para el botón
layout = [
[sg.Input(key="-NOMBRE-")],
[sg.Button("Aceptar", key="-ACEPTAR-")],
]
window = sg.Window("Título de la ventana", layout)
while True:
event, values = window.read()
print(f"Evento: {event}, valores: {values}, objeto 'input': {window['-ACEPTAR-']}")
if event == sg.WIN_CLOSED:
break
# Cerramos la ventana
window.close()
Update¶
Para modificar algún elemento (por ejemplo un texto) podemos utilizar el método update.
Por ejemplo:
window["-NOMBRE-"].update("No me gusta ese nombre")
O bien para dejar el campo en blanco:
window["-NOMBRE-"].update("")
Importante: Por defecto no se puede llamar a update()
antes del primer read()
.
Desafío¶
Crear un programa que muestre un contador y un botón "Incrementar" que lo vaya incrementando.
Sketch:
Preguntas:
- ¿Qué tipo de elemento usar para mostrar el contador?
- ¿Dónde guardamos el valor?
- ¿Qué muestra al inicio?
- ¿Dónde agregamos el código para incrementarlo y mostrarlo?
import PySimpleGUI as sg
# Creamos una variable en la que guardaremos el dato a mostrar
contador = 0
# Ponemos un valor inicial al campo de texto
contenido = [
[sg.Text(contador, key="-CONTADOR-")],
[sg.Button("Incrementar", key="-CONTAR-")],
]
window = sg.Window("Contador", contenido)
while True:
event, values = window.read()
print(f"{event} {values}")
if event == sg.WIN_CLOSED:
break
elif event == "-CONTAR-":
# Incrementamos el contador
contador += 1
# Obtenemos el elemento "-CONTADOR-"
elemento_contador = window["-CONTADOR-"]
# Le enviamos un mensaje "update" con un nuevo valor
elemento_contador.update(contador)
# Cerramos la ventana
window.close()
Manejo de ventanas múltiples¶
- Hay muchas formas de manejar múltiples ventanas en PySimpleGUI.
- Veremos sólo una de estas formas.
Repaso:
- Una ventana tiene un "layout" con elementos (objetos como botones, campos de texto, imágenes, etc).
- Al crear una ventana en la variable
window
leemos sus eventos conwindow.read()
.
Preguntas:
- ¿Cómo creamos múltiples ventanas?
- ¿Cómo podemos leer los eventos de muchas ventanas?
- ¿Cómo organizamos el código para que el while donde procesamos eventos no sea gigante?
- ¿Dónde mantenemos los datos del programa para evitar usar variables globales?
¿Cómo creamos múltiples ventanas?¶
Fácil, cuando querramos crear/mostrar una ventana, creamos su layout y creamos un objeto con sg.Window(...)
,
luego cuando no la necesitamos la cerramos con el método close()
. Es conveniente crear funciones o métodos para crear las ventanas (junto con sus layouts) de forma cómoda:
import PySimpleGUI as sg
def crear_ventana_principal():
layout = [
[sg.Button("Siguiente", key="-PRINCIPAL-SIGUIENTE-")],
]
return sg.Window("Ventana principal", layout, finalize=True)
def crear_ventana_secundaria():
layout = [
[sg.Button("Volver", key="-SECUNDARIA-VOLVER-")],
]
return sg.Window("Ventana secundaria", layout, finalize=True)
Nota: el argumento finalize=True
es necesario para que funcione correctamente sg.read_all_windows()
.
¿Cómo podemos leer los eventos de muchas ventanas?¶
Podríamos hacerlo de distintas formas, pero la más sencilla es usar sg.read_all_windows()
. Esta función similar
al método read()
con la diferencia que lee eventos de todas las ventanas activas y retorna también la ventana
en la que se generó el evento:
while True:
current_window, event, values = sg.read_all_windows()
print(f"Evento: {event}, valores: {values}")
if event == sg.WIN_CLOSED:
break
elif event == "-PRINCIPAL-SIGUIENTE-":
window = crear_ventana_secundaria()
current_window.close()
elif event == "-SECUNDARIA-VOLVER-":
window = crear_ventana_principal()
current_window.close()
import PySimpleGUI as sg
def crear_ventana_principal():
layout = [
[sg.Button("Siguiente", key="-PRINCIPAL-SIGUIENTE-")],
]
return sg.Window("Ventana principal", layout, finalize=True)
def crear_ventana_secundaria():
layout = [
[sg.Button("Volver", key="-SECUNDARIA-VOLVER-")],
]
return sg.Window("Ventana secundaria", layout, finalize=True)
crear_ventana_principal()
while True:
current_window, event, values = sg.read_all_windows()
print(f"Ventana actual: {current_window}, Evento: {event}, valores: {values}")
if event == sg.WIN_CLOSED:
current_window.close()
break
elif event == "-PRINCIPAL-SIGUIENTE-":
crear_ventana_secundaria()
current_window.close()
elif event == "-SECUNDARIA-VOLVER-":
crear_ventana_principal()
current_window.close()
¿Cómo organizamos el código para que el while donde procesamos eventos no sea gigante?¶
Al mismo tiempo que intentamos resolver esta pregunta también podríamos pensar en cómo evitar que el programa principal sea gigante.
Dividir la funcionalida en funciones y separar en módulos.
¿Dónde mantenemos los datos del programa para evitar usar variables globales?¶
Charlemos opciones.
- Usar un diccionario con los valores del programa y pasarlo como parámetro.
datos_juego = {
"puntaje-actual": 0,
"jugador": "Pepe",
"tiempo_restante": 1,
}
- Crear una case o más para mantener los datos en un objeto.
class Juego:
def __init__(self):
self.puntaje_actual = 0
...
juego = Juego()
Una alternativa mejorable¶
Supongamos que en el programa principal está en juego.py y creamos la siguiente estructura de archivos y carpetas para el proyecto:
.
├── datos_juego.py
├── __init__.py
├── juego.py
└── pantallas
├── configuracion.py
├── creditos.py
├── __init__.py
└── principal.py
El programa principal en juego.py podría ser algo parecido a:
import PySimpleGUI as sg
from pantallas import configuracion, creditos, principal
from datos_juego import DatosJuego
datos_juego = DatosJuego()
estado.ventana_actual = "principal"
principal.crear_ventana()
while True:
current_window, event, values = sg.read_all_windows()
print(f"Ventana actual: {current_window}, Evento: {event}, valores: {values}")
if event == sg.WIN_CLOSED:
current_window.close()
break
# Si pongo keys a los eventos que me permitan distinguir de qué ventana vienen
# es fácil despachar los eventos a otras funciones.
# En este ejemplo asumimos que los eventos tienen como prefijo de las keys el
# nombre de la ventana, por ejemplo:
# "-PRINCIPAL-CONFIGURAR-", "-CONFIGURACION-ACEPTAR-", etc...
# Ejercicio para el lector, parsear el evento para extraer el prefijo:
event_words = # ...
match event_words:
case ["PRINCIPAL", _]:
principal.procesar_eventos(current_window, event, valores, datos_juego)
case ["CONFIGURACION", _]:
secundario.procesar_eventos(current_window, event, valores, datos_juego)
# ...