Lic.
Marcelo Fernández
[Link]@[Link] - [Link]
Publicado bajo Licencia Creative Commons - BY,
excepto las imágenes y logos tomadas de sitios de Internet
Introducción - GUIs en Python
¿Qué es una GUI? ¿Y un toolkit gráfico?
Bibliotecas: ”Bindings” o ”Wrappers”
Toolkits disponibles en Python:
Tcl/Tk: Instalada por defecto.
wxPython: wrapper de wxWidgets (antes wxWindows)
PyGTK: wrapper de GTK.
PyQT: wrapper de QT.
(Ver comparativa en [Link]
Introducción - wxPython
Biblioteca para usar wxWidgets desde Python
Hereda sus características
Robusta, años evolucionando (1992).
Pensado para ser Multiplataforma desde el inicio.
Conserva el Look and Feel del entorno y su velocidad,
ya que utiliza componentes GUI estándar de cada SO.
No sufre del problema del ”común denominador”.
Flexibilidad. Permite embeber componentes nativos.
LGPL. Aplicaciones con cualquier licencia.
Windows, Linux, Mac y más con casi
el mismo código fuente, sólo recompilando.
Introducción - wxWidgets
Introducción - wxWidgets
Introducción - wxPython
Pero wxPython amplía las ventajas:
Es Python: más fácil de aprender y adecuado que C++.
Código ”pythónico”.
+ Fácilmente extensible, AGW es un ejemplo.
Windows, Linux, Mac y más con el mismo código
fuente, sin recompilar. Sólo se ejecuta el script y listo.
Desventajas
Instalación: No está incluido en Python mismo.
Muchas capas de abstracción.
Curva de aprendizaje media/alta, lógicamente
según la aplicación que se quiera desarrollar
Introducción - wxPython
Mi
Mi Aplicación
Aplicación
Python
Python
Bindings
WxPython
WxPython
wxD
wxD wxErlang
wxErlang wxPerl
wxPerl Python/C++ wxRuby
wxRuby wxHaskell
wxHaskell Otros
Otros
Python/C++
wxWidgets
wxWidgets
C++
C++
Windows
Windows Linux,Unix/GTK
Linux,Unix/GTK Mac
Mac OSX
OSX Otras
Otras
Win32
Win32//Win64
Win64 wxGTK
wxGTK wxMac
wxMac wxX11,wxDFB,wxMotif...
wxX11,wxDFB,wxMotif...
Widgets / Plataformas
Ver más en [Link]
Ejemplo 1 – Hola Mundo
#!/usr/bin/env python
import wx
if __name__ == '__main__':
app = [Link]()
frame = [Link](None, wx.ID_ANY, "Hola Mundo")
[Link]()
[Link]()
Ejemplo 2 – Estructura Base
#!/usr/bin/env python
import wx
class TestFrame([Link]):
def __init__(self, parent, title):
[Link].__init__(self, parent, title=title, size=(200,100))
[Link] = [Link](self, style=wx.TE_MULTILINE)
if __name__ == '__main__':
app = [Link]()
frame = TestFrame(None, 'Editor de Texto')
[Link]()
[Link]()
[Link]()
Modelo de Cajas - wxPython
Todo lo visible hereda de [Link]
Existen ventanas ”contenedores”,
que sirven para contener a otras
ventanas. Ej.: [Link], [Link],
[Link].
Los contenedores permiten aislar y
gestionar las regiones que él ocupa,
para definir dónde se ubicarán y
cómo se distribuirán finalmente los widgets que
contiene.
Un contenedor puede contener uno o más
widgets y/o contenedores
Organización de Widgets
Organización Estática
Posicionamiento basado en pixels
Gestión manual de la ubicación de los componentes
Pero limitado por donde se lo mire:
Monitores y/o Resoluciones diferentes
Idiomas, SOs, ”Skins”, Tipografías diferentes
Organización Dinámica
Mucho más util en todos el resto de los casos
wxWidgets lo provee mediante los Sizers
Diferentes algoritmos de posicionamiento,
diferentes subclases de Sizer. (demo)
Ejemplo 3/1 – Sizers y Widgets
#!/usr/bin/env python
# coding: utf-8
import wx
class MainWindow([Link]):
def __init__(self, parent, title):
# Llamo al constructor del padre de MainWindow
[Link].__init__(self, parent, title=title, size=(640,480))
# Creo una StatusBar para la ventana
[Link]()
# Creo una instancia de Menú y le agrego items
filemenu= [Link]()
[Link](wx.ID_ABOUT, u'&Acerca De...', u'Info. del Programa')
[Link]()
[Link](wx.ID_EXIT, u'&Salir', u'Salir del Programa')
# Barra de menú, le agrego el menú + items y la seteo como de la ventana
menuBar = [Link]()
[Link](filemenu, u'&Archivo')
[Link](menuBar)
Ejemplo 3/2 – Sizers y Widgets
# Creo cada sizer, sus widgets y los agrego al sizer correspondiente
self.sizer_botones = [Link]([Link])
[Link] = []
for i in range(0, 6):
[Link]([Link](self, wx.ID_ANY, u'Botón &' + str(i)))
self.sizer_botones.Add([Link][i], proportion=1, flag=[Link])
[Link] = [Link]([Link])
[Link] = [Link](self, style=wx.TE_MULTILINE)
self.sizer_form = [Link](rows=2, cols=3)
for row in range(0, 2):
self.sizer_form.AddMany(
([Link](self, id=wx.ID_ANY, label=u'Columna 1'),
[Link](self), [Link](self)))
# Agrego cada Sizer al Sizer de la Ventana con su proporción y flags
[Link](self.sizer_botones, proportion=0, flag=[Link])
[Link]([Link], proportion=1, flag=[Link])
[Link](self.sizer_form, proportion=0)
[Link]([Link])
if __name__ == '__main__':
app = [Link]()
frame = MainWindow(None, u'Editor de Ejemplo')
[Link]()
[Link]()
WxPython - Eventos
wxPython está en un loop infinito, el MainLoop(),
esperando que el usuario haga algo.
Acción(Objeto) => Reacción = Evento
Bind() permite que un evento invoque una función
[Link](wx.EVT_BUTTON, [Link])
def OnClick(self, event):
print 'Click!' # event es una instancia de [Link]
Los Command Events siguen una jerarquía y se
propagan hacia los padres, los Basic Events no.
[Link]() permite que el evento la siga
Ejemplo 4/1 - Eventos
class MainWindow([Link]):
def __init__(self, parent, title):
[Link].__init__(self, parent, title=title, size=(300,150))
[Link] = [Link]([Link])
# Defino Etiquetas, Campos de Texto y sus eventos
self.sizer_form = [Link](rows=2, cols=2)
[Link] = [Link](self)
[Link] = [Link](self)
self.sizer_form.Add([Link](self, wx.ID_ANY, label=u'Nombre'))
self.sizer_form.Add([Link], flag=[Link])
self.sizer_form.Add([Link](self, wx.ID_ANY, label=u'Apellido'))
self.sizer_form.Add([Link], flag=[Link])
# Conecto los eventos Key_Up a OnTxtNombre y OnTxtApellido
[Link](wx.EVT_KEY_UP, [Link])
[Link](wx.EVT_KEY_UP, [Link])
# Defino botones Aceptar/Cancelar
self.sizer_botones = [Link]([Link])
[Link] = [Link](self, wx.ID_ANY, u'&Aceptar')
[Link] = [Link](self, wx.ID_ANY, u'&Cancelar')
self.sizer_botones.Add([Link])
self.sizer_botones.Add([Link])
# Conecto los eventos click a las funciones OnAceptar y OnCancelar
[Link](wx.EVT_BUTTON, [Link])
[Link](wx.EVT_BUTTON, [Link])
Ejemplo 4/2 - Eventos
# Agrego ambos sizers al principal
[Link](self.sizer_form, flag=[Link]|wx.ALIGN_CENTER)
[Link](self.sizer_botones, flag=wx.ALIGN_CENTER)
[Link]([Link])
def OnAceptar(self, event):
dlg = [Link](self, u'¡Click en Aceptar!', u'Ejemplo', [Link])
[Link]()
[Link]()
def OnCancelar(self, event):
[Link]()
def OnTxtNombre(self, event):
keycode = [Link]()
print u'Tecleó en Nombre: ' + str(keycode)
if keycode == wx.WXK_TAB:
print u'Tab!'
def OnTxtApellido(self, event):
keycode = [Link]()
print u'Tecleó en Apellido :' + str(keycode)
if keycode == wx.WXK_F1:
print u'F1!'
Jerarquía de Clases - wxPython
Repaso
¿Se complicó un poco, no? :-)
Básicamente, con un poco código se pueden
hacer aplicaciones de escritorio
multiplataforma, sin salir de Python...
La instalación es muy sencilla y el deploy en el
cliente también.
¡Aplicaciones con Look and Feel nativo!
Lo malo: Este método no escala.
La ”Capa Visual” y la ”Lógica de Negocio”
están mezcladas. Algo no va bien...
Diseñadores de GUI
Ventajas
Flexibilidad. Permiten separar el código de la vista.
Permiten ver inmediatamente las ventanas con
nuestros widgets y sin tener que escribir código.
¡Sí escala!
XRC: Formato estándar de wxWidgets para describir
GUIs. Es un simple archivo XML.
Desventajas
No sirve en todos los casos: Formularios dinámicos.
La carga del XML es un poco más lenta que si
armamos la interfaz con código.
Boa Constructor
Boa
Constructor
wxGlade
wxGlade
Totalmente Visual
Genera XRC y .py
No soporta
muchos de los
controles actuales
de wxPython
Bastante rústico
XRCed
wxFormBuilder
Ejemplo 5/1 – XRC
#!/usr/bin/python
# coding: utf-8
import wx
import [Link] as xrc
class EditorApp([Link]):
XRC_FILE = '[Link]'
¡Con esta línea armé todo el form!
def OnInit(self):
# Cargo el XRC y el frame principal
[Link] = [Link](EditorApp.XRC_FILE)
[Link] = [Link](None, 'MainWindow')
# Obtengo la referencia al texto, útil
[Link] = [Link]([Link], 'txtTexto')
# Conecto los eventos
[Link](wx.EVT_MENU, [Link], id=[Link]('mnuNuevo'))
[Link](wx.EVT_MENU, [Link], id=[Link]('mnuAbrir'))
[Link](wx.EVT_MENU, [Link], id=[Link]('mnuGuardar'))
[Link](wx.EVT_MENU, [Link], id=[Link]('mnuSalir'))
[Link]()
return True
Ejemplo 5/2 – XRC
def OnNuevo(self, event):
[Link]() # TODO: Preguntar si desea guardar los cambios
def OnAbrir(self, event):
filters = 'All files (*.*)|*.*|Text files (*.txt)|*.txt'
dialog = [Link](None, message=u'Abrir Archivo de Texto',
wildcard=filters, style=[Link])
if [Link]() == wx.ID_OK:
file_path = [Link]()
[Link](open(file_path, 'r').read())
[Link]()
def OnGuardar(self, event):
dialog = [Link](None, message=u'Guardar Archivo de Texto',
style=[Link]|wx.FD_OVERWRITE_PROMPT)
if [Link]() == wx.ID_OK:
file_path = [Link]()
open(file_path, 'w').write([Link]())
[Link]()
def OnSalir(self, event):
[Link]()
if __name__ == '__main__':
app = EditorApp()
[Link]()
wxPython Demo
¡Demo!
Y como si fuera poco...
Visualización e impresión de HTML simple
Print Framework, con vista previa y configuración
Clipboard y drag and drop
Ayuda en línea. Gran comunidad alrededor
Librería de graficación de objetos vectoriales: OGL.
Soporte para Cairo y OpenGL (GLCanvas)
Texto enriquecido (RTF) y "estilizado" (STC)
Animaciones y multimedia
Programación multiproceso, Unicode,
componentes personalizados, [Link]
Links
Sitio oficial: [Link]
Libro de referencia: wxPython in
Action (Manning)
Wiki Comunidad: [Link]
Listas de correo:
wxPython-users
wx-users
PyAr - [Link] ;-)
[Link]
[Link]