Tabla de contenido
Una vez más aquí. Hoy vengo a dejar un pequeño artículo para un usuario que me ha preguntado cómo podía realizar una aplicación GUI de Python, ya que en sus estudios parece ser que solo le han enseñado un poco de Python desde la terminal. El caso es que revisando los apuntes que en su día tomé, encontré una algunos en referencia a cómo crear aplicaciones GUI de Python. Estos apuntes están ya un poco desfasados, ya que hoy en día hay muchos kits de herramientas de interfaz gráfica de usuario (GUI) que se pueden utilizar con Python. Posiblemente los tres más grandes y populares son PyQT, Tkinter y wxPython, los cuales funcionarán con Windows, macOS y Gnu/Linux. Además hay que decir que PyQt tiene la capacidad de funcionar también en dispositivos móviles.
Como suopngo que todo el mundo sabe, una interfaz gráfica de usuario es una aplicación que puede contener botones, ventanas y muchos otros widgets que el usuario puede utilizar para interactuar con la aplicación. En las siguientes líneas vamos a ver cómo se puede crear una interfaz gráfica de usuario con Python utilizando el kit de herramientas GUI de wxPython.
Definición de una GUI
Como mencioné antes, una interfaz gráfica de usuario (GUI) es una interfaz que se dibuja en la pantalla para que el usuario interactúe con la aplicación. Las interfaces de usuario tienen algunos componentes comunes en todas ellas (o casi todas):
- Ventana principal
- Menú
- Barra de herramientas
- Botones
- Entrada de texto
- Etiquetas
Todos estos elementos se conocen genéricamente como widgets. Hay muchos otros widgets comunes y muchos widgets personalizados compatibles con wxPython. El desarrollador puede tomar los widgets y los organizará de forma lógica en una ventana, para que el usuario interactúe con la aplicación.
Primeros pasos con wxPython
El kit de herramientas GUI de wxPython es un contenedor de Python alrededor de una biblioteca de C++ llamada wxWidgets. El primer lanzamiento de wxPython fue en 1998, por lo que wxPython existe desde hace bastante tiempo. La principal diferencia de wxPython con respecto a los otros kits de herramientas mencionados anteriormente, es que wxPython usa los widgets reales en la plataforma nativa siempre que sea posible. Esto hace que las aplicaciones de wxPython parezcan nativas del sistema operativo en el que se ejecutan.

