Tutorial 2 - Hacerlo interesante

En Tutorial 1, generamos un proyecto stub que era capaz de ejecutarse, pero no escribimos ningún código nosotros mismos. Echemos un vistazo a lo que se generó para nosotros.

Qué se generó

En el directorio src/helloworld, deberías ver 3 archivos: __init__.py, __main__.py y app.py.

__init__.py marca el directorio helloworld como un módulo importable de Python. Es un archivo vacío; el mero hecho de que exista le dice al intérprete de Python que el directorio helloworld define un módulo.

__main__.py marca el módulo helloworld como un tipo especial de módulo - un módulo ejecutable. Si intentas ejecutar el módulo helloworld usando python -m helloworld, el archivo __main__.py es donde Python empezará a ejecutarse. El contenido de __main__.py es relativamente simple:

from helloworld.app import main

if __name__ == "__main__":
    main().main_loop()

Es decir, importa el método main de la aplicación helloworld; y si se está ejecutando como punto de entrada, llama al método main(), e inicia el bucle principal de la aplicación. El bucle principal es la forma en que una aplicación GUI escucha la entrada del usuario (como clics de ratón y pulsaciones de teclado).

El archivo más interesante es app.py - contiene la lógica que crea la ventana de nuestra aplicación:

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW

class HelloWorld(toga.App):
    def startup(self):
        main_box = toga.Box()

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()

def main():
    return HelloWorld()

Vamos a ir a través de esta línea por línea:

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW

En primer lugar, importamos el conjunto de herramientas de widgets toga, así como algunas clases de utilidades y constantes relacionadas con el estilo. Nuestro código aún no las utiliza, pero lo haremos en breve.

A continuación, definimos una clase:

class HelloWorld(toga.App):

Cada aplicación Toga tiene una única instancia toga.App, que representa la entidad en ejecución que es la aplicación. La app puede acabar gestionando múltiples ventanas; pero para aplicaciones sencillas, habrá una única ventana principal.

A continuación, definimos un método startup():

def startup(self):
    main_box = toga.Box()

Lo primero que hace el método de inicio es definir una caja principal. El esquema de diseño de Toga se comporta de forma similar a HTML. Construyes una aplicación construyendo una colección de cajas, cada una de las cuales contiene otras cajas, o widgets reales. Luego aplicas estilos a estas cajas para definir cómo consumirán el espacio disponible en la ventana.

En esta aplicación, definimos una sola caja, pero no ponemos nada en ella.

A continuación, definimos una ventana en la que podemos poner esta caja vacía:

self.main_window = toga.MainWindow(title=self.formal_name)

Esto crea una instancia de toga.MainWindow, que tendrá un título que coincida con el nombre de la aplicación. Una ventana principal es un tipo especial de ventana en Toga - es una ventana que está estrechamente vinculada al ciclo de vida de la aplicación. Cuando la Ventana Principal se cierra, la aplicación sale. La Ventana Principal es también la ventana que tiene el menú de la aplicación (si estás en una plataforma como Windows donde las barras de menú son parte de la ventana)

A continuación añadimos nuestra caja vacía como contenido de la ventana principal, e indicamos a la aplicación que muestre nuestra ventana:

self.main_window.content = main_box
self.main_window.show()

Por último, definimos un método main(). Esto es lo que crea la instancia de nuestra aplicación:

def main():
    return HelloWorld()

Este método main() es el que es importado e invocado por __main__.py. Crea y devuelve una instancia de nuestra aplicación HelloWorld.

Esa es la aplicación Toga más simple posible. Pongamos algo de nuestro propio contenido en la aplicación, y hagamos que la aplicación haga algo interesante.

Añadir contenido propio

Modifica tu clase HelloWorld dentro de src/helloworld/app.py para que tenga este aspecto:

class HelloWorld(toga.App):
    def startup(self):
        main_box = toga.Box(style=Pack(direction=COLUMN))

        name_label = toga.Label(
            "Your name: ",
            style=Pack(padding=(0, 5)),
        )
        self.name_input = toga.TextInput(style=Pack(flex=1))

        name_box = toga.Box(style=Pack(direction=ROW, padding=5))
        name_box.add(name_label)
        name_box.add(self.name_input)

        button = toga.Button(
            "Say Hello!",
            on_press=self.say_hello,
            style=Pack(padding=5),
        )

        main_box.add(name_box)
        main_box.add(button)

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()

    def say_hello(self, widget):
        print(f"Hello, {self.name_input.value}")

