A Parte 1 desta série analisou as noções básicas de script de
Kernel-based Virtual Machine (KVM) usando
libvirt e Python. Este artigo usa os
conceitos desenvolvidos na primeira parte para desenvolver diversos aplicativos de utilitário e adicionar
uma interface gráfica com o usuário (GUI) na combinação. Há duas opções principais
para um kit de ferramentas de GUI com ligações de Python e que serve para várias plataformas. A
primeira é Qt, que agora pertence à Nokia; a segunda é wxPython. Ambas
têm seguidores fervorosos e muitos projetos de software livre em suas listas de
usuários.
Para este artigo, eu me concentro no wxPython, mas por preferência pessoa do que
qualquer coisa. Eu começo com uma breve introdução ao wxPython e a
configuração básica apropriada. A partir daí, passo para alguns programas
de exemplo e, depois, para a integração com o libvirt.
Essa abordagem deve apresentar noções básicas suficientes do wxPython para você desenvolver um
programa simples e, depois, expandir esse programa para adicionar recursos.
Espero que você absorva esses conceitos e desenvolva-os a fim de atender
às suas necessidades específicas.
Um bom local para começar é com algumas definições básicas. A biblioteca do wxPython
é, na verdade, um wrapper sobre os wxWidgets baseados em C++. No contexto de criação de uma GUI, um widget é
essencialmente um bloco de construção. Há cinco widgets independentes no
nível superior da hierarquia de widgets:
wx.Frame,
wx.Dialog,
wx.PopupWindow,
wx.MDIParentFramee
wx.MDIChildFrame.
A maioria dos exemplos aqui tem base no wx.Frame,
pois, essencialmente, ele implementa uma única janela modal.
No wxPython, Frame é uma classe que você
instancia como está, ou herda dela, a fim de adicionar ou aprimorar a funcionalidade.
É importante entender como os widgets aparecem dentro de um quadro para que você saiba
como colocá-los apropriadamente. O Layout é determinado pelo posicionamento absoluto
ou usando dimensionadores. Um dimensionador é uma ferramenta útil que
redimensiona os widgets quando o usuário muda o tamanho da janela clicando
e arrastando um lado ou um canto.
A forma mais simples de um programa wxPython precisa ter algumas linhas de código para configurar tudo. Uma rotina típica principal pode parecer um pouco com a Listagem 1.
Listagem 1. Definição XML do dispositivo
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
frame.Show()
app.MainLoop()
|
Todo aplicativo wxPython é uma instância do wx.App()
e precisa instanciá-lo como mostra a Listagem 1. Quando você passa
False para wx.App, significa
"não redirecione stdout e stderr para uma janela". A próxima linha
cria um quadro instanciando a classe MyFrame()
. Em seguida, você mostra o quadro e o controle de passagem para
app.MainLoop(). A classe
MyFrame() normalmente contém uma função
__init__ para inicializar o quadro
com seus widgets de preferência. Também é onde você conectaria quaisquer eventos de widget
aos seus manipuladores apropriados.
Esse é provavelmente um bom local para mencionar uma ferramenta de depuração útil que acompanha o wxPython. Ela é chamada de ferramenta de inspeção de widget (consulte a Figura 1) e exige apenas duas linhas de código para ser usada. Primeiro, você precisa importá-la com:
import wx.lib.inspection |
Em seguida, para usá-la, basta chamar a função Show()
:
wx.lib.inspectin.InspectionTool().Show() |
Clicar no ícone Events na barra de ferramentas do menu mostra dinamicamente os eventos, à medida que eles são acionados. É uma forma realmente interessante de ver eventos à medida que eles ocorrem, caso você não tenha certeza quais eventos um widget específico suporta. Também proporciona uma apreciação melhor do que acontece nos bastidores quando seu aplicativo está em execução.
Figura 1. A ferramenta de inspeção de widget do wxPython
Adicione uma GUI a uma ferramenta de linha de comando
A Parte 1 desta série apresentou uma ferramenta simples para exibir o
status de todas as máquinas virtuais (VMs) em execução. É simples alterar essa
ferramenta em uma ferramenta de GUI com wxPython. O widget wx.ListCtrl fornece somente a
funcionalidade que você precisa para apresentar as informações de uma maneira tabular. Para usar
um widget wx.ListCtrl , você precisa adicioná-lo ao seu
quadro com a seguinte sintaxe:
self.list=wx.ListCtrl(frame,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER) |
É possível escolher entre diversos estilos, incluindo as opções
wx.LC_REPORT e wx.SUNKEN_BORDER usadas anteriormente. A
primeira opção coloca o wx.ListCtrl no modo Report,
que é um dos quatro modos disponíveis. Os outros são Icon, Small
Icon e List. Para adicionar estilos como
wx.SUNKEN_BORDER, basta usar o caractere de barra vertical
(|). Alguns estilos são mutuamente
exclusivos, como os estilos de borda diferente, portanto, consulte o wiki do wxPython
se tiver dúvida (consulte Recursos).
Depois de instanciar o widget wx.ListCtrl , é possível
começar a adicionar coisas a ele, como cabeçalhos de coluna. O método InsertColumn tem dois
parâmetros obrigatórios e dois opcionais. Primeiro é o índice da coluna, que tem
base em zero, seguido por uma cadeia de caracteres para definir o título. O terceiro serve para
formatação e deve parecer com o seguinte
LIST_FORMAT_CENTER,
_LEFT ou
_RIGHT.
Finalmente, é possível definir uma largura fixa passando um número inteiro ou redimensionar
automaticamente a coluna usando
wx.LIST_AUTOSIZE.
Agora que você o widget wx.ListCtrl está configurado, use os métodos InsertStringItem
e SetStringItem para preenchê-los com dados. Cada nova linha no widget wx.ListCtrl
precisa ser adicionada usando o método InsertStringItem
. Os dois parâmetros obrigatórios especificam onde realizar a inserção,
com um valor de 0 aparecendo no topo da lista e a cadeia de caractere a ser
inserida nesse local. InsertStringItem
retorna um número inteiro indicando o número da linha da cadeia de caracteres inserida. É
possível fazer uma chamada para GetItemCount() para a lista
e usar o valor de retorno para o índice a fim de anexar na parte inferior, como mostra a Listagem 2 .
Listagem 2. Versão GUI da ferramenta de linha de comando
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()
|
A Figura 2 mostra os resultados desses esforços.
Figura 2. A ferramenta de informações GUI KVM
É possível aprimorar a aparência dessa tabela. Um aprimoramento considerável
seria redimensionar as colunas. É possível fazer isso adicionando o parâmetro
width = à chamada
InsertColumn ou usar uma linha de código, como
esta:
self.ListCtrl.SetColumnWidth(column,wx.LIST_AUTOSIZE) |
A outra coisa que você pode fazer é adicionar um redimensionador de modo que os controles sejam
redimensionados com a janela pai. É possível fazer isso com um
wxBoxSizer em algumas linhas de código. Primeiro, você
cria o redimensionador e, em seguida, adiciona a ele os widgets que você deseja
redimensionar junto com a janela principal. Veja como seria o código:
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) |
A última chamada para self.panel.SetSizerAndFit()
instrui o wxPython a definir o tamanho inicial do painel com base no
tamanho mínimo do redimensionador dos widgets incorporados. Isso ajuda a dar à sua
tela inicial um tamanho razoável com base no conteúdo interno.
Fluxo de controle baseado na ação de um usuário
Uma das coisas legais sobre o widget wx.ListCtrl
é que é possível detectar quando um usuário clica em uma parte específica do
widget e executa alguma ação com base nisso. Essa funcionalidade permite que você
faça coisas como classificar uma coluna alfabeticamente, em ordem direta ou inversa,
com base no clique do usuário no título da coluna. A técnica para conseguir isso
usa um mecanismo de retorno de chamada. Você precisa fornecer uma função para lidar com cada
ação que deseja processar, ligando o widget e o método
de processamento. Faça isso com o método Bind
.
Cada widget tem uma quantidade de eventos associados. Há também
eventos associados a coisas como o mouse. Os eventos de mouse têm nomes como
EVT_LEFT_DOWN,
EVT_LEFT_UP e EVT_LEFT_DCLICK, junto com a mesma convenção de
nomenclatura para os outros botões. É possível lidar com todos os eventos de mouse anexando
ao tipo EVT_MOUSE_EVENTS . O
truque é pegar o evento no contexto do aplicativo ou da janela
na qual você está interessado.
Quando o controle passa para o manipulador de eventos, ele precisa executar as etapas necessárias para lidar com a ação e retornar o controle ao local anterior. Esse é o modelo de programação conduzido por evento que toda GUI precisa implementar para lidar com as ações do usuário de uma forma pontual. Muitos aplicativos GUI modernos implementam o multiencadeamento para não dar ao usuário a impressão de que o programa não está respondendo. Eu falo brevemente sobre isso posteriormente neste artigo.
Timers representam outro tipo de evento com os quais um programa precisa lidar possivelmente. Por exemplo, convém executar uma função de monitoramento periódico a um intervalo definido pelo usuário. Seria necessário fornecer uma tela na qual o usuário especificaria o intervalo e iniciar um timer que acionaria um evento ao expirar. A expiração do timer aciona um evento que pode ser usado para ativar uma seção de código. Talvez seja necessário definir ou reiniciar o tempo, dependendo novamente da preferência do usuário. Você poderia usar facilmente essa técnica para desenvolver uma ferramenta de monitoramento de VM.
A Listagem 3 fornece um aplicativo de demonstração simples com linhas de um botão e
um texto estático. Usar wx.StaticText é uma
maneira fácil de mostrar uma cadeia de caracteres na janela. A ideia é clicar no botão
uma vez para iniciar um timer e registrar o horário de início. Clicar no botão
registra o horário de início e muda o rótulo para Stop.
Clicar no botão novamente preenche a caixa de texto do horário de término e muda o
botão de volta para Start.
Listagem 3. Aplicativo simples com um botão e um 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()
|
GUI de monitoramento aprimorado
Agora, é possível adicionar a funcionalidade à GUI de monitoramento simples apresentada
anteriormente. Há mais uma parte do wxPython que você precisa entender antes
de ter tudo que precisa para criar seu aplicativo. A adição de uma caixa de seleção à
primeira coluna de um widget wx.ListCtrl possibilitaria
a execução de uma ação em diversas linhas com base no status da
caixa de seleção. É possível fazer isso usando o que o wxPython chama de mixins.
Essencialmente, um mixin é uma classe auxiliar que adiciona algum tipo de
funcionalidade ao widget pai. Para adicionar caixa de seleção mixin, basta usar
o código a seguir para instanciá-la:
listmix.CheckListCtrlMixin.__init__(self) |
Também é possível aproveitar a vantagem dos eventos para adicionar a capacidade de marcar ou limpar todas as caixas clicando no título da coluna. Fazer isso simplifica a execução de coisas como iniciar ou parar todas as VMs com apenas alguns cliques. É necessário escrever alguns manipuladores de evento a fim de responder aos eventos apropriados da mesma forma que você mudou o rótulo no botão anteriormente. Esta é a linha de código para configurar um manipulador para o evento de clique na coluna:
self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list) |
wx.EVT_LIST_COL_CLICK é acionado quando qualquer cabeçalho de
coluna é clicado. Para determinar qual coluna foi clicada, use o método
event.GetColumn() . Veja uma função
de manipulador simples para o evento OnColClick :
def OnColClick(self, event):
print "column clicked %d\n" % event.GetColumn()
event.Skip()
|
A chamada de event.Skip() é importante se você precisar
propagar o evento para outros manipuladores. Embora essa necessidade não seja
aparente nessa instância, ela pode se tornar um problema quando diversos manipuladores
precisarem processar o mesmo evento. Há uma boa discussão sobre propagação de eventos
no site de wiki do wxPython, com muito mais detalhes do que este artigo permite.
Finalmente, adicione o código aos dois manipuladores de botão para iniciar ou parar todas as
VMs marcadas. É possível iterar sobre as linhas em seu
wx.ListCtrl e obter o ID da VM com apenas algumas
linhas de código, como mostra a Listagem 4 .
Listagem 4. Iniciando e parando as VMs marcadas
#!/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()
|
Há duas coisas a serem apontadas aqui com relação ao estado das VMs no
KVM: as VMs em execução são exibidas quando você usa o método
listDomainsID() do libvirt. Para ver as máquinas que não estão em execução, você precisa usar listDefinedDomains(). É necessário apenas manter esses
dois separados de modo que você saiba quais VMs você pode iniciar e quais
você pode parar.
Este artigo se concentrou principalmente nas etapas necessárias para o desenvolvimento de um wrapper de GUI
usando wxPython que, por sua vez, gerenciar o KVM com
libvirt. A biblioteca wxPython é ampla e
fornece uma grande variedade de widgets a fim de permitir que você desenvolva
aplicativos baseados em GUI com uma aparência profissional. Este artigo apenas
deu uma ideia dos recursos possíveis, mas espero que o tenha motivado
a investigar ainda mais. Consulte mais Recursos para ajudá-lo a executar seu aplicativo.
Aprender
Website de libvirt: verifique todo o site para obter mis informações.-
Manual de Referência para
libvirt: acesse o manual de referência de APIlibvirt. - Python.org: encontre mais informações sobre os recursos Python que você precisa.
-
wxPython.org: obtenha mais informações sobre o
wxPython.
- Wiki do wxPython: expanda seu conhecimento com os diversos tutoriais encontrados aqui.
- Zona de software livre do developerWorks: encontre informações práticas, ferramentas e atualizações de projeto amplas para ajudá-lo a desenvolver com tecnologias de software livre e utilizá-las com produtos IBM.Explore mais artigos relacionados ao Python.
- Eventos interessantes: confira futuras conferências, exposições e webcasts interessantes para desenvolvedores de software livre IBM.
- Podcasts do developerWorks: escute entrevistas e explicações interessantes para desenvolvedores de software
- Demos do developerWorks: Acompanhe nossas demos gratuitas e saiba mais sobre as tecnologias IBM e de software livre e funções dos produtos.
- DeveloperWorks no Twitter: siga-nos para acompanhar as últimas notícias.
Obter produtos e tecnologias
- Avalie produtos de software IBM: a partir de downloads de teste para produtos hospedados na nuvem, é possível inovar no seu próximo projeto de desenvolvimento de software livre usando software especialmente para desenvolvedores.
Discutir
- comunidade do developerWorks: Conecte-se a outros usuários do developerWorks enquanto explora os blogs, fóruns, grupos e wikis voltados para desenvolvedores. Ajude a desenvolver o software livre do mundo real na comunidade do developerWorks.
Paul Ferrill escreve nos meios de comunicação de comércio de computadores há mais de 20 anos. Começou escrevendo resenhas de rede para o PC Magazine sobre produtos como LANtastic e versões anteriores do Novell Netware. Paul é graduado e possui mestrado em engenharia elétrica e escreveu softwares para mais arquiteturas e plataformas de computador do que consegue se lembrar.