Esto no quiere decir que wxPython no admita widgets personalizados. De hecho, el kit de herramientas wxPython ofrece muchos widgets personalizados, junto con docenas de widgets principales.
La página de descargas de wxPython tiene una sección llamada Archivos adicionales que es bueno revisar. Aquí hay una descarga del paquete de demostración de wxPython. Esta es una aplicación pequeña y agradable que demuestra la gran mayoría de los widgets que se incluyen con wxPython. La demostración permite a un desarrollador ver el código en una pestaña y ejecutarlo en una segunda pestaña. Incluso puede editar y volver a ejecutar el código en la demostración para ver cómo sus cambios afectan la aplicación.
Instalar wxPython
Para este ejemplo vamos a utilizar el último wxPython, que es wxPython 4. Las versiones wxPython 3 y wxPython 2 están diseñadas solo para Python 2. El paquete wxPython 4 es compatible con Python 2.7 y Python 3.
Hoy en día se puede utilizar pip para instalar wxPython 4, lo que no era posible en las versiones heredadas de wxPython. Puedes utilizar el siguiente comando en una terminal (Ctrl+Alt+T) para instalarlo en tu máquina:
pip install wxpython
En caso de que el anterior comando muestre mensajes de error, estos son útiles para descubrir qué falta, y puede usar la sección de requisitos previos en la página de wxPython Github para encontrar la información que necesitas si desea instalar wxPython en Gnu/Linux.
Hay algunas wheel de Python disponibles para las versiones de Gnu/Linux más populares, que se pueden encontrar en la sección Extras de Linux con las versiones GTK2 y GTK3. Para instalar una de esta wheel, usaríamos el siguiente comando:
pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-20.04/ wxPython
Asegúrate de haber modificado el comando anterior para que coincida con su versión de Gnu/Linux.
Bucles de eventos
Una interfaz gráfica de usuario funciona esperando que el usuario haga algo. Este algo se llama evento. Los eventos ocurren cuando el usuario hace algo, mientras la aplicación está enfocada o cuando el usuario usa el ratón para presionar un botón u otro widget.
Debajo de la interfaz, el kit de herramientas de la GUI está ejecutando un bucle infinito que se llama bucle de eventos. El bucle de eventos solo espera que ocurran los eventos y luego actúa sobre estos, de acuerdo con lo que el desarrollador ha codificado para que haga la aplicación.
Cuando estemos programando una interfaz gráfica de usuario, es importante tener en cuenta que necesitaremos conectar cada uno de los widgets a los controladores de eventos para que nuestra aplicación haga algo. Hay una consideración especial que debe tener en cuenta cuando trabajamos con bucles de eventos: se pueden bloquear la aplicación. Cuando bloquea un bucle de eventos, la GUI dejará de responder y se congelará para el usuario.
Cualquier proceso que inicie en una GUI que demore más de un cuarto de segundo probablemente debería iniciarse como un hilo o proceso separado. Esto evitará que la GUI se congele y ofrecerá una mejor experiencia de usuario.
El marco wxPython tiene métodos especiales seguros para subprocesos que puede usar para comunicarse con su aplicación para informarle que el subproceso ha finalizado o para actualizarlo.
Creación de una aplicación GUI de Python de esqueleto
El esqueleto de una aplicación en un contexto de GUI es una interfaz de usuario con widgets que no tienen controladores de eventos. Estos son útiles para la creación de prototipos. Básicamente, crea la GUI y la presenta a las partes interesadas para que la aprueben antes de dedicar mucho tiempo a la lógica de back-end.
Vamos a comenzar creando una aplicación del estilo «Hola mundo» con wxPython:

import wx app = wx.App() frame = wx.Frame(parent=None, title='Hola usuari@s de entreunosyceros.net') frame.Show() app.MainLoop()
Este ejemplo tiene dos partes: wx.App y wx.Frame. El wx.App es el objeto de la aplicación de wxPython y es necesario para ejecutar su GUI. La wx.App inicia algo llamado .MainLoop(), que es el ciclo de eventos.
La otra pieza es wx.Frame, y esta creará una ventana para que el usuario interactúe. En este caso, le decimos a wxPython que el marco no tiene padre y que su título es «Hola usuari@s de entreunosyceros.net«.
Si ejecutamos este código, así es como se verá la ventana:

De forma predeterminada, un wx.Frame incluirá botones para minimizar, maximizar y salir en la parte superior. Sin embargo, normalmente no creará una aplicación de esta manera. La mayoría del código de wxPython requerirá que subclasifiquemos wx.Frame y otros widgets para que podamos obtener todo el poder del conjunto de herramientas.
Para hacer las cosas un poco mejor, vamos a reescribir el anterior código como una clase. Esta reescritura nos va a dar el mismo resultado que el código anterior.
import wx class MyFrame(wx.Frame): def __init__(self): super().__init__(parent=None, title='Hola usuari@s de entreunosyceros.net') self.Show() if __name__ == '__main__': app = wx.App() frame = MyFrame() app.MainLoop()
Se puede utilizar este código como plantilla para nuestras aplicaciones. Sin embargo, esta aplicación no hace mucho, así que vamos a ver algunos de los otros widgets que podríamos añadir a la aplicación.
Widgets
El kit de herramientas de wxPython tiene más de cien widgets para elegir. Esto nos permite crear aplicaciones ricas en funciones, aun que entre tantas posibilidades es posible perderse un poco. Esta la principal razón por la cual la demostración de wxPython puede resultar tan útil, ya que tiene un filtro de búsqueda que podemos usar para ayudarnos a encontrar los widgets que podrían interesarnos en nuestro proyecto.
El siguiente paso que vamos a dar será dar al usuario la posibilidad de añadir texto y presionar un botón. Para conseguir esto, el código de nuestra aplicación sería el siguiente:

import wx class MyFrame(wx.Frame): def __init__(self): super().__init__(parent=None, title='Hola usuari@s de entreunosyceros.net') panel = wx.Panel(self) self.text_ctrl = wx.TextCtrl(panel, pos=(5, 5)) my_btn = wx.Button(panel, label='Pulsa aquí', pos=(5, 55)) self.Show() if __name__ == '__main__': app = wx.App() frame = MyFrame() app.MainLoop()
Cuando ejecutemos este código, la aplicación debería verse así:

El primer widget que vamos a añadir se llama wx.Frame. Este widget no es obligatorio.
Cuando añadimos este widget a un marco y es el único elemento secundario del marco, se expandirá automáticamente para llenar el marco consigo mismo.
El siguiente paso es agregar un wx.TextCtrl. El primer argumento para casi todos los widgets es a qué padre debe ir el widget. En este caso, nos interesa que el control de texto y el botón estén en la parte superior de la ventana, por lo que es necesario especificarlo.
También es necesario indicarle a wxPython el lugar en el que colocar el widget, esto se consigue mediante el parámetro pos, en el que se indicará la posición. En wxPython, la ubicación de origen es (0,0), que es la esquina superior izquierda del padre.
Al final añadirmos el botón a la ventana, y le damos una etiqueta. Para evitar que los widgets se superpongan, debemos establecer la coordenada y en 55 para la posición del botón.
Posicionamiento absoluto
Cuando escribimos las coordenadas exactas para la posición de nuestro widget, la técnica que utilizada se denomina posicionamiento absoluto (o eso me parece). La mayoría de los kits de herramientas de GUI brindan esta capacidad, pero en realidad no se recomienda.
A medida que la aplicación se vuelve más compleja, se vuelve muy difícil realizar un seguimiento de todas las ubicaciones de los widgets. Por lo que si tenemos que restablecer todas estas posiciones, podemos encontrarnos con un trabajo extremadamente complicado.
Para ahorrarnos estos problemas, todos los kits de herramientas GUI modernos ofrecen una solución para esto.
Dimensionadores (dimensionamiento dinámico)
El kit de herramientas de wxPython incluye dimensionadores, que se utilizan para crear diseños dinámicos. Estos administran la ubicación de nuestros widgets y los ajustarán cuando cambiemos el tamaño de la ventana de la aplicación. Otros kits de herramientas de GUI se referirán a los dimensionadores como diseños.
Estos son los tipos principales de dimensionadores más conocidos:
wx.BoxSizer
wx.GridSizer
wx.FlexGridSizer
En nuestro código vamos a añadir un wx.BoxSizer, y veremos así que podemos hacer un poquito mejor nuestro ejemplo.

