La Parte 1 de esta serie describe los conceptos básicos sobre la secuencia de comandos de la máquina virtual Kernel con libvirt y Python. Esta entrega utiliza los conceptos desarrollados para crear algunas aplicaciones de utilidades y añadir una interfaz gráfica de usuario (GUI) en la mezcla. Existen dos opciones principales para un kit de herramientas de GUI que tiene enlaces Python y con varias plataformas. La primera es Qt, que ahora pertenece a Nokia; la segunda es wxPython. Ambas tienen grandes seguidores y muchos proyectos de código abierto en sus listas de usuarios.
En este artículo, me concentraré en wxPython por preferencia personal. Comenzaré con una introducción breve de wxPython y en la configuración básica correspondiente. Desde aquí, veremos algunos ejemplos de programas y luego lo integraremos con libvirt.
Este enfoque debe introducir los conceptos básicos suficientes de wxPython para que pueda desarrollar un programa simple y, luego, expandirlo añadiendo funciones.
Podrá tomar estos conceptos y aprovecharlos para cumplir necesidades específicas.
Empezaremos con algunas definiciones básicas. La biblioteca de wxPython es un derivador en la parte superior de C++ basado en wxWidgets. En el contexto de creación de una GUI, un widget es un bloque de creación. Existen cinco widgets independientes en el nivel más alto de la jerarquía de widget:
wx.Frame,
wx.Dialog,
wx.PopupWindow,
wx.MDIParentFramey
wx.MDIChildFrame.
La mayoría de los ejemplos se basan en wx.Frame, ya que implementa una ventana simple.
En wxPython, Frame es una clase que crea una instancia como está o que hereda para añadir o mejorar un función.
Es importante comprender cómo widgets aparecen dentro de una trama para que sepa cómo ubicarlos correctamente. El diseño se determina por el posicionamiento absoluto o por el uso de sizers. Un sizer es una herramienta que modifica el tamaño de los widgets cuando el usuario cambia el tamaño de la ventana al hacer clic o arrastrar un lateral o esquina.
El formulario más simple de un programa wzPython debe tener algunas líneas de códigos para realizar la configuración. Una rutina principal típica se asemeja al Listado 1.
Listado 1 Definición del dispositivo XML
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
frame.Show()
app.MainLoop()
|
Cada aplicación de wxPython es una instancia de wx.App() y debe crear una instancia como se muestra en el Listado 1. Cuando pasa de False a wx.App, significa "no redirigir stdout y stderr a una ventana". La siguiente línea crea una trama al inicializar la clase MyFrame(). Luego muestra la trama y pasa el mando a app.MainLoop(). La clase MyFrame() generalmente contiene la función __init__ para inicializar la trama con las widgets que seleccionó. Aquí es donde debe conectar los eventos de widget a sus manejadores respectivos.
Este es el mejor momento para mencionar una herramienta de depuración que viene con wxPython. Se llama herramienta de inspección de widget (vea la Figura 1) y solo requiere dos líneas de códigos. Primero, debe importarla con:
import wx.lib.inspection |
Luego, para usarla, simplemente debe usar la función Show():
wx.lib.inspectin.InspectionTool().Show() |
Al hacer clic en el ícono Events en la barra de herramientas del menú se muestran los eventos mientras se lanzan. Es una forma muy clara para ver los eventos en tiempo real si no está seguro de los eventos que admite widget. También puede apreciar mejor lo que está sucediendo entre bastidores cuando se está ejecutando su aplicación.
Figura 1. Herramienta de inspección de widget de wxPython
Añadir una GUI a una herramienta de línea de comandos
La Parte 1 de esta serie presenta una herramienta simple para ver el estado de todas las máquinas virtuales que se están ejecutando (VM). Es simple cambiar esa herramienta a una herramienta de GUI con wxPython. El widget wx.ListCtrl solo provee la función que es necesaria para presentar la información en un formulario tabular. Para usar un widget wx.ListCtrl, usted debe añadirlo a una trama con la siguiente sintaxis:
self.list=wx.ListCtrl(frame,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER) |
Es posible seleccionar diferentes estilos, incluso las opciones wx.LC_REPORT y wx.SUNKEN_BORDER que se usaron anteriormente. La primera opción coloca wx.ListCtrl en modo Informe, que es uno de nuestros cuatro modos disponibles. Los demás son ícono, ícono pequeño y lista. Para añadir estilos como wx.SUNKEN_BORDER, solo debe usar la barra vertical (|). Algunos estilos son exclusivos, como los estilos de bordes diferentes, por eso revise la wiki de wxPython si tiene dudas (vea Recursos).
Luego de iniciar el widget wx.ListCtrl, puede comenzar a añadir elementos, como títulos de columnas. El método InsertColumn tiene dos parámetros obligatorios y dos opciones. El primero es el índice de columna, que tiene base cero, seguido de una cadena para establecer el título. El tercero es para el formato y debe ser LIST_FORMAT_CENTER, _LEFT o _RIGHT.
Finalmente, es posible establecer un ancho fijo ingresando un entero o tener un tamaño automático de columna usando wx.LIST_AUTOSIZE.
Ahora que el widget está configurado wx.ListCtrl es posible usar los métodos InsertStringItem y SetStringItem para completarlo con datos. Cada fila nueva en el widget wx.ListCtrl debe añadirse usando el método InsertStringItem. Los dos parámetros obligatorios especifican dónde realizar la inserción, con un valor de 0 que indican la parte superior de la lista y la cadena para insertar en la ubicación. InsertStringItem se convierte en entero e indica la cantidad de filas en la cadena ingresada. Puede llamar a GetItemCount() para la lista y usar el valor de retorno para el índice para añadirlo en la parte inferior, como se muestra en el Listado 2.
Listado 2 Versión de GUI de la herramienta de línea de comandos
import wx
import libvirt
conn=libvirt.open("qemu:///system")
class MyApp(wx.App):
def OnInit(self):
frame = wx.Frame(None, -1, "KVM Info")
id=wx.NewId()
self.list=wx.ListCtrl(frame,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.list.Show(True)
self.list.InsertColumn(0,"ID")
self.list.InsertColumn(1,"Name")
self.list.InsertColumn(2,"State")
self.list.InsertColumn(3,"Max Mem")
self.list.InsertColumn(4,"# of vCPUs")
self.list.InsertColumn(5,"CPU Time (ns)")
for i,id in enumerate(conn.listDomainsID()):
dom = conn.lookupByID(id)
infos = dom.info()
pos = self.list.InsertStringItem(i,str(id))
self.list.SetStringItem(pos,1,dom.name())
self.list.SetStringItem(pos,2,str(infos[0]))
self.list.SetStringItem(pos,3,str(infos[1]))
self.list.SetStringItem(pos,4,str(infos[3]))
self.list.SetStringItem(pos,5,str(infos[2]))
frame.Show(True)
self.SetTopWindow(frame)
return True
app = MyApp(0)
app.MainLoop()
|
La Figura 2 muestra los resultados de estos esfuerzos.
Figura 2. La herramienta de información de KVM de GUI
Es posible mejorar la apariencia de esta tabla. Una notable mejoría puede ser modificar el tamaño de las columnas. Puede hacerlo añadiendo el parámetro width = a la llamada InsertColumn o usar una línea de código, como esta:
self.ListCtrl.SetColumnWidth(column,wx.LIST_AUTOSIZE) |
Otro paso que puede realizar es añadir un sizer para que controle la modificación de tamaño con la ventana padre. Puede hacerlo con wxBoxSizer en unas líneas de código. Primero, crea el sizer y, luego, le añade los widgets que desea modificar el tamaño junto con la ventana principal. El código será de esta manera:
self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.list, proportion=1,flag=wx.EXPAND | wx.ALL, border=5) self.sizer.Add(self.button, flag=wx.EXPAND | wx.ALL, border=5) self.panel.SetSizerAndFit(self.sizer) |
La última llamada a self.panel.SetSizerAndFit() indica a wxPython que establezca el tamaño inicial del panel según el tamaño mínimo del sizer desde los widgets integrados. Esto ayuda a que su pantalla inicial tenga un tamaño razonable según el contenido.
Flujo de control según la acción de un usuario
Una de las buenas cosas sobre el widget wx.ListCtrl es que puede detectar cuando un usuario hace clic en una parte específica del widget y realiza una acción. Esta función le permite ordenar una columna por orden alfabético hacia adelante o hacia atrás según en qué título de columna hace clic el usuario. La técnica para lograr esto usa un mecanismo de devolución de llamada. Debe brindar una función para manejar cada acción que desee procesar enlazando el widget y procesando el método. Esto se realiza con el método Bind.
Cada widget tiene una cantidad de eventos asociados. También hay eventos asociados con el mouse. Los eventos del mouse se denominan EVT_LEFT_DOWN, EVT_LEFT_UP y EVT_LEFT_DCLICK, junto con el mismo convenio de denominación para otros botones. Puede manejar todos los eventos del mouse adjuntando un tipo EVT_MOUSE_EVENTS. El truco es atrapar el evento en contexto de la aplicación o ventana en que está interesado.
Cuando el control pasa al controlador de evento, este debe realizar los pasos necesarios para controlar la acción y, luego, devolver el control donde estaba antes. Este es el modelo de programación de mando de evento que cada GUI debe implementar para manejar las acciones de los usuarios en un tiempo adecuado. Muchas ampliaciones modernas de GUI implementan un multiproceso para poder ofrecer al usuario la impresión del que el programa no responde. Comentaremos esto brevemente más adelante en este artículo.
Los temporizadores representan otro tipo de evento que un programa debe tratar. Por ejemplo, desea realizar una función de monitoreo periódico a un intervalo de usuario definido. Tendrá que brindar una pantalla donde el usuario puede especificar el intervalo y, luego, lanzar un temporizador que pueda activar un evento cuando este se venza. El vencimiento del temporizador activa un evento que puede usar para activar una sección de código. Tendrá que establecer o reiniciar el tiempo, según la preferencia del usuario. Puede usar fácilmente esta técnica para desarrollar una herramienta de monitoreo de VM.
El Listado 3 provee una aplicación de demostración simple con un botón y líneas de texto estáticas. Usar wx.StaticText es una forma fácil para crear una cadena en una ventana. La idea es hacer clic en el botón para comenzar el temporizador y registrar la hora de inicio. Hacer clic en el botón que registra la hora de inicio y cambia a Stop.
Hacer clic en el botón completa el tiempo de detención en el recuadro de texto y cambia al botón Start.
Listado 3 Aplicación simple con un botón y texto estático
import wx
from time import gmtime, strftime
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Buttons")
self.panel = wx.Panel(self, wx.ID_ANY)
self.button = wx.Button(self.panel, id=wx.ID_ANY, label="Start")
self.button.Bind(wx.EVT_BUTTON, self.onButton)
def onButton(self, event):
if self.button.GetLabel() == "Start":
self.button.SetLabel("Stop")
strtime = strftime("%Y-%m-%d %H:%M:%S", gmtime())
wx.StaticText(self, -1, 'Start Time = ' + strtime, (25, 75))
else:
self.button.SetLabel("Start")
stptime = strftime("%Y-%m-%d %H:%M:%S", gmtime())
wx.StaticText(self, -1, 'Stop Time = ' + stptime, (25, 100))
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
|
Ahora, puede añadir una función a la supervisión de GUI que se presentó anteriormente. Existe una parte más de wxPython que es necesaria para comprender antes de saber todo lo que es necesario para crear su aplicación. Añadir un recuadro de selección a la primera columna del widget wx.ListCtrl hará posible accionar en varias líneas según el estado del cuadro de verificación. Puede hacer esto usando lo que wxPython denomina mixins.
En un sentido, una mixin es una clase ayudante que añade algunos tipos de funciones a un widget padre. Para añadir el recuadro de selección de mezcla, solo use el siguiente código para crear una instancia:
listmix.CheckListCtrlMixin.__init__(self) |
Puede aprovechar los eventos para añadir la capacidad de seleccionar o borrar todos los cuadros al hacer clic en el título de la columna. Con esto es más fácil iniciar o detener todas las VM con solo algunos clics. Debe escribir algunos controladores de eventos para responder a los eventos correspondientes de la misma forma que cambió los botones anteriormente. Esta es la línea de código que necesita para establecer un controlador para el evento de clic en columna:
self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list) |
wx.EVT_LIST_COL_CLICK se activa cuando se hace clic en el título de la columna. Para determinar en qué columna se hizo clic, es posible usar el método event.GetColumn(). Esta es una función de controlador simple para el evento OnColClick:
def OnColClick(self, event):
print "column clicked %d\n" % event.GetColumn()
event.Skip()
|
La llamada event.Skip() es importante si necesita propagar el evento a otros controladores. Aunque esto no parezca necesario en este paso, puede causar un problema cuando varios controladores necesiten procesar el mismo evento. Existe un buen análisis sobre la propagación de eventos en el sitio de wiki wxPython, que tiene muchos más detalles.
Finalmente, añada el código a los dos botones de los controladores para iniciar o detener todas las VM revisadas. Es posible iterar sobre las líneas en su wx.ListCtrl y sacar la ID de VM con solo unas líneas de códigos, como se muestra en el Listado 4.
Listado 4 Comenzar y dejar de revisar las VM
#!/usr/bin/env python
import wx
import wx.lib.mixins.listctrl as listmix
import libvirt
conn=libvirt.open("qemu:///system")
class CheckListCtrl(wx.ListCtrl, listmix.CheckListCtrlMixin,
listmix.ListCtrlAutoWidthMixin):
def __init__(self, *args, **kwargs):
wx.ListCtrl.__init__(self, *args, **kwargs)
listmix.CheckListCtrlMixin.__init__(self)
listmix.ListCtrlAutoWidthMixin.__init__(self)
self.setResizeColumn(2)
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.list = CheckListCtrl(self.panel, style=wx.LC_REPORT)
self.list.InsertColumn(0, "Check", width = 175)
self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list)
self.list.InsertColumn(1,"Max Mem", width = 100)
self.list.InsertColumn(2,"# of vCPUs", width = 100)
for i,id in enumerate(conn.listDefinedDomains()):
dom = conn.lookupByName(id)
infos = dom.info()
pos = self.list.InsertStringItem(1,dom.name())
self.list.SetStringItem(pos,1,str(infos[1]))
self.list.SetStringItem(pos,2,str(infos[3]))
self.StrButton = wx.Button(self.panel, label="Start")
self.Bind(wx.EVT_BUTTON, self.onStrButton, self.StrButton)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.list, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
self.sizer.Add(self.StrButton, flag=wx.EXPAND | wx.ALL, border=5)
self.panel.SetSizerAndFit(self.sizer)
self.Show()
def onStrButton(self, event):
if self.StrButton.GetLabel() == "Start":
num = self.list.GetItemCount()
for i in range(num):
if self.list.IsChecked(i):
dom = conn.lookupByName(self.list.GetItem(i, 0).Text)
dom.create()
print "%d started" % dom.ID()
def OnColClick(self, event):
item = self.list.GetColumn(0)
if item is not None:
if item.GetText() == "Check":
item.SetText("Uncheck")
self.list.SetColumn(0, item)
num = self.list.GetItemCount()
for i in range(num):
self.list.CheckItem(i,True)
else:
item.SetText("Check")
self.list.SetColumn(0, item)
num = self.list.GetItemCount()
for i in range(num):
self.list.CheckItem(i,False)
event.Skip()
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
|
Existen dos cosas para destacar con respecto al estado de las VM en KVM: La ejecución de las VM se muestra cuando use el método listDomainsID() de libvirt. Para ver las máquinas que no se están ejecutando debe usar listDefinedDomains(). Debe tenerlos separados para saber qué VM puede iniciar o detener.
Este artículo se centra principalmente en los pasos necesarios para desarrollar un derivador de GUI con wxPython que maneja KVM con libvirt. La biblioteca wxPython es amplia y provee una amplia gama de widgets para que pueda desarrollar aplicaciones basadas en GUI de apariencia profesional. Este artículo detalla la parte superficial de sus capacidades, pero se motivará para investigar más. Asegúrese de verificar los Recursos para poder hacer funcionar esta aplicación.
Aprender
- sitio web de
libvirt: Revise todo el sitio para obtener más información. -
Manual de referencia de
libvirt: Acceso al manual de referencia completo de APIlibvirt. - Python.org: Obtenga más información sobre los recursos de Python que necesita.
-
wxPython.org: Conozca más acerca de wxPython.
- wxPython wiki: Amplíe su conocimiento con estos tutoriales.
- Zona de códigos abiertos de developerWorks: Encuentre grandes cantidades de información práctica, herramientas y actualizaciones del proyecto que lo ayudarán a desarrollar con tecnologías de código abierto y a usarlas con productos IBM. Explore más artículos relacionados con Python.
- Eventos de interés: Revise las próximas conferencias, congresos y transmisiones por la web que son interesantes para los desarrolladores de código abierto de IBM.
- Podcasts de developerWorks: Seleccione entrevistas y debates interesantes para desarrolladores de software
- Demos de developerWorks: Mire nuestros demos gratuitos y aprenda acerca de IBM y las tecnologías de código abierto y funciones de productos.
- developerWorks en
Twitter: Síganos para obtener las últimas novedades.
Obtener los productos y tecnologías
- Evaluar los productos de software de IBM: Desde descargas de pruebas hasta productos en la nube, puede comenzar su próximo proyecto de desarrollo de código abierto con un software especialmente para desarrolladores.
Comentar
- Comunidad de developerWorks: Conéctese con otros usuarios de developerWorks mientras explora los blogs conducidos por desarrolladores, foros, grupos y wikis. Ayude a desarrollar el grupo Código abierto del mundo real en la comunidad de developerWorks.
Paul Ferrill ha trabajado como escritor en el rubro de la informática por más de 20 años.Obtuvo su primer trabajo en la escritura de reseñas enPC Magazine sobre productos como LANtastic y las primeras versiones de Novell Netware. Paul posee una Licenciatura y una Maestría en Ingeniería Eléctrica y ha escrito software para tantas plataformas y arquitecturas informáticas que es difícil recordar.