Nota

No elimine las importaciones en la parte superior del archivo, o el main() en la parte inferior. Solo necesitas actualizar la clase HelloWorld.

Veamos en detalle lo que ha cambiado.

Seguimos creando una caja principal; sin embargo, ahora estamos aplicando un estilo:

main_box = toga.Box(style=Pack(direction=COLUMN))

El sistema de diseño integrado de Toga se llama «Pack». Se comporta de forma muy parecida a CSS. Defines objetos en una jerarquía - en HTML, los objetos son <div>, <span>, y otros elementos DOM; en Toga, son widgets y cajas. A continuación, puedes asignar estilos a los elementos individuales. En este caso, estamos indicando que se trata de una caja COLUMN - es decir, es una caja que consumirá todo el ancho disponible, y ampliará su altura a medida que se añada contenido, pero intentará ser lo más corta posible.

A continuación, definimos un par de widgets:

name_label = toga.Label(
    "Your name: ",
    style=Pack(padding=(0, 5)),
)
self.name_input = toga.TextInput(style=Pack(flex=1))

Aquí definimos un Label y un TextInput. Ambos widgets tienen estilos asociados; la etiqueta tendrá 5px de relleno a su izquierda y derecha, y ningún relleno en la parte superior e inferior. El TextInput está marcado como flexible - es decir, absorberá todo el espacio disponible en su eje de diseño.

El TextInput se asigna como una variable de instancia de la clase. Esto nos da fácil acceso a la instancia del widget - algo que usaremos en un momento.

A continuación, definimos una caja para alojar estos dos widgets:

name_box = toga.Box(style=Pack(direction=ROW, padding=5))
name_box.add(name_label)
name_box.add(self.name_input)

La caja_de_nombre es una caja igual que la caja principal; sin embargo, esta vez, es una caja ROW. Eso significa que el contenido se añadirá horizontalmente, e intentará que su anchura sea lo más estrecha posible. La caja también tiene algo de relleno - 5px en todos los lados.

Ahora definimos un botón:

button = toga.Button(
    "Say Hello!",
    on_press=self.say_hello,
    style=Pack(padding=5),
)

El botón también tiene 5px de relleno en todos los lados. También definimos un handler - un método a invocar cuando se pulsa el botón.

A continuación, añadimos el cuadro de nombre y el botón al cuadro principal:

main_box.add(name_box)
main_box.add(button)

Esto completa nuestro diseño; el resto del método de inicio es como antes - definiendo una MainWindow, y asignando la caja principal como contenido de la ventana:

self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = main_box
self.main_window.show()

Lo último que tenemos que hacer es definir el manejador del botón. Un manejador puede ser cualquier método, generador o co-rutina asíncrona; acepta el widget que generó el evento como argumento, y será invocado cada vez que se pulse el botón:

def say_hello(self, widget):
    print(f"Hello, {self.name_input.value}")

El cuerpo del método es una simple sentencia print - sin embargo, interrogará el valor actual de la entrada name, y usará ese contenido como el texto que se imprime.

Ahora que hemos realizado estos cambios podemos ver cómo quedan iniciando de nuevo la aplicación. Como antes, usaremos el modo desarrollador:

(beeware-venv) $ briefcase dev

[helloworld] Starting in dev mode...
===========================================================================

Notarás que esta vez, no instala dependencias. Briefcase puede detectar que la aplicación ha sido ejecutada anteriormente, y para ahorrar tiempo, sólo ejecutará la aplicación. Si añades nuevas dependencias a tu aplicación, puedes asegurarte de que se instalan pasando una opción -r cuando ejecutes briefcase dev.

Esto debería abrir una ventana GUI:

Ventana Hello World Tutorial 2, en macOS

Si introduce un nombre en el cuadro de texto y pulsa el botón GUI, debería ver aparecer la salida en la consola donde inició la aplicación.

Siguientes pasos

Ahora tenemos una aplicación que hace algo un poco más interesante. Pero sólo se ejecuta en nuestro propio ordenador. Vamos a empaquetar esta aplicación para su distribución. En Tutorial 3, vamos a empaquetar nuestra aplicación como un instalador independiente que podríamos enviar a un amigo, un cliente, o subir a una App Store.