import wx class MyFrame(wx.Frame): def __init__(self): super().__init__(parent=None, title='Hola usuari@s de entreunosyceros.net') panel = wx.Panel(self) my_sizer = wx.BoxSizer(wx.VERTICAL) self.text_ctrl = wx.TextCtrl(panel) my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5) my_btn = wx.Button(panel, label='Pulsa aquí') my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5) panel.SetSizer(my_sizer) self.Show() if __name__ == '__main__': app = wx.App() frame = MyFrame() app.MainLoop()
En este código crearemos una instancia de wx.BoxSizer y la pasamos wx.VERTICAL, que es la orientación en la que se añaden los widgets al dimensionador.
Para este ejemplo, los widgets se añaden verticalmente, lo que significa que se agregarán uno a una, de arriba a abajo. También puede establecer la orientación de BoxSizer en wx.HORIZONTAL. Si optamos por esto, los widgets se agregarán de izquierda a derecha.
Para agregar un widget a un medidor, utilizaremos .Add(). Esto acepta hasta cinco argumentos en referencia a:
- ventana (el widget)
- proporción
- bandera
- borde
- datos del usuario
El argumento de la ventana es el widget que se añadirá, mientras que la proporción establece cuánto espacio en relación con otros widgets en el dimensionador debe ocupar este widget en particular. De forma predeterminada, es cero, lo que le dice a wxPython que deje el widget en su proporción predeterminada.
El tercer argumento es bandera. De hecho, podemos pasar varias banderas si es lo que queremos, siempre que estas las separemos con una barra vertical: |. wxPython utiliza | para agregar banderas usando una serie de OR bit a bit.
En este ejemplo, añadimos el control de texto con los indicadores wx.ALL y wx.EXPAND. El indicador wx.ALL indica a wxPython que queremos añadir un borde en todos los lados del widget, mientras que wx.EXPAND hace que los widgets se expandan tanto como puedan dentro del dimensionador.
Finalmente, tiene el parámetro de borde, que le dice a wxPython cuántos píxeles de borde desea alrededor del widget. El parámetro userData solo se usa cuando desea hacer algo complejo con el tamaño del widget.
Cuando ejecutemos esta versión del código, nuestra aplicación de ejemplo debería tener el siguiente aspecto:

Si te interesa obtener más información sobre los dimensionadores, la documentación de wxPython te puede sacar de dudas.
Añadir un evento
Si bien la aplicación se ve más interesante visualmente, todavía no hace nada. Si presionamos el botón, realmente no sucede nada. Para solucionar esto, vamos a añadir un evento.

import wx class MyFrame(wx.Frame): def __init__(self): super().__init__(parent=None, title='Hola usuari@s de entreunosyceros.net') panel = wx.Panel(self) my_sizer = wx.BoxSizer(wx.VERTICAL) self.text_ctrl = wx.TextCtrl(panel) my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5) my_btn = wx.Button(panel, label='Pulsa aquí') my_btn.Bind(wx.EVT_BUTTON, self.on_press) my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5) panel.SetSizer(my_sizer) self.Show() def on_press(self, event): value = self.text_ctrl.GetValue() if not value: print("No hay nada que hacer!") else: print(f'Has escrito: "{value}"') if __name__ == '__main__': app = wx.App() frame = MyFrame() app.MainLoop()
Los widgets en wxPython nos permiten adjuntarles enlaces de eventos para que puedan responder a ciertos tipos de eventos.
En nuestro ejemplo, evidentemente queremos que el botón haga algo cuando el usuario lo presione. Esto se puede lograr llamando al método .Bind() del botón. Este método toma el evento al que desea vincularse, el controlador para llamar cuando ocurre el evento, una fuente opcional y un par de identificaciones opcionales.
En este ejemplo, vincularemos el objeto de botón al evento wx.EVT_BUTTON y le indicaremos que llame a on_press() cuando se active ese evento.
Un evento se ‘dispara’ cuando el usuario realiza el evento al que se ha vinculado. En este caso, el evento que se configuró es el evento de presionar el botón, wx.EVT_BUTTON.
.on_press() acepta un segundo argumento al que puede llamar event. Esto es por convención. Podrías llamarlo de otra forma si quisieras. Sin embargo, el parámetro de evento aquí se refiere al hecho de que cuando se llama a este método, su segundo argumento debe ser un objeto de evento de algún tipo.
Dentro de .on_press(), se puede obtener el contenido del control de texto llamando a su método GetValue(). Luego, imprime una cadena en la salida estándar según el contenido del control de texto.

Esto es tan solo un ejemplo de lo que los usuarios podemos hacer con wxPython. Una interfaz de usuario puede contener muchas más cosas que las que se muestran en este ejemplo, pero espero que a ese usuario le sirva para empezar a meterse en este apasionante mundo.