U5 Interfaz gráfica
Unidad 5 Interfaz Gráfica de Usuarios (GUI)
Informática III – ISM – UNL 2018
Motivación
Durante todo el cursado los programas que hemos realizado se utilizan mediante ingreso de texto por teclado en una consola (es decir, un interprete de comandos). Esto hace poco “amigable” e interactivo a nuestros desarrollos, más aún que actualmente tenemos tantos dispositivos con posibilidades gráficas como laptops, celulares, tablets, etc. Por lo tanto daremos una introducción a la programación de interfaces gráficas, usando en Python el módulo wxPython.
GUI para Python
GUI proviene de Graphical User Interface, que podríamos traducir a “Interfaz Gráfica de Usuario” en español, es decir, una forma de programar que nos permita desarrollar usando elementos gráficos estándar como ventanas, menúes, botones, grillas, etc.
Cada lenguaje tiene sus propios GUIs, en el caso de Python podemos nombrar a Tkinter (nativo en Python) wxPython, Qt, GTK, entre los más conocidos. En este cursado aprenderemos wxPython.
wxPython
wxPython es un módulo para Python de la librería wxWidgets (escrita para C++) creado en 1996. El principal desarrollador de wxPython es Robin Dunn, quien en 1995 necesitaba mostrar un desarrollo gráfico en una máquina Unix (HP-UX) , pero su jefe también quería mostrarlo en Windows 3.1. wxPython es cross platform, es decir, puede usarse el mismo código para desarrollar en Windows, Mac y Unix. Nota: una guía de instalación de wxPython en
Thonny puede encontrarse en este link. Ejemplo de wxPython
Veamos el siguiente ejemplo:
Ejemplo de wxPython
Veamos el siguiente ejemplo:
Importar el módulo wxPython
Esto importa el módulo que tiene todas las funciones de wxPython. Todas las funciones de wxPython que usemos de ahora en adelante deberán comenzar con wx.
Ejemplo de wxPython
Veamos el siguiente ejemplo:
Se crea la aplicación gráfica
Si o si, todas las aplicaciones que desarrollemos de ahora en adelante con wxPython deben tener creado un objeto wx.App() que representa la aplicación gráfica.
Ejemplo de wxPython
Veamos el siguiente ejemplo:
Se crea la ventana gráfica (wx.Frame) y se le da un título
wx.Frame es la ventana gráfica, un tipo de “contenedor” (container) ya que es el objeto base que contendrá a los demás objetos (botones, menués, grillas, etc.). En otras palabras un objeto Frame es el “padre” (parent) de los otros objetos que crearemos. En este caso, el parent de frame no existe (es None) ya que es la
ventana principal (o sea, no depende de nadie para inicializarse). Ejemplo de wxPython
Veamos el siguiente ejemplo:
Se muestra la ventana
Al objeto frame debemos mostrarlo en pantalla usando el método Show() Si no lo hacemos, al ejecutarlo no veremos la ventana, aunque esté creada correctamente.
Ejemplo de wxPython
Veamos el siguiente ejemplo:
Inicia la ejecución de la aplicación gráfica
Finalmente ejecutamos un bucle infinito (si! y antes se enseñó que no debieran existir…). MainLoop (o “bucle principal”) nos permite ejecutar la aplicación hasta que la misma se cierre Mientras tanto, permite capturar todos los eventos de la ventana, como por ejemplo cuando se hace clic en el botón cerrar.
El objeto wx.Frame
Como vimos en el ejemplo, Frame es el objeto que representa una ventana y forma parte de los contenedores. wx.Frame posee una barra de título, bordes y un área central contenedora. Barra de titulo
Borde Área contenedora
El objeto wx.Frame
Tamaño de la ventana: Se puede aplicar un tamaño específico de ventana mediante el método SetSize(ancho,alto), donde alto y ancho son los valores en píxeles. Por defecto el tamaño es 400x250 Posición de la ventana: También se puede configurar en qué posición de nuestra pantalla posicionar inicialmente la ventana con SetPosition((x,y)). Donde x e y son las posiciones de la esquina superior izquierda de la ventana. Por defecto (0,0).
El objeto wx.Frame
Posición de la ventana: frame.SetPosition((0,0))
frame.SetPosition((500,0))
- Centrado en pantalla: frame.Centre()
Crear objetos de wx.Frame
Usar una variable de tipo wx.Frame nos permite hacer ventanas y aplicaciones rápidamente, pero necesitamos cosas más complejas, como eventos, por ejemplo. Para eso debemos crear nuestra propia clase ventana, extendiendo de la clase wx.Frame: import wx class MiVentana(wx.Frame): def __init__(self,parent,id,name): wx.Frame.__init__(self,parent,id,name)
Crear objetos de wx.Frame
class es una palabra reservada que nos permite crear una ventana customizada, pero usando las propiedades genéricas de wx.Frame. Cuando se crea una nueva ventana se ejecuta automáticamente el método __init__() por eso lo escribimos ejecutando el método __init__() de la clase (wx.Frame). Una vez que definimos MiVentana, decimos que esta clase es “hija” de la clase wx.Frame. Análogamente decimos que wx.Frame es “padre” de MiVentana.
Crear objetos de wx.Frame
Dentro de la clase MiVentana el objeto self sirve para referenciar las propiedades de la ventana misma (self). Por ejemplo, si queremos cambiar el tamaño, hacemos: self.SetSize(300,500) Además, cada función creada dentro de la clases MiVentana, deberá llevar como primer parámetro la palabra self: class MiVentana(wx.Frame): … def CargarTitulo(self,titulo): self.SetTitle(titulo)
Crear objetos de wx.Frame
Este es un ejemplo de una clase derivada de wx.Frame
Menú principal
El menú principal de una ventana es el que vemos en la barra de título. wxPython nos permite crear esta barra de menú mediante el componente wx.MenuBar. Cada menú dentro de la barra puede crearse mediante wx.Menu. Y los items de cada menú mediante wx.MenuItem. Luego de creados cada item debe ser agregado a su correspondiente menú, y cada menú a la barra. Veamos un ejemplo:
Menú principal
Menú principal
wxFormBuilder
Programar ventanas desde el código si bien permite mayor control, hace más difícil de diseñar un programa con ventanas. Para diseñar visualmente las ventanas usaremos un software que nos permitirá crear las ventanas y luego exportar el código generado a nuestra IDE (Thonny). Este tipo de software se denominan RAD por Rapid Application Development (Desarrollo Rápido de Aplicaciones). Este software es wxFormBuilder (link) cuyo tutorial para instalar en su máquina pueden encontrarlo en el PDF de instalación de wxPython y Pyo. En este cursado cubriremos el uso de wxFormBuilder, pero también se pueden usar otras alternativas como wxGlade o
DialogBlocks (free trial). wxFormBuilder
Selección de componentes
Componentes del proyecto
Visualización gráfica/ Visualización código
Propiedades y eventos de los componentes Ejemplo de uso de wxFormBuilder
A continuación se mostrarán los pasos necesarios para hacer una aplicación con wxFormBuilder y wxPython. Vamos a generar una ventana con un botón y al hacer clic en el mismo, se mostrará un mensaje La salida debería ser la siguiente:
Ejemplo de uso de wxFormBuilder
El primer paso es crear el Proyecto
Se ingresa un nombre en “name” y en “path” se hace clic en […] para elegir donde se guarda en la PC
Ejemplo de uso de wxFormBuilder
(1) Ahora agregaremos wx.Frame a nuestro (2) En Component Palette proyecto Elegimos la pestaña “Forms” y luego “Frame”
Le damos el nombre (“name”), que será el nombre de variable y el titulo (“title”)
Ejemplo de uso de wxFormBuilder La Paleta de Componentes (“Component Palette”) permite seleccionar los componentes visuales para hacer una aplicación con wxPython
Cada pestaña es una categoría: Common (Comunes) tiene los componentes más usados en general, botones, menú de selección, texto estático, checks, imágenes, etc. Additional (adicionales) tiene componentes menos usados, pero útiles como buscador de archivos, colores, hiperlinks. Data (datos) posee los componentes para mostrar información, como grillas. Ejemplo de uso de wxFormBuilder
Cada pestaña es una categoría: Containers (contenedores) son los componentes que permiten tener otros componentes, como paneles y separadores. Menu/Toolbar (menú y barra de herramientas) como los que vimos, pero permite otras configuraciones de menú con botones e imágenes. Layout: permiten ordenar automáticamente los componentes mediante los “sizers”.
Forms: frames y ventanas de diálogos. Ejemplo de uso de wxFormBuilder Una vez creada la Ventana tenemos que posicionar un botón para hacer clic. Para ese paso vamos a la pestaña “Common” y seleccionamos “wxButton”. Probablemente veamos un mensaje de error como este:
El mensaje nos indica que tenemos que agregar un “sizer” para que el objeto creado se posicione correctamente en la ventana.
Ejemplo de uso de wxFormBuilder Podemos encontrar los sizers en la pestaña “Layout”, donde veremos las siguientes opciones:
wxBoxSizer es el que más se usa y nos permite elegir ordenar los componentes dentro de la ventana en sentido horizontal o vertical. wxGridSizer permite ordenar los componentes en una grilla de filas y columnas de igual tamaño. wxFlexGridSizer es igual al wxGridSizer, pero las columnas o filas pueden variar de tamaño.
wxGridBagSizer similar a wxGridSizer , pero permite cambiar el tamaño por celdas. Ejemplo de uso de wxFormBuilder
(1) Agregamos el sizer (2) En Component Palette A la ventana Elegimos la pestaña “Layout” y luego “wxBoxSizer”
(3) Le damos el nombre (“name”) y la orientación “orient” wxVERTICAL, de manera que los items ubiquen uno debajo del otro.
Ejemplo de uso de wxFormBuilder
(1) dentro del sizer (2) En Component Palette ubicamos el botón elegimos la pestaña “Common” y luego “wxButton”
(3) Le damos el nombre (“name”) y un mensaje para mostrar en la opción “label”. Por ejemplo, “Click me!”
Ejemplo de uso de wxFormBuilder
El siguiente paso es situar un componente de texto estático (wxStaticText) dentro de la ventana. El mensaje estará vacío cuando la ventana se abra, y mostrará un mensaje cuando hagamos click en el botón anteriormente creado.
Para eso elegiremos en la pestaña Common el componenete wxStaticText y lo colocaremos en el wxBoxSizer antes creado, así aparecerá debajo del botón.
Ejemplo de uso de wxFormBuilder
(1) dentro del sizer (2) En Component Palette ubicamos el texto elegimos la pestaña “Common” y luego “wxStaticText”
(3) Le damos el nombre texto e iniciamos el “label” con una cadena vacía
Ejemplo de uso de wxFormBuilder
Una vez creada la ventana, podemos “exportar” el código en Python y usarlo en Thonny para ejecutar nuestro código. Para ello debemos ir a la pestaña “Python” de el menú “Editor”:
En esa pestaña veremos la ventana creada pero en formato de líneas de código. Copiaremos ese código y lo pasaremos a una aplicación de Thonny. Cada vez que modifiquemos algo en wxFormBuilder deberemos copiarlo a Thonny para evitar que no se muestren los últimos cambios. Ejemplo de uso de wxFormBuilder
Ejemplo de uso de wxFormBuilder
Fuera de MiFrame creamos la aplicación Ejemplo de uso de wxFormBuilder Ahora se puede ejecutar el código, y deberíamos tener una ventana como la siguiente.
Pero al hacer clic en el botón no pasa nada. Debemos setear los “eventos” del botón.
Los eventos son parte muy importante en una aplicación visual, ya que permite interactuar al usuario con sus componentes (ya veremos esto en detenimiento).
Para generar un evento en un componente debemos ir a la pestaña “Events” en “Object Properties” (Propiedades de Objetos) Ejemplo de uso de wxFormBuilder El evento que queremos que se ejecute debe suceder cuando el botón sea clickeado, que es “OnButtonClick”
Allí escribimos el nombre de la función que va a ejecutarse cuando se haga click. En este caso la llamaremos “escribirMensaje”.
Esa función automáticamente se escribirá en la pestaña de Python junto con el código de la clase MiFrame: def escribirMensaje( self, event ): event.Skip()
Y también, dentro de la clase MiFrame se “conectará” el componente del botón al evento con el método Bind que recibe como parámetros el tipo de evento (wx.EVT_BUTTON) y el nombre de la función que ejecutará el evento: self.boton.Bind( wx.EVT_BUTTON, self.escribirMensaje ) Ejemplo de uso de wxFormBuilder
Dentro de la función escribirMensaje pondremos lo que deseamos que se ejecute, en nuestro caso que la propiedad “label” del componente texto muestre un mensaje.
Para ello usamos el método SetLabel() de wx.StaticText def escribirMensaje( self, event ): self.texto.SetLabel("Hola, wxMundo!")
Notemos que self representa al objeto padre (MiFrame), texto es el componente wx.StaticText y el mensaje es “Hola, wxMundo”.
También notemos que como la propiedad se llama “label” el método para cambiar la propiedad siempre tiene el prefijo “Set”. Lo mismo sucede con otras propiedades (SetStyle, SetName, etc.)
Para leer el contenido de una propiedad se usa el prefijo “Get”. Ejemplo de uso de wxFormBuilder
Ejemplo de uso de wxFormBuilder
Conexión del botón al evento (wx.EVT_BUTTON) y a la función que se ejecutará al dar click
Ejemplo de uso de wxFormBuilder
Ejecución del evento, cambiando el valor de la propiedad label de texto.
Ejemplo de uso de wxFormBuilder Antes y después de hacer click:
wx.BoxSizer
¿Qué pasaría si a la propiedad orient del sizer del ejemplo anterior la cambiamos a wx.HORIZONTAL? Veríamos algo como esto:
El container wx.BoxSizer permite manejar dos tipos de alineaciones, verticales u horizontales. wx.BoxSizer
En el código veremos que un wx.BoxSizer se define de la siguiente manera: box = wx.BoxSizer(wx.HORIZONTAL)
Si cambiamos a orientación vertical: box = wx.BoxSizer(wx.VERTICAL)
wx.BoxSizer Para agregar elementos a un wx.BoxSizer se utiliza el método Add, por ejemplo: box.Add(componente,proportion,flag,border)
Los parámetros proportion, flag y border de cada componente se puede ver seleccionando el componente en wxFormBuilder y buscando en “Properties” los ítems “sizeritem” y “sizeritembase”:
Componente seleccionado
Componente seleccionado
wx.BoxSizer Para agregar elementos a un wx.BoxSizer se utiliza el método Add, por ejemplo: box.Add(componente,proportion,flag,border)
proportion es la opción para cambiar la proporción de tamaño a los distintos componentes que vamos agregando. Por defecto la proporción inicial es 0.
En el siguiente ejemplo tenemos un wx.BoxSizer horizontal y agregamos 3 componentes con proporciones distintas. A mayor proporción más tamaño. box.Add(c1,0) box.Add(c2,1) box.Add(c3,2)
En este caso el componente de proporción 2 tiene el doble que el de proporción 1. wx.BoxSizer Para agregar elementos a un wx.BoxSizer se utiliza el método Add, por ejemplo: box.Add(componente,proportion,flag,border)
flag nos permite habilitar los bordes (con tamaño border=5) en las distintas partes del componente, por ejemplo si a c1 le habilitamos el borde derecho: box.Add(c1,0,wx.RIGHT, 5) Se puede habilitar el izquierdo junto con el derecho usando | : box.Add(c1,0,wx.RIGHT | wx.LEFT, 5)
Si queremos borde en todas las direcciones: wx.ALL
wx.BoxSizer
Muchas veces queremos tener configuraciones en las cuales los componentes en la misma ventana tengan distintas orientaciones, por ejemplo:
wx.BoxSizer
Podemos combinar varios wx.BoxSizer’s dentro de otros
En este ejemplo tenemos un sizerBase (en rojo) con orientación wx.HORIZONTAL que tiene 2 componentes que a su vez también son wx.BoxSizer (sizer1 y sizer2)
sizer1 sizer2 sizerBase
wx.BoxSizer
sizer1 tiene orientación wx.VERTICAL, mientras que sizer2 tiene orientación wx.HORIZONTAL. Ambos están dentro de sizerBase.
sizer1 tiene proporción=0 y sizer2 tiene proporción=1.
A su vez V1, V2 y V3 son componentes de sizer1, y H1, H2 y H3 de sizer2
sizer1 sizer2
wx.BoxSizer
El código y el proyecto wxFormBuilder de esa parte sería:
sizerBase = wx.BoxSizer(wx.HORIZONTAL)
sizer1 = wx.BoxSizer(wx.VERTICAL)
sizer2 = wx.BoxSizer(wx.HORIZONTAL)
# agregar componentes a sizer1
sizer1.Add(V1,2,wx.ALL)
sizer1.Add(V2,1,wx.ALL) sizer1.Add(V3,0,wx.ALL) # agregar componentes a sizer2
sizer2.Add(H1,0,wx.ALL)
sizer2.Add(H2,0,wx.ALL)
sizer2.Add(H3,2,wx.ALL)
# agregar sizer1 y sizer2 al sizerBase:
sizerBase.Add(sizer1, 0)
sizerBase.Add(sizer2, 1) Otros Sizers
wx.GridSizer – Permite ordenar los componentes en una tabla bidimensional. Cada celda dentro de la tabla tiene el mismo tamaño: grilla = wx.GridSizer(filas, columnas, espacio_v, espacio_h) – En este caso filas y columnas es la cantidad de filas y columnas que usaremos, mientras que espacio_v y espacio_h son el espacio vertical y horizontal entre componentes, respectivamente. grilla = wx.GridSizer(4 , 3 , 5, 10) nos genera algo así:
Otros Sizers
wx.GridSizer – El método Add también se usa para wx.GridSizer – En este caso se empieza agregando componentes por la fila y luego por columna. – Otro flag que podemos usar con Add es wx.EXPAND, que hace que el componente agregado tome toda la celda: grilla.Add(b00,0,wx.EXPAND) espacio_h=10 grilla.Add(b01,0,wx.EXPAND) grilla.Add(b10,0,wx.EXPAND) grilla.Add(b11,0,wx.EXPAND)
espacio_v=5 Otros Sizers
wx.FlexGridSizer – Es similar a wx.GridSizer pero permite tener celdas con el mismo alto en una fila y con el mismo ancho en una columna, pero no necesariamente todas las celdas tienen el mismo alto y ancho. flex = wx.FlexGridSizer(filas,columnas,espacio_v,espacio_h) – Los métodos AddGrowableRows y AddGrowableCols permiten seleccionar qué filas y columnas podrán “agrandarse” si se aplica wx.EXPAND. flex.AddGrowableRow(0) flex.AddGrowableCol(2)
Otros Sizers
wx.GridBagSizer – Es el más complejo de los sizers, permite posicionar un componente directamente en una posición pos=(x,y) de la grilla. – Además puede hacer que un componente ocupe varias filas y/o columnas (span). bag = wx.GridBagSizer(espacio_v,espacio_h) bag.Add(b00, pos=(0,0) , span=(1,2),wx.EXPAND) bag.Add(b11, pos=(1,1) )
Eventos
La parte más importante de las apps GUI (además de la visualización) son los eventos. De hecho, decimos que una aplicación gráfica esta orientada a los eventos.
Sin eventos, solo tenemos una “maqueta” de la aplicación.
Los eventos nos permite interactuar mediante teclado, mouse, y otros tipos de periféricos con el entorno gráfico. Existen muchos tipos de evento: – De teclado (al presionar tecla, al dejar de presionar) – De mouse (mover, hacer click, mantener presionado) – De wx.Frame (al cerrar una ventana, al abrir) – Aquellos que dependen del componente.
Eventos
Evento de mouse: – wx.EVT_MOTION
Eventos
Evento de mouse: – wx.EVT_MOTION – En la función MoverMouse es donde se definirá qué hacer cuando el mouse se mueve por encima del wx.Frame que hemos creado. – Notemos que la función recibe como parámetro a event. Esta variable es de tipo wx.MoveEvent y posee información del evento que la creo, por ejemplo para obtener la posición actual del mouse usamos el método GetPosition() que retorna la lista [x,y]. def MoverMouse(self,event): [x,y] = event.GetPosition() print(“(x,y)=(%d,%d)”%(x,y))
Eventos
Evento de mouse: – Si ejecutamos ese código en la IDE Thonny tendremos que cada que vez que pasemos el mouse sobre el wx.Frame veremos en la salida de las coordenadas en píxeles.
– Con un wx.StaticText que modifique su label en el evento podríamos mejorar esto:
Eventos
Conexión de eventos (Binding) – Detengamos un poco en la definición del wx.Frame que creamos, y en especial en el método Bind() – Bind nos permite conectar un componente a un evento y designar la función que se ejecutará cuando el evento se “dispare”: self.Bind(wx.EVT_MOTION,self.MoverMouse)
Eventos
Conexión de eventos (Binding) – Detengamos un poco en la definición del wx.Frame que creamos, y en especial en el método Bind(). – Bind nos permite conectar un componente a un evento y designar la función que se ejecutará cuando el evento se “dispare”: self.Bind(wx.EVT_MOTION,self.MoverMouse)
Componente que dispara el evento, en este caso el mismo wx.Frame
Eventos
Conexión de eventos (Binding) – Detengamos un poco en la definición del wx.Frame que creamos, y en especial en el método Bind(). – Bind nos permite conectar un componente a un evento y designar la función que se ejecutará cuando el evento se “dispare”: self.Bind(wx.EVT_MOTION,self.MoverMouse)
Tipo de evento que la función debe esperar que se ejecute
Eventos
Conexión de eventos (Binding) – Detengamos un poco en la definición del wx.Frame que creamos, y en especial en el método Bind() – Bind nos permite conectar un componente a un evento y designar la función que se ejecutará cuando el evento se “dispare”: self.Bind(wx.EVT_MOTION,self.MoverMouse)
Función que se ejecutará cuando el evento se genere
Eventos
Otros eventos de Mouse: – OnLeftDown (wx.EVT_LEFT_DOWN): al presionar con el botón izquierdo – OnLeftUp (wx.EVT_LEFT_UP): al dejar de presionar con el botón izquierdo – OnLeftDClick (wx.EVT_LEFT_DCLICK): al hacer doble click con el botón izquierdo. – OnRightDown (wx.EVT_RIGHT_DOWN): al presionar con el botón derecho – OnRightUp (wx.EVT_RIGHT_UP): al dejar de presionar con el botón derecho – OnMiddleDown (wx.EVT_MIDDLE_DOWN): al presionar con el botón del medio (rueda) – OnMiddleUp (wx.EVT_MIDDLE_UP): al dejar presionar con el botón del medio (rueda) – OnMouseWheel (wx.EVT_MOUSEWHEEL): al mover la rueda del mouse arriba/abajo. Eventos
Eventos de menú: – Ya vimos como crear un menú, pero no hemos generado ningún evento cuando algún item es clickeado. – El evento que se dispara cuando se hace click en el menú es wx.EVT_MENU. – Cada ítem creado tiene asociado un ID único para identificarlo cuando un EVT_MENU se dispara, supongamos los siguientes items. – Lo primero que haremos es crear el menú a través de wxFormBuilder y asignarle un evento a cada ítem:
Eventos
Eventos de menú: – Seleccionamos un ítem y vamos a Events, donde crearemos la función que maneje el evento de ese ítem:
Item elegido Función asociada al evento – Esto genera las funciones en el código que podemos ver haciendo click en la pestaña de Python:
Eventos
Eventos de menú: – Dentro de cada función manejadora del evento (Event Handler) podemos programar qué queremos que suceda. – Un ejemplo sería probar de seleccionar “Cerrar” y que la ventana se cierre, para esto usamos el método Close() de wx.Frame: def OnCerrarItem( self, event ): self.Close()
– Pero muchas veces un usuario hace click equivocadamente y no quiere cerrar la ventana, por lo tanto es mejor mostrar una ventana que pregunte si se desea cerrar o no la ventana: una confirmación. – Para ello usaremos el componente wx.MessageDialog wx.MessageDialog(parent,mensaje,titulo,estilo) – Donde mensaje es lo que se desea “preguntar” al usuario, titulo de la ventana de diálogo y estilo es un grupo de flags que le dará formato a la ventana. Eventos
wx.MessageDialog: – Veamos como quedaría la función que se ejecuta al clickear en Cerrar:
– Generamos el wx.MessageDialog con el identificador diag, la ventana mostrará la pregunta “¿Desea salir?” y utilizará la constante de estilo wx.YES_NO, es decir mostrará un botón para SI y otro para NO. – Luego se muestra el diálogo con el método ShowModal() que permite esperar la elección del usuario (SI o NO). – Luego se compara la respuesta ret con la constante wx.ID_YES que identifica la respuesta cuando el botón seleccionado fue SI. En ese caso se cierra la ventana.
Eventos
wx.MessageDialog:
Mensaje
wx.ID_NO wx.ID_YES
Eventos
Eventos de teclado: – Además del mouse, el otro gran dispositivo de entrada es el teclado y muchas veces queremos capturar los eventos de teclado para brindar alguna funcionalidad a nuestros programas. – Existen 3 eventos de teclado: • wx.EVT_KEY_DOWN (OnKeyDown) : se ejecuta cuando alguna tecla ha sido presionada • wx.EVT_KEY_UP (OnKeyUp): se ejecuta cuando alguna tecla ha sido levantada. • wx.EVT_CHAR (OnChar): se ejecuta al escribir algún carácter con teclado.
Eventos
Eventos de teclado: – En este ejemplo, la presión de la tecla ESC hace que la ventana se cierre. – Lo primero es generar un wx.Panel porque los wx.Frame no pueden capturar eventos de teclado, solo sus componentes. – Luego en el evento OnKeyDown del panel asignamos la función CerrarVentana:
Eventos
Eventos de teclado: – wx.EVT_KEY_DOWN se ejecuta cuando cualquier tecla esta presionada, pero ¿cómo sabemos qué tecla se presionó? ¿Cómo identificar ESC? – Esto lo hacemos con el método GetKeyCode() de event que retorna un número entero identificando el código ASCII de la tecla presionada (¿se acuerdan de la función ord()?) – Para identificar la tecla ESC podemos preguntar por el código 27 o, mejor aún, por la constante de wxPython que identifica la tecla ESC: wx.WXK_ESCAPE:
Eventos
Códigos de teclas: – wx.WXK_RIGHT, wx.WXK_LEFT, wx.WXK_UP, wx.WXK_DOWN: teclas de cursor. – wx.WXK_F1 hasta wx.WXK_F12: teclas de funciones. – wx.WXK_NUMPAD0 hasta wx.WXK_NUMPAD9: teclas del teclado numérico – wx.WXK_BACK: tecla ← (o backspace) – wx.WXK_TAB: tabulación – wx.WXK_RETURN: tecla Enter – wx.WXK_SPACE: barra espaciadora – wx.WXK_DELETE: tecla suprimir (o delete) – wx.WXK_SHIFT, wx.WXK_ALT, wx.WXK_CONTROL (para Mac representa la tecla Command) – wx.WXK_RAW_CONTROL: en Mac, si la tecla Ctrl está mapeada con la tecla Command. – wx.WXK_NONE: ninguna tecla válida se ha presionado. Widgets
Un “widget” puede ser traducido como un componente visual o herramienta para el uso en aplicaciones.
En wxPython un widget es una herramienta sobre la cual se puede interactuar y generar eventos.
Comencemos con wx.Button que representa un botón: boton = wx.Button(parent,id,mensaje) parent es el “padre” que contiene al componente botón.
mensaje es el valor que mostrará el botón.
id, entero que sirve para identificar unívocamente el botón entre todos los componentes. Si se ingresa -1, wxPython se encargará de asignarle un id. boton = wx.Button(self,-1,”Cerrar Ventana”)
Widgets
Evento de botón: – El evento que se dispara cuando se hace click en un componente wx.Button es wx.EVT_BUTTON, que se puede programar eligiendo el componente y en Events seleccionando OnButtonClick
– El valor del mensaje del botón puede ser cambiado con el método SetLabel boton.SetLabel(“Otro mensaje”)
Widgets
wx.TextCtrl – Este control equivale a un componente de edición de texto básico. textCtrl = wx.TextCtrl(parent,id,valor) – valor es la cadena inicial que muestra el componente. Por defecto es una cadena vacía. – Los métodos más usados de TextCtrl son: • textCtrl.GetValue(): retorna una cadena con el valor actual. • textCtrl.SetValue(nuevo_texto): ingresa un nuevo valor al componente.
Widgets
wx.TextCtrl – Veamos el siguiente ejemplo: un botón que, al presionarlo, muestra la suma del texto en 2 componentes TextCtrl:
Ingreso de la función que manejará el evento Selección del Componente que dispara el evento Widgets
wx.TextCtrl – En la función CalcularSuma tenemos:
Widgets
wx.TextCtrl – En la función CalcularSuma tenemos:
GetValue() retorna un str, por lo tanto el operador + no suma, concatena
Widgets
wx.TextCtrl – En la función CalcularSuma tenemos:
Notar que SetLabel recibe Como parámetro un str.
Widgets
wx.TextCtrl – Qué pasa si no ingresamos alguno de los valores en el TextCtrl?
– Tenemos error ya que int() espera algún valor. – Para solucionarlo podemos inicializar con 0 el TextCtrl o modificar el valor en la función CalcularSuma. Widgets
wx.ComboBox – Un “combo box” es una lista desplegable de opciones de las cuales solo se puede seleccionar una. combo = wx.ComboBox(parent,id,choices=[ ]) – choices es una lista de cadenas con los ítems a mostrar. combo = wx.ComboBox(self,-1,choices=[“rock”,”jazz”,”R&B”])
Widgets
wx.ComboBox – El evento principal sobre un ComboBox es al seleccionar una opción. Esto puede verse con el evento wx.EVT_COMBOBOX, seleccionando OnCombobox en wxFormBuilder:
– El método event.GetString() retorna la cadena seleccionada cuando se cambia de valor el wx.ComboBox: def CambiarTexto(self,event): tex = event.GetString() self.texto.SetLabel("Opción elegida: %s"%tex) Widgets
wx.ComboBox – A veces es necesario conocer cuál es la posición del item elegido dentro de la lista, esto lo podemos observar con event.GetSelection() que retorna un entero donde 0 es el primer elemento de la lista. def CambiarTexto(self,event): tex = event.GetString() ind = event.GetSelection() self.texto.SetLabel("Opción elegida: %s en la posición %d"%(tex,ind))
Widgets
Otros métodos de wx.ComboBox – combo.GetCount() retorna la cantidad de elementos del ComboBox. – combo.GetString(indice): retorna la cadena correspondiente a la opción en la posición indice. – combo.SetString(indice,texto): modifica el texto de la opción en la posición indice. Si indice no es válido, será un error. Estilo de wx.ComboBox: – Se puede darle un estilo al ComboBox mediante el parámetro style. Los estilos pueden combinarse usando el carácter | – wx.CB_READONLY: configura al ComboBox en “solo lectura” es decir no puede ser editable. – wx.CB_SORT: ordena las opciones dentro del ComboBox alfabéticamente. combo = wx.ComboBox(self,-1,choices=[“Uno”,”Dos,”Tres”], style=wx.CB_SORT | wx.CB_READONLY)
Widgets
Volvamos al ejemplo de la suma, pero ahora elegiremos qué tipo de operación se realizará entres 2 números, eligiendo la misma de un wx.ComboBox
Agregamos las opciones haciendo click en “choices”
Widgets
Calcular ahora necesita determinar qué operación se eligió:
Convertimos a float para poder tomar mejores cálculos
Usamos GetSelection() Para obtener el indice de la opción elegida
Recordemos que en caso de resto y división el divisor debe ser distinto de 0.
Widgets
Calcular ahora necesita determinar qué operación se eligió:
Widgets
Notemos que puede haber algunos errores cuando ejecutamos el programa: – Si el divisor es cero, cuando hacemos Cociente o Resto – Si no se selecciona una operación (GetSelection() es -1) – Si los números ingresados son texto vacío.
Para mitigar esto, en el código se agregaron sentencias return, pero deberíamos mostrar al usuario que el ingreso fue equivocado. Antes se usaba print() pero ahora necesitamos algún mensaje visual. wx.MessageBox(mensaje,titulo,estilo)
Al ejecutar MessageBox, se abre una ventana con un botón y un mensaje que nosotros podemos codificar, por ejemplo: wx.MessageBox(“Debe seleccionar operación”,”Error!”,wx.ICON_ERROR)
Widgets
Uso de wx.MessageBox para mostrar errores:
Widgets
Uso de wx.MessageBox para mostrar errores:
Widgets
wx.CheckBox – Los CheckBox son componentes que nos permiten activar o desactivar opciones con un “check”. – Consiste de una caja para chequear y una etiqueta para determinar qué opción se activa o desactiva. – El evento que se dispara cuando se hace click en la caja es OnCheckBox, que genera un wx.EVT_CHECKBOX – El ejemplo que veremos permite habilitar la edición de un campo de texto o deshabilitarla mediante un CheckBox:
Widgets
wx.CheckBox – El método que nos deja ver cuál es el estado del CheckBox es GetValue(), que retornará True si está “tildado” o False si no lo está. – Otra alternativa es usar el método IsChecked(), que es análogo al anterior. – El método que usaremos en el TextCtrl es Enable(habilitar), donde habilitar es un bool: si es True habilita la edición. – Notemos que al iniciar la app ponemos CheckBox está “tildado”, por lo tanto TextCtrl tendrá la edición habilitada inicialmente.
Widgets
wx.RadioButton – Tiene un comportamiento similar a wx.CheckBox, pero la diferencia es que permite elegir una de varias opciones. – Si una opción es elegida, las otras automáticamente dejan de estar seleccionadas, así que también se puede decir que es parecido a wx.ComboBox. – En realidad para usar el RadioButton, los mismos deben ser agrupados en otro componente llamado wx.RadioBox, que contendrá todas las opciones mutuamente excluyentes entre si. – Luego yendo a choices del RadioBox podemos ingresar las opciones:
Widgets
wx.RadioButton – Volvamos atrás al ejemplo de la calculadora, pero en lugar de opciones en un CheckBox, usaremos las mismas opciones en un RadioBox – Al igual que con CheckBox, el índice de la opción en la lista se obtiene con GetSelection()
Widgets
wx.Slider – Es un control que permite mover un selector entre un valor mínimo y otro máximo mediante el mouse.
– El valor mínimo se configura en la opción minValue, el máximo en maxValue y el valor inicial en value. – Con “style” se puede elegir el estilo del Slider: wx.SL_LABELS muestra los valores máximo, mínimo y actual. wx.SL_VERTICAL cambia la dirección a vertical. wx.SL_INVERSE invierte el orden de los valores. Widgets
wx.Slider – En este ejemplo se mueve el Slider y mediante el evento OnScroll se muestra en un wx.StaticText el valor actual del Slider.
Widgets
wx.SpinCtrl – Un SpinCtrl es similar al Slider pero los valores se muestran en un TextCtrl y se incrementan y/o decrementan con un botones que ya vienen en el componente. – Como en Slider también se deben configurar los valores máximo, mínimo e inicial. – El evento que se dispara cuando se oprimen los botones es OnSpinCtrl
wx.Timer
Este componente permite tener un contador de tiempo en nuestro código.
wx.Timer ejecuta eventos en un tiempo determinado para que otros componentes de la aplicación puedan realizar tareas con una frecuencia o a un tiempo necesarios. timer = wx.Timer(parent,id)
Un Timer se inicia (y reinicia) con el método Start(ms), donde ms es la cantidad de milisegundos (1 s = 1000 ms) y se detiene con Stop().
En wxFormBuilder podemos encontrar el wx.Timer en la pestaña “Additional” con la forma de un reloj:
wx.Timer Timer posee un solo evento, wx.EVT_TIMER cuyo manejador es OnTimer en wxFormBuilder.
La función (en este caso ActualizarReloj) se ejecutará automáticamente cada ms milisegundos.
El siguiente código lanza un Timer cuando se presiona ToggleButton y actualiza los segundos en el StaticText (period = 1000).
Un ToggleButton es un botón que tiene dos estados, presionado (True) y no presionado (False), y GetValue() retorna esos valores cuando se activa el evento OnToggleButton.
wx.Timer Timer posee un solo evento, wx.EVT_TIMER cuyo manejador es OnTimer en wxFormBuilder.
La función (en este caso ActualizarReloj) se ejecutará automáticamente cada ms milisegundos.
El siguiente código lanza un Timer cuando se presiona ToggleButton y actualiza los segundos en el StaticText (period = 1000).
Un ToggleButton es un botón que tiene dos estados, presionado (True) y no presionado (False), y GetValue() retorna esos valores cuando se activa el evento OnToggleButton.
El módulo wx.grid
Un Grid es una tabla similar a una matriz gráfica, como las usadas en planillas de cálculo.
En wxFormBuilder puede encontrarse en la pestaña “Data”
Veamos las partes de un wxGrid:
col_label grid_lines Columnas (cols)
Filas (rows)
row_label El módulo wx.grid Realizaremos un ejemplo en el cual podremos mostrar en una grilla los valores de un archivo del mismo formato que la base de datos de materiales para reverberación
Es decir, las columnas serán el nombre del material y 6 frecuencias. En total 7 columnas.
Para abrir el archivo usaremos otra herramienta visual, el wx.FilePickerCtrl, del que ya hablaremos Como primer paso, pondremos cols=7 y rows=0 ya que aún no cargamos ningún valor.
Luego pondremos los nombres de cada columna, iremos a col_label_values y haremos clic en […]
En column_sizes podemos poner una lista de números separados por coma con el tamaño de las columnas en orden. Para este caso usaremos 120,60,60,60,60,60,60
Como no queremos tener ninguna etiqueta de fila, usaremos row_label_size = 0 El módulo wx.grid Esta es la maqueta inicial
Ahora veremos qué sucede al elegir el archivo mediante el wx.FilePickerCtrl.
Elegimos en wxFormBuilder ese componente y vamos a evento, donde el evento cuando se elige un archivo es OnFileChanged.
Llamaremos al manejador de ese evento AbrirArchivo
El módulo wx.grid
GetPath() del wx.FilePickerCtrl retorna el archivo elegido.
Abrimos el archivo como siempre y recorremos por renglón.
Se divide (split) por “;” lo cual nos da las columnas de cada fila.
AppendRows() de la grilla agrega una fila (row) vacía a la misma
GetNumberRows() retorna cuantas filas tiene la grilla en ese momento.
SetCellValue(fila,columna,valor) coloca un valor en la fila y columna dada. El módulo wx.grid
Notemos qué pasa si queremos abrir otro archivo:
Se agrega al final del archivo anterior. Para solucionar, antes de abrir el nuevo archivo debemos borrar de la grilla los datos del anterior.
Usaremos el método DeleteRows(desde,filas), donde desde es la primera fila desde la que se borrará y filas es la cantidad a borrar desde esa fila. El módulo wx.grid limpiarGrilla() elimina los valores de la grilla desde 0 a filas
Antes de cargar nada se debe “limpiar” la grilla
El módulo wx.grid Agregaremos botones para eliminar y agregar coeficientes. Cuando hagamos click en el botón “Eliminar Material” debemos borrar la fila seleccionada. Antes debemos saber cuál fila es, debemos capturar el momento en el que se cambia de fila con el evento OnGridSelectCell, al cual le asignaremos la función CambiarFila El evento ejecutado es un wx.grid.GridEvent que posee el método GetRow(), del cual se obtiene el índice de la fila seleccionada cada vez que cambiamos de celda. Usaremos la variable filaSeleccionada para mantener el último valor de fila elegida.
El módulo wx.grid Cuando eliminamos algo en alguna aplicación siempre es bueno preguntar antes. Usemos el wx.MessageDialog para tener una confirmación de la eliminación del material
El módulo wx.grid Habremos notado a esta altura que borrar o agregar un nuevo material no se ve reflejado en el archivo de texto. Esto se debe a que aún no guardamos la información modificada. Pongamos un nuevo botón, llamado guardar para guardar los cambios de la grilla en el archivo con la función GuardarDatos.
El módulo wx.grid El último paso podría ser hacer más “amigable” nuestra app, usemos botones gráficos: wx.BitmapButton. Estos botones nos permiten usar una imagen en cualquier formato para identificar la función del botón en lugar de una etiqueta
Integración wx y pyo Con los componentes y eventos de wxPython podemos realizar cualquier operación sobre los módulos de Python. pyo no es la excepción, y además cuenta con componentes visuales propios que iremos nombrando. Veamos un ejemplo, un selector de señales y un visualizador de espectro y osciloscopio.
Integración wx y pyo El osciloscopio es el componente pyo.PyoGuiScope. El espectrómetro es pyo.PyoGuiSpectrum Ninguno de esos controles están en wxFormBuilder, así que deberemos ingresarlos como CustomControl en la pestaña Additional:
Y luego completar los campos de las propiedades del CustomControl de la siguiente manera (en este ejemplo para el PyoGuiSpectrum): – name: es el nombre que tendrá el control en nuestro programa. – class: es el nombre de la clase. – construction: es como se define cuando se inicializa el objeto.
Integración wx y pyo Una vez creada la maqueta es hora de editar el archivo de python en Thonny. Lo primero es crear el servidor de Pyo y los objetos Scope() y Spectrum(), le pasamos como input una señal senoidal, para inicializarlos correctamente. import pyo pyoServer = pyo.Server().boot() pyoSc = pyo.Scope(pyo.Sine()) pyoSp = pyo.Spectrum(pyo.Sine())
Ahora agregaremos un evento llamado OnPlay al botón botonPlay que inicie la reproducción de la señal seleccionada en el wx.ChoiceBox que hemos llamado tipoSenyal.
Notemos que para que el PyoGuiScope() funcione necesita que se lo asocie con un objeto tipo Scope(). Para eso usaremos el método setAnalyzer() de PyoGuiScope()
Integración wx y pyo
Obtenemos la señal elegida y asignamos a senyal
Si la señal no es de las elegidas, mostramos error
Si Server() no fue iniciado, lo hacemos
Cambiamos la señal de entrada de los analizadores a la señal elegida
El objeto visual ahora está relacionado con su correspondiente objeto de análisis Integración wx y pyo Finalmente, programamos otros 2 eventos. En el botón Stop (botonStop) agregamos el evento OnStop(), que detendrá la reproducción de la señal. Y en el wx.Frame usamos el evento EVT_CLOSE para programar la función OnClose(). Notemos que en esa función detenemos el servidor de pyo y luego destruimos la ventana con el método Destroy() que es similar a Close() pero limpia mejor la memoria.
Integración wx y pyo
Además de PyoGuiSpectrum y PyoGuiScope, pyo también tiene otros componentes gráficos: PyoGuiSndView que permite conectar con un componente SndTable para visualizar contenido de un archivo de audio y seleccionar por partes
Integración wx y pyo
Los componentes PyoGuiGrapher y PyoGuiMultiSliders pueden ser vistos en el siguiente ejemplo: https://github.com/belangeo/pyo/blob/master/examples/wxgui/ 01_gui_widgets_example.py
Bibliografía
Bodnar, J (2018, Mayo) wxPython Tutorial. ZetCode. Consultado Octubre 2018 desde http://zetcode.com/wxpython/ Dunn, R (2017, Agosto) wxPythonHistory. WxPython. Consultado Octubre 2018 desde https://wiki.wxpython.org/WxPythonHistory
The wxPython Team (2018, Junio) wxPython Docs. WxPython API Documentation. Consultado Octubre 2018 desde https://docs.wxpython.org/index.html Belanger, O. (2018, Agosto) Additional wxPython Widgets. Consultado Octubre 2018 desde http://ajaxsoundstudio.com/pyodoc/api/classes/wxgui.h tml