Programación
Librerías wxWindows
Interfaces grá gr áficas gráficas con wxPython Arturo Fernández Montoro
Conoceremos las posibilidades que ofrece este módulo de Python para escribir aplicaciones con una GUI multiplataforma. Introducción Actualmente cada vez es más importante realizar aplicaciones que puedan ejecutarse en varias plataformas. Es por ello que en los últimos años hemos asistido a la proliferación de lenguajes y herramientas de desarrollo que nos faciliten la tarea de construir software multiplataforma. Esta fue una de las características que impulsaron, por ejemplo, la rápida adopción del lenguaje Java por parte de muchos desarrolladores. Otro ejemplo son los lengua jes de scripting orientados a objetos como Python o Ruby. Ruby. Para conseguir que s ólo desa-
Figura 1. wxPython listo para usar.
MUNDO
Linux
50
rrollemos un programa una vez y podamos ejecutarlo en distintas plataformas, necesitamos un intérprete específico (o una máquina virtual) para cada una de ellas. Uno de los principales problemas con los que nos encontramos cuando pensamos en desarrollar software para distintas plataformas es la interfaz gráfica. Cada sistema operativo utiliza o puede utilizar un sistema gr áfico, entendiendo este como un conjunto de componentes de interfaces de usuario, distinto. Los lenguajes de programaci ón nos ofrecen librerías para trabajar con la interfaz y el sistema gráfico de una plataforma concreta. Por ejemplo, podemos escribir un programa en
C++ utilizando las librerías MFC que seguro no correrá en Linux. Habitualmente, para sistemas UNIX se utilizaban las librerías de Motif. Lenguajes como Java solventan este problema incluyendo librer ías nativas como AWT (Abstract Windowing Toolkit ) que implementan un sistema para construir componentes gráficos independiente del sistema operativo. Sin embargo, sólo podemos utilizar estas librerías cuando programamos en Java. Observando este hecho parece interesante poder utilizar una sola librería gr áfica en cualquier lenguaje y que sea independiente del sistema operativo. Más que una sola librer ía, lo ideal es tener una API común de programación, ya que los binarios de esta librería serán diferentes para cada sistema operativo. Pues bien, eso es precisamente wxWindows.
wxWindows Se trata de un conjunto de librer ías que permiten constuir componentes de interfaz gráfica de usuario con independenc independencia ia del sistema operativo y de su sistema gr áfico (GTK+, Windows, Motif y Mac). Originariamente se desarrolló sólo para el lenguaje C++, pero actualmente existen también versiones para Python y Perl. La gran ventaja es que aunque cambiemos de lenguaje no es necesario aprender una nueva API ya que esta es común para todos los lenguajes. En otras palabras, no se trata de un traductor de una GUI a otra, por ejemplo, no toma una aplicación desarrollada para Motif y la traduce a una Windows. Podemos considerar a wxWindows como un framework en el sentido de que nos proporciona un conjunto de componentes y rutinas comunes en la programaci ón con componentes GUI, liberándonos del esfuerzo de escribir cada uno de ellos en cada programa. Su capacidad para manejar sistemas de ficheros virtuales, generar cuadros de di álogo, ventanas, botones y otros controles, adem ás del soporte para OpenGL, la convierten en una buena herramienta para construir modernas aplicaciones dónde la interfaz gr áfica de usuario es de gran importancia.
Programación
Interfaces gráficas con wxPython
wxWindows comenzó su andadura en 1992 en el Artificial Intelligence Applications Institute de la Universidad de Edinburgo, y surgió de la idea de Julian Smart cuando se planteó la necesidad de escribir una aplicación que debía funcionar tanto en Windows como en terminales con X-Window. Dado que las herramientas comerciales no estaban a su alcance, decidi ó escribir su propia librería que, con el paso del tiempo y con la ayuda de otras personas, terminó convirti éndose en un proyecto de Open Source y en lo que hoy es wxWindows (la “w” viene de Windows y la “x” de X-Window). Actualme se rige bajo la licencia denominada wxWindows license que básicamente es la licencia L-GPL con la salvedad de que pueden licenciarse otros productos propietarios que tomen sólo los binarios de esta librer ía. Con ello los autores pretenden por un lado que sea software Open Source y por otro satisfacer las necesidades de aquellos que pretenden costruir software propietario incluyendo wxWindows como librer ía base para la GUI. La licencia en cuestión ha sido aprobada por la Open Source Initiative, que se encarga de gestionar las distintas licencias que existen en el mundo Open Source.
Listado 1 1 2 3 4 5
La aplicación “Hello World!”
from wxPython.wx import * app = wxPySimpleApp() frame = wxFrame(None, -1, “Hello World!”) frame.Show(1) app.MainLoop()
wxPython El lenguaje Python gana cada día más adeptos debido a sus principales características: rapidez de ejecución, multiplataforma y las ventajas propias de un lenguaje de scripting orientado a objetos. Cuando deseamos escribir una GUI utilizando este lenguaje disponemos de diversas opciones: pyQT pyQT,, pyGTK, Tkinter o wxPython. Esta última es, básicamente, la versión de wxWindows para Python. Se trata de un completo toolkit para construir elementos de interfaces gr áficas de usuario. Utilizar esta librería nos permitirá escribir aplicaciones multiplataforma con una completa GUI. Evidentemente, si ya conocemos wxWindows tendremos mucho camino avanzado debido a que la API es pr ácticamente igual, salvando las distintas sintácticas que existen entre Python y C++ (recordemos que originariamente wxWindows fue desarrollada para este lenguaje). Por otro lado, wxWindows est á dotada de una gran estabilidad y se viene utilizando desde hace diez años. wxPython es un módulo de Python más que podemos incorporar en nuestros programas. Este módulo nos permite crear instancias de las clases de wxWindows e invocar a sus métodos. Todas la clases son prácticamente iguales en ambas librerías, salvando las distancias entre los lenguajes, por lo que la documentaci ón sobre wxWindows nos valdrá perfectamente para desarrollar con wxPython.
A partir de aquí asumiremos que el lector conoce el lenguaje Python o est á familiarizado con él. Como punto de partida recomendamos el artículo que sobre este lenguaje aparce en Mundo Linux 53.
Figura 2. Nuestra primera aplicación.
Instalación En primer lugar debemos tener instalado el intérprete de Python, para la realizaci ón de este artículo nosotros hemos utilizando la versión 2.1.3. Habitualmente podemos encontrarlo en todas las distribuciones de Linux. Posteriormente debemos instalar la versión de wxWindows para Linux, que en este caso utiliza las librerías de GTK+, es lo que se conoce con el nombre de wxGTK. No olvidemos que antes de instalar esta librería, nuestro sistema debe contar con el toolkit de GTK+. Si utilizamos Gnome seguro que ya las tenemos instaladas. GTK+ es utilizado utiliz ado por muchos programas, seguramente nuestro sistema ya cuenta con estas librerías. Por último, sólo nos queda instalar wxPython. Todo este software se distribuye distri buye en formato tarball y para instalarlo se sigue el proceso habitual para el software empaquetado en este formato, es decir, debemos descromprimir y compilar. Si tenemos la suerte de utilizar Debian el proceso se simplifica: basta con instalar el paquete libwxgtk2.2-python (en caso de Python 2), que además nos configurará el módulo y nos dejará todo listo para empezar a trabajar. MUNDO
51
Linux
Programación
Listado 2
Menú, barra de estado y métodos callback
1 ID_MENU_EXIT = 101 2 ID_MENU_ABOUT = 102 3 self.CreateStatusBar() 4 self.SetStatusText(“Welcome!”) 5 6 7 8 9
# MenuBar menuBar = wxMenuBar() menu = wxMenu() menu.Append(ID_MENU_ABOUT, “A&bout”, “About...”) menu.Append(ID_MENU_EXIT, “E&xit”, “Close application”)
10 EVT_MENU(self, ID_MENU_EXIT, self.OnbtnClose) 11 EVT_MENU(self, ID_MENU_ABOUT, self.OnbtnAbout) 12 menuBar.Append(menu, “&File”) 13 self.SetMenuBar(menuBar) 14 def OnbtnClose(self, evt): 15 self.Close()
El módulo wxPython debe quedar localizado en el directorio site-packages de Python. En el caso de Debian se trata del directorio /usr/lib/python2.1/site-packages/wxPython/. Dado que el módulo se encuentra en este directorio, no tendremos ningún problema a la hora de realizar la “importación” del mismo desde nuestros programas. La última versión liberada de wxPython es la 2.4.1.2, la cual necesita la versión 2.4 de wxGTK. Sin embargo, para la realizaci ón de este artículo nosotros hemos utilizado la versión 2.2, dado que se trata de la versión estable incluida en Debian Woody. Para comprobar que tenemos correctamente instalado wxPython, podemos abrir una shell, ejecutar python y teclear from wxPython.wx import * . Si todo va bien, la l ínea de comandos del int érprete nos mostrará otra línea. Ver figura 1.
Hello World! Figura 3. Nuestra aplicaci ó ón. n .
MUNDO
Linux
52
En lugar de describir o resumir la API que presenta wxPython, pensamos que es m ás interesante comenzar viendo un caso pr áctico que nos ayude
a empezar a programar de forma r ápida. En cualquier caso podemos encontrar en la web (ver referencias) amplia información sobre todo el conjunto de widgets que nos nos ofrece ofrece wxWindows y wxPython, que seguro nos servir á para ir profundizando y sacar amplio partido para el desarrollo de interfaces gráficas de usuario en Python. Como introducción hemos elegido todo un clásico: Hello World!. Nuestra primera aplicaci ón será una ventana que presenta el tradicional mensaje. El código está en el listado 1. Lo primero que debemos tener en cuenta es que wxPython está dirigido a eventos, es decir, cada control utiliza una rutina de callback que responde a cierto evento. La primera l ínea de código indica el módulo que debemos importar y qué clases. En este caso hemos utilizado el asterisco (*) para importar todas las clases, aunque podíamos haber importado sólo aquellas que vamos a utilizar. Seguidamente indicamos que esta va a ser una aplicación wxPython (línea 2), esto implica que está dirigida a eventos y que debe establecerse un ciclo inicial (esto se indica en la línea 5). El único control que tiene nuestra aplicación es un wxFrame . Debemos pensar en frames y no en ventanas, ya que un frame es un contenedor de controles. La l ínea 3 presenta la creación de nuestro wxFrame , los parámetros que recibe este constructor son el “padre” del wxFrame , su ID y el mensaje de texto que aparece como título. En este caso hemos elegido parámetros por defecto para indicar que este wxFrame no tiene “padre”. Solo nos resta mostrar el wxFrame en pantalla, para ello utilizamos el método “Show”. El resultado puede apreciarse en la figura 2.
Caso práctico Obviamente, una aplicación con una GUI tiene más que un simple ventana. En este apartado mostraremos, a través de un ejemplo más completo, los principales controles y métodos que podemos utilizar con wxPython para conseguir una GUI consistente. Como caso práctico vamos a realizar una aplicación que genere un fichero a partir de unos valores que introduce el usuario. Este fichero resultado nos servirá para configurar un acceso JDBC para el servidor de aplicaciones J2EE JBoss. La motivación del desarrollo de esta aplicación viene dada por el hecho de que para realizar esta configuración, JBoss no facilita ninguna herramienta automática. Asimismo, podremos comprobar la ventaja de realizar esta aplicación en Python en lugar de utilizar Java, Ja va, que sería lo normal, dado que estamos hablando de JBoss. Se trata de utilizar dos lenguajes de programación con un objetivo com ún. Normalmente, se tiende a utilizar el mismo lenguaje de programación cuando intentamos resolver un problema
Programación
Interfaces gráficas con wxPython
Listado 3
Etiquetas, cajas de texto y combo
1 lblUser = wxStaticText(self.panel, -1, “DB user: “,wxPoint(20,110)) 2 self.txtUser = wxTextCtrl(self.panel, -1, “”,wxPoint(140,110)) 3 # criteria combo 4 arrCriteria = [‘ByContainerAndApplication’, ‘ByContainer’, ‘ByApplication’, ‘ByNothing’] 5 self.cbCriteria = wxComboBox(se wxComboBox(self.panel, lf.panel, ID_CBCRITERIA, arrCriteria[1 arrCriteria[1], ], wxPoint(140, 230), wxSize(440, -1), arrCriteria, wxCB_DROPDOWN) 6 btnClose = wxButton(self.panel, ID_BTNCLOSE, “Close”, wxPoint(220, 270), wxSize(80, 25))
Listado 4
Comboboxes
1 ID_CBURL = 30 2 ID_CBDRIVER = 40 3 urlList = [‘jdbc:oracle:thin:@[servername]:[port]:[database name]’, ‘jdbc:oracle:thin:@[servername]:[port]:[database name]’, ‘jdbc:mysql://[servername]:[port]/[database name]’, ‘jdbc:postgresql://[servername]:[port]/[database name]’, ‘jdbc:db2:[database name]’, ‘jdbc:informixsqli://[host].[domain]:[port]/[databasename]:INFORMIXSERVER=[server]’, ‘jdbc:sybase:Tds:[host].[domain]:[port]/[databasename]?JCONNECT_VERSION=6’ ] 4 driverList = [‘oracle.jdbc.driver.OracleDriver’ ‘oracle.jdbc.xa.client.OracleXADataSource’, ‘org.gjt.mm.mysql.Driver’, ‘org.postgresql.Driver’, ‘COM.ibm.db2.jdbc.app.DB2Driver’, ‘com.informix.jdbc.IfxDriver’, ‘com.sybase.jdbc2.jdbc.SybDataSource’ ] 5 self.cbDriver = wxComboBox(self.panel, ID_CBDRIVER, driverList[0], wxPoint(140, 70), wxSize(440, -1), driverList, wxCB_DROPDOWN) 6 self.cbUrl = wxComboBox(self.panel, ID_CBURL, urlList[0], wxPoint(140, 30), wxSize(440, wxSize(44 0, -1), urlList, wxCB_DROPDOWN) wxCB_DROPDOWN) 7 EVT_COMBOBOX(self, ID_CBURL, self.OnChangecbUrl) 8 def OnChangecbUrl(self, evt): self.cbDriver.SetSelection(self.cbUrl.GetSelection()) 9 EVT_COMBOBOX(self, ID_CBDRIVER, self.OnChangecbDriver) 10 def OnChangecbDriver(self, evt): self.cbUrl.SetSelection(self.cbDriver.GetSelection())
dado. Sin embargo, es interesante evaluar qu é lenguaje es más apropiado para qué tarea e intentar que ambos puedan encajar entre sí. Es por ello que decidimos realizar la aplicaci ón mencionada en Python, aunque finalmente nos servirá para configurar JBoss y correr aplicaciones escritas en Java. Además, esta aplicación nos servirá para entrar en contacto con wxPython. Proponemos al lector y programador de Java que escriba la misma aplicación en este lenguaje y lo compare con el que aqu í presentamos. En concreto, la aplicaci ón pedirá al usuario una serie de valores para generar el fichero XML que sirve para configurar un pool de conexiones a base de datos y su DataSource asociado. Si estamos familiarizados con el desarollo J2EE estos valores nos serán populares. Podemos generar un fichero para los siguientes gestores de bases de datos: PostgreSQL, MySQL, Oracle, DB2, Informix y Sybase. Empecemos con el código. Creamos una clase llamada MainFrame que herede de la clase wxFrame. Esta clase será el contenedor de controles y tendrá métodos callback para responder a la acción del usuario sobre dichos controles. El constructor de esta clase será el encargado de crear cada control.
El listado 2 nos muestra la creación de una barra de menú, el acceso a la barra de status y la conexión de los métodos callback. Cuando se produce un evento, wxPython captura el mensa je y realiza realiza una llamada llamada a un método de nuestra aplicación. Para ello debemos hacer un registro que conecte la sucesión de un evento a un método concreto. Pues bien, estos m étodos comienzan por EVT_. Veamos la línea 10. A través de la
Figura 4. Salvando un fichero.
MUNDO
53
Linux
Programación
Listado 5
Cuadros de diálogo modales.
1 dlg = wxFileDialog(self, “Save a text file”,””, “”, “XML Files(*.xml)|*.xml|All files(*.*)|*.*|”, wxSAVE); 2 if dlg.ShowModal() == wxID_OK: 3 filename = dlg.GetFilename() 4 dirname = dlg.GetDirectory() 5 try: 6 self.CreateFileJBoss(path.join(dirname, fi f ilename)) 7 dlgMsg = wx wxMessageDialog(self.panel, “F “File ge generated su sucessfully!”, style = wxOK); 8 dlgMsg.ShowModal() 9 except: 10 dlgMsg = wxMessageDialog(self.panel, “Error! File not generated”, style = wxICON_ERROR); 11 dlgMsg.ShowModal()
Figura 5. Cuadro de di á álogo. l ogo.
MUNDO
Linux
54
llamada a EVT_MENU() estamos indicando que cuando se produzca un evento relacionado con la barra de menú y que afecte a la entrada indentificada por ID_MENU_EXIT, se debe llamar al método OnbtnClose de nuestra aplicación. Este identificador está asociado a la entrada Close application del menú (línea 9). El método onbtnClose() se encarga de cerrar el wxFrame, observemos que necesita el parámetro evt el cual le indica que debe responder a un evento. La creación del menú y de la barra de status se hace en las líneas 6 y 7 respectivamente. Posteriormente, se añaden las entradas al menú, que en nuestro caso son sólo dos: About y Exit. Los ID de cada control son números que no atienden a un criterio concreto, simplemente deben ser distintos para cada control y nos sirven para identificarlos de forma unívoca en las llamadas a los m étodos. Pasamos a ver los principales controles que utiliza nuestra aplicación ejemplo: q Botones q Etiquetas de texto q Elementos desplegables (comboboxes) q Cajas de texto q Cuadros de diálogo modales Comezamos observando la línea 1 del listado 3, en ella se crea una etiqueta de texto. En este caso concreto se trata de la etiqueta que indica el nombre de usuario. El último parámetro indica, a través de una función, la posición relativa de la etiqueta respecto al frame. La siguiente línea sirve para crear una caja de texto donde se
podrá introducir el nombre del usuario. El tercer argumento de la función es la cadena vacía porque inicialmente no queremos que se muestre ningún valor en la caja de texto. Las líneas 5 y 6 crean un combo donde se podr á seleccionar el criterio de manejo del pool de conexiones por parte de Jboss. El tercer argumento de la funci ón wxComboBox es la lista de los criterios seleccionables por el usuario. En nuestro caso utilizamos la lista arrCriteria. La última línea del listado indica la creación de un botón que sirve para cerrar la aplicación. Con el objetivo de facilitar el trabajo al usuario, nuestra aplicación seleccionará de forma automática el driver y su URL apropiada. Es decir, una vez seleccionado el valor en uno de los combos, se dispara un evento que marca el valor correspondiente en el otro combo. Con esto evitamos que el usuario pueda seleccionar un driver que no corresponde con su URL y viceversa. El listado 4 nos muestra el código en cuestión. Una vez creados los dos combos, registramos los métodos que responden al evento de cambio del valor seleccionado en dichos combos. Debemos registrar un método para cada combobox. Las líneas 8 y 9 muestran el método que se va a ejecutar cuando cambie el texto del combo de las URL. En primer lugar se obtiene el índice del elemento seleccionado de la lista de los valores de dicho combo (método GetSelection()), y posteriormente se selecciona el driver correspondiente en el otro combo (método SetSelection()). Las listas de ambos combos tienen el mismo n úmero de elementos, por ello basta con indicar su índice para seleccionar el valor correspondiente. Porr último, veremos los cuadros de diálogo Po modales que utiliza nuestra aplicación. El cuadro más sencillo que podemos crear con wxPython es aquel que tiene un botón, un mensaje y un icono. Recordemos que la aplicaci ón que estamos analizando se encarga de generar un fichero XML. Pues bien, si este se ha generado correctamente se informa al usuario de ello a trav és de uno de estos cuadros modales. Veámoslo en las líneas 7 y 8 del listado 5. El segundo argumento de la función wxMessageDialog() es el mensaje mostrado al usuario y el tercer argumento es el icono.
Interfaces gráficas con wxPython
El método ShowModal() muestra el cuadro en pantalla. Habremos observado que estas l íneas se encuentran dentro de un bloque try-except, se trata del manejo de excepciones que nos ofrece Python. En caso de que ocurra una excepción al generar el fichero, se ejecutar á el código del bloque except, que en este caso muestra otro cuadro de diálogo indicándole al usuario que ha ocurrido un error. En las dos primeras líneas del listado 5 podemos ver la creaci ón de otro cuadro modal distinto. Se trata del t ípico cuadro Save as... que permite al usuario guardar un fichero en un directorio del sistema de ficheros. La función se llama wxFileDialog y recibe como par ámetros el título del cuadro de diálogo, el tipo de ficheros que debe mostrar y una constante que indica el tipo de cuadro, en este caso wxSAVE. Observemos cómo en la línea 2 preguntamos si el método ShowModal() es igual a wxID_OK, en cuyo caso pasaremos a la creaci ón del fichero. Esta condición se dará cuando el usuario seleccione el nombre del fichero a salvar y pulse el botón OK. La aplicaci ón estará esperando a que se produzca ese evento.
Conclusiones Esperamos que este artículo le haya servido al lector de aproximación al mundo de wxPython. A través de un ejemplo concreto, hemos pretendido realizar un vistazo a las posibilidades que puede ofrecernos este módulo de Python. Como hemos podido comprobar, se trata de una completa librería para escribir aplicaciones con una GUI moderna y consistente. Una de sus principales ventajas es la multiplataforma. El ahorro de líneas de programación es muy significativo. Si además le añadimos las ventajas que de por s í nos ofrece Python, encontramos en el binomino Python-wxPython una alternativa muy a tener en cuenta en el futuro de las aplicaciones. GNU/Linux es una plataforma ideal para el desarrollo de aplicaciones con wxPython. En este caso es importante el papel de las librer ías de GTK+. Uniendo todos estos elementos conseguimos un completo entorno de desarrollo en software libre. Quizá echamos en falta un completo IDE que nos permita diseñar de forma gráfica la interfaz,
así com editar código, depurarlo, ejecutarlo, etc. Pues bien, por ahora podemos contar contar con BoaConstructor, que es un IDE que pretender cubrir este hueco. De momento se encuentra en versi ón alpha, aunque podemos trabajar sin problemas con la última versión. Gracias a este IDE, escrito en Python, nuestro trabajo se simplificará y podemos considerarlo como una verdadera herramienta para el RAD (desarrollo rápido de aplicaciones). Muchos desarrolladores piensan que wxPython debe convertirse en el est ándar para desarrollar aplicaciones con interfaz gráfica de usuario en Python. De hecho, algunos consideran que se trata de la forma más rápida de migrar las aplicaciones realizadas con MFC para que puedan ejecutarse en entornos UNIX y Mac OS.
Programación
Figura 6. El site de wxPython.
igidbc.py El autor ofrece el programa jgjdbc.py, escrito en Python, bajo licencia GPL, disponible en el área de descargas de Mundo Linux , www. revistasprofesionales.com. Este programa genera un fichero XML con los datos necesarios para utilizar un pool de conexiones de BD a través de un Datasource en el servidor de aplicaciones JBoss. Lo crea para el SGBD que le indiquemos. Habitualmente hay que realizar esta tarea manualmente porque no hay una herramienta que automatice el proceso.
Referencias Proyecto wxWindows: http://www.wxwindows.org Proyecto wxPython: http://www.wxpython.org q Lenguaje Python: http://www.python.org http://www.wxpython.org/dowload /dowload q Descargas de wxPython: http://www.wxpython.org q Wiki site muy completo sobre wxPython: http://wiki.python.org q Guía de estilo para codificar en Python: http://www.python.org/peps/pep-0008.html q IDE para RAD en Python con wxPython: http://boa-constructor.sourceforge.net q Open Source Initiative: http://www.opensource.org q q
MUNDO
55
Linux