{
"metadata": {
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[Python para Desenvolvedores](http://ricardoduarte.github.io/python-para-desenvolvedores/#conteudo)\n",
"===================================\n",
"2ª edi\u00e7\u00e3o, revisada e ampliada\n",
"-----------------------------------\n",
"\n",
"Cap\u00edtulo 33: Interface Gr\u00e1fica\n",
"=============================\n",
"_____________________________\n",
"As Interfaces Gr\u00e1ficas com Usu\u00e1rio (GUI, *Graphic User Interface*) se popularizaram no ambiente *desktop*, devido \u00e0 facilidade de uso e a produtividade. Existem hoje muitas bibliotecas dispon\u00edveis para a constru\u00e7\u00e3o de aplica\u00e7\u00f5es GUI, tais como: GTK+, QT, TK e wxWidgets.\n",
"\n",
"Arquitetura\n",
"-----------\n",
"Interfaces gr\u00e1ficas geralmente utilizam a met\u00e1fora do *desktop*, um espa\u00e7o em duas dimens\u00f5es, \u00e9 que ocupado por janelas retangulares, que representam aplicativos, propriedades ou documentos.\n",
"\n",
"![Esquema de GUI](files/gui.png)\n",
"\n",
"As janelas podem conter diversos tipos de controles (objetos utilizados para interagir com o usu\u00e1rio ou para apresentar informa\u00e7\u00f5es) e *containers* (objetos que servem de reposit\u00f3rio para cole\u00e7\u00f5es de outros objetos).\n",
"\n",
"Na maior parte do tempo, a interface gr\u00e1fica espera por eventos e responde de acordo. Os eventos podem ser resultado da intera\u00e7\u00e3o do usu\u00e1rio, como cliques e arrastar de mouse ou digita\u00e7\u00e3o, ou ainda de eventos de sistema, como o rel\u00f3gio da m\u00e1quina. A rea\u00e7\u00e3o a um evento qualquer \u00e9 definida atrav\u00e9s de fun\u00e7\u00f5es *callback* (fun\u00e7\u00f5es que s\u00e3o passadas como argumento para outras rotinas).\n",
"\n",
"Controles mais usados:\n",
"\n",
"+ R\u00f3tulo (*label*): ret\u00e2ngulo que exibe texto.\n",
"+ Caixa de texto (*text box*): \u00e1rea de edi\u00e7\u00e3o de texto.\n",
"+ Bot\u00e3o (*button*): \u00e1rea que pode ser ativada interativamente.\n",
"+ Bot\u00e3o de r\u00e1dio (*radio button*): tipo especial de bot\u00e3o, com o qual s\u00e3o formados grupos aonde apenas um pode ser apertado de cada vez.\n",
"+ Bot\u00e3o de verifica\u00e7\u00e3o (*check button*): bot\u00e3o que pode ser marcado e desmarcado.\n",
"+ Barras de rolagem (*scroll bars*): controles deslizantes horizontais ou verticais, usados para intervalos de valores num\u00e9ricos.\n",
"+ Bot\u00e3o girat\u00f3rio (*spin button*): caixa de texto com dois bot\u00f5es com setas ao lado que incrementam e decrementam o n\u00famero na caixa.\n",
"+ Barra de status (*status bar*): barra para exibi\u00e7\u00e3o de mensagens, geralmente na parte inferior da janela.\n",
"+ Imagem (*image*): \u00e1rea para exibi\u00e7\u00e3o de imagens.\n",
"\n",
"Controles podem ter aceleradores (teclas de atalho) associados a eles.\n",
"\n",
"Containers mais usados:\n",
"\n",
"+ Barra de menu (*menu bar*): sistema de menus, geralmente na parte superior da janela.\n",
"+ Fixo (*fixed*): os objetos permanecem fixos nas mesmas posi\u00e7\u00f5es.\n",
"+ Tabela (*table*): cole\u00e7\u00e3o de compartimentos para fixar os objetos, distribu\u00eddos em linhas e colunas.\n",
"+ Caixa horizontal (*horizontal box*): semelhante \u00e0 tabela, por\u00e9m apenas com uma linha.\n",
"+ Caixa vertical (*vertical box*): semelhante \u00e0 tabela, por\u00e9m apenas com uma coluna.\n",
"+ Caderno (*notebook*): v\u00e1rias \u00e1reas que podem ser visualizadas uma de cada vez quando selecionadas atrav\u00e9s de abas, geralmente na parte superior.\n",
"+ Barra de ferramentas (*toolbar*): \u00e1rea com bot\u00f5es para acesso r\u00e1pido aos principais recursos do aplicativo.\n",
"\n",
"Para lidar com eventos de tempo, as interfaces gr\u00e1ficas implementam um recurso chamado temporizador (*timer*) que evoca a fun\u00e7\u00e3o callback depois de um certo tempo programado.\n",
"\n",
"PyGTK\n",
"-----\n",
"O GTK+ (GIMP Toolkit) \u00e9 uma biblioteca Open Source escrita em linguagem C. Originalmente concebida para ser usada pelo [GIMP](http://www.gimp.org/), \u00e9 compat\u00edvel com as plataformas mais utilizadas atualmente e rica em recursos, entre eles, um construtor de interfaces chamado Glade.\n",
"\n",
"Interface do Glade:\n",
"\n",
"![Interface do Glade](files/glade.png)\n",
"\n",
"O GTK+ \u00e9 usado pelo [GNOME](http://www.gnome.org/) (ambiente desktop Open Source) e por diversos aplicativos, como os portes do Mozilla Firefox e do BrOffice.org para sistemas UNIX. O GTK+ pode ser usado no Python atrav\u00e9s do pacote [PyGTK](http://www.pygtk.org/). Os portes das bibliotecas para Windows podem ser encontrados em:\n",
"\n",
"+ PyGTK: http://ftp.gnome.org/pub/gnome/binaries/win32/pygtk/\n",
"+ PyGObject: http://ftp.gnome.org/pub/gnome/binaries/win32/pygobject/\n",
"+ PyCairo: http://ftp.gnome.org/pub/gnome/binaries/win32/pycairo/\n",
"\n",
"Embora seja poss\u00edvel criar interfaces inteiramente usando c\u00f3digo, \u00e9 mais produtivo construir a interface em um software apropriado. O Glade gera arquivos XML com extens\u00e3o \u201c.glade\u201d, que podem ser lidos por programas que usam GTK+, automatizando o processo de criar interfaces gr\u00e1ficas.\n",
"\n",
"Roteiro b\u00e1sico para construir uma interface:\n",
"\n",
"No Glade:\n",
"\n",
"+ Crie uma janela usando algum dos modelos dispon\u00edveis em \u201cN\u00edveis Superiores\u201d.\n",
"+ Crie containers para armazenar os controles.\n",
"+ Crie os controles.\n",
"+ Crie os manipuladores para os sinais necess\u00e1rios.\n",
"+ Salve o arquivo com a extens\u00e3o \u201c.glade\u201d.\n",
"\n",
"No Python:\n",
"\n",
"+ Importe os pacotes necess\u00e1rios.\n",
"+ Use o GTK para interpretar o arquivo XML do Glade.\n",
"+ Crie rotinas para serem usadas como fun\u00e7\u00f5es *callback*.\n",
"+ Associe as rotinas com os manipuladores correspondentes que foram criados no Glade, atrav\u00e9s do m\u00e9todo `signal_autoconnect()`.\n",
"+ Ative o la\u00e7o para processar eventos com `gtk.main()`.\n",
"\n",
"Exemplo (rel\u00f3gio):\n",
"\n",
"No Glade:\n",
"\n",
"+ Clique em \u201cjanela\u201d em \u201cN\u00edveis Superiores\u201d.\n",
"+ Nas propriedades da janela:\n",
" + Mude \u201cNome\u201d para \u201cmain\u201d em \u201cGeral\u201d.\n",
" + Mude \u201cRedimension\u00e1vel\u201d para \u201cSim\u201d.\n",
" + Mude \u201cPosi\u00e7\u00e3o da janela\u201d para \u201cCentralizar\u201d.\n",
" + Mude \u201cVis\u00edvel\u201d para \u201cSim\u201d em \u201cComum\u201d.\n",
" + Mude o manipulador para \u201con_main_destroy\u201d do sinal \u201cdestroy\u201d de \u201cGtkObject\u201d em \u201cSinais\u201d.\n",
"+ Clique em \u201cCaixa vertical\u201d em \u201cContainers\u201d, depois clique dentro da janela e escolha o n\u00famero de itens igual a 3.\n",
"+ Clique em \u201cBarra de menu\u201d em \u201cContainers\u201d, depois clique dentro do espa\u00e7o vazio superior e delete os itens \u201cEditar\u201d e \u201cVer\u201d.\n",
"+ Clique em \u201cBarra de status\u201d em \u201cControle e Exibi\u00e7\u00e3o\u201d e depois clique dentro do espa\u00e7o vazio inferior.\n",
"+ Mude o nome da barra de status para \u201csts_data\u201d em \u201cGeral\u201d.\n",
"+ Clique em \u201cR\u00f3tulo\u201d em \u201cControle e Exibi\u00e7\u00e3o\u201d e depois clique dentro do espa\u00e7o vazio central.\n",
"+ Nas propriedades do r\u00f3tulo, mude \u201cNome\u201d para \u201clbl_hora\u201d e \u201cR\u00f3tulo\u201d para vazio em \u201cGeral\u201d, \u201cSolicita\u00e7\u00e3o de largura\u201d para \u201c300\u201d e \u201cSolicita\u00e7\u00e3o de altura\u201d para \u201c150\u201d em \u201cComum\u201d.\n",
"+ No \u201cInspetor\u201d (lista em forma de \u00e1rvore com todos itens), delete:\n",
" + \u201cimagemenuitem1\u201d.\n",
" + \u201cimagemenuitem2\u201d.\n",
" + \u201cimagemenuitem3\u201d.\n",
" + \u201cimagemenuitem4\u201d.\n",
" + \u201cseparatormenuitem1\u201d.\n",
"+ No \u201cInspetor\u201d:\n",
" + localize \u201cimagemenuitem5\u201d e mude o manipulador em \u201cSinais\u201d do sinal \u201cactivate\u201d para \u201con_imagemenuitem5_activate\u201d de \u201cGtkMenuItem\u201d.\n",
" + localize \u201cimagemenuitem10\u201d e mude o manipulador em \u201cSinais\u201d do sinal \u201cactivate\u201d para \u201con_imagemenuitem10_activate\u201d de \u201cGtkMenuItem\u201d.\n",
"+ Salve o arquivo como \u201crelogio.glade\u201d.\n",
"\n",
"Janela principal do rel\u00f3gio:\n",
"\n",
"![Janela principal do relogio](files/relogio_glade.png)\n",
"\n",
"C\u00f3digo em Python:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"\"\"\"\n",
"Um rel\u00f3gio com GTK.\n",
"\"\"\"\n",
"\n",
"import datetime\n",
"\n",
"# GTK e outros m\u00f3dulos associados\n",
"import gtk\n",
"import gtk.glade\n",
"import gobject\n",
"import pango\n",
"\n",
"\n",
"class Relogio(object):\n",
" \"\"\"\n",
" Implementa a janela principal do programa.\n",
" \"\"\"\n",
"\n",
" def __init__(self):\n",
" \"\"\"\n",
" Inicializa a classe.\n",
" \"\"\"\n",
"\n",
" # Carrega a interface\n",
" self.tree = gtk.glade.XML('relogio.glade', 'main')\n",
"\n",
" # Liga os eventos\n",
" callbacks = {\n",
" 'on_main_destroy': self.on_main_destroy,\n",
" 'on_imagemenuitem5_activate': self.on_main_destroy,\n",
" 'on_imagemenuitem10_activate': self.on_imagemenuitem10_activate\n",
" }\n",
"\n",
" self.tree.signal_autoconnect(callbacks)\n",
"\n",
" # Coloca um t\u00edtulo na janela\n",
" self.tree.get_widget('main').set_title('Rel\u00f3gio')\n",
"\n",
" # O r\u00f3tulo que reber\u00e1 a hora\n",
" self.hora = self.tree.get_widget('lbl_hora')\n",
"\n",
" # A barra de status que reber\u00e1 a data\n",
" self.data = self.tree.get_widget('sts_data')\n",
" print dir(self.data)\n",
"\n",
" # Muda a fonte do r\u00f3tulo\n",
" self.hora.modify_font(pango.FontDescription('verdana 28'))\n",
"\n",
" # Um temporizador para manter a hora atualizada\n",
" self.timer = gobject.timeout_add(1000, self.on_timer)\n",
"\n",
" def on_imagemenuitem10_activate(self, widget):\n",
" \"\"\"\n",
" Cria a janela de \"Sobre\".\n",
" \"\"\"\n",
"\n",
" # Caixa de dialogo\n",
" dialog = gtk.MessageDialog(parent=self.tree.get_widget('main'),\n",
" flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,\n",
" type=gtk.MESSAGE_OTHER, buttons=gtk.BUTTONS_OK,\n",
" message_format='Primeiro exemplo usando GTK.')\n",
"\n",
" dialog.set_title('Sobre')\n",
" dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)\n",
"\n",
" # Exibe a caixa\n",
" dialog.run()\n",
" dialog.destroy()\n",
" return\n",
"\n",
" def on_timer(self):\n",
" \"\"\"\n",
" Rotina para o temporizador.\n",
" \"\"\"\n",
"\n",
" # Pega a hora do sistema\n",
" hora = datetime.datetime.now().time().isoformat().split('.')[0]\n",
"\n",
" # Muda o texto do r\u00f3tulo\n",
" self.hora.set_text(hora)\n",
"\n",
" # Pega a data do sistema em formato ISO\n",
" data = datetime.datetime.now().date().isoformat()\n",
" data = 'Data: ' + '/'.join(data.split('-')[::-1])\n",
"\n",
" # Coloca a data na barra de status\n",
" self.data.push(0, data)\n",
"\n",
" # Verdadeiro faz com que o temporizador rode de novo\n",
" return True\n",
"\n",
" def on_main_destroy(self, widget):\n",
" \"\"\"\n",
" Termina o programa.\n",
" \"\"\"\n",
"\n",
" raise SystemExit\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
"\n",
" # Inicia a GUI\n",
" relogio = Relogio()\n",
" gtk.main()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Arquivo \"relogio.glade\":"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"\n",
"\n",
"\n",
"\n",
" \n",
" True\n",
" False\n",
" GTK_WIN_POS_CENTER\n",
" \n",
" \n",
" \n",
" True\n",
" \n",
" \n",
" \n",
" False\n",
" \n",
" \n",
" \n",
" \n",
" 300\n",
" 150\n",
" True\n",
" 5\n",
" 5\n",
" \n",
" \n",
" 1\n",
" \n",
" \n",
" \n",
" \n",
" True\n",
" 2\n",
" \n",
" \n",
" False\n",
" 2\n",
" \n",
" \n",
" \n",
" \n",
" \n",
""
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Exemplo (rodando programas):\n",
"\n",
"No Glade:\n",
"\n",
"+ Crie uma janela com o nome \u201cmain\u201d com o manipulador \u201con_main_destroy\u201d para o sinal \u201cdestroy\u201d.\n",
"+ Crie um *container* fixo para receber os controles.\n",
"+ Crie uma caixa de texto chamada \u201cntr_cmd\u201d. Esta caixa receber\u00e1 comandos para serem executados.\n",
"+ Crie um bot\u00e3o de verifica\u00e7\u00e3o chamado \u201cchk_shell\u201d, com o texto \u201cJanela de texto\u201d. Se o bot\u00e3o estiver marcado, o comando ser\u00e1 executado em uma janela de texto.\n",
"+ Crie um bot\u00e3o chamado \u201cbtn_rodar\u201d com o manipulador \u201con_btn_fechar_clicked\u201d para o sinal \u201cclicked\u201d. Quando clicado, o comando da caixa de texto \u00e9 executado.\n",
"+ Crie um bot\u00e3o chamado \u201cbtn_fechar\u201d com o manipulador \u201con_btn_fechar_clicked\u201d para o sinal \u201cclicked\u201d. Quando clicado, o programa termina.\n",
"\n",
"Janela principal:\n",
"\n",
"![Janela principal](files/janela_glade.png)\n",
"\n",
"C\u00f3digo em Python:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"\"\"\"\n",
"Rodando programas com GTK.\n",
"\"\"\"\n",
"\n",
"import subprocess\n",
"\n",
"import gtk\n",
"import gtk.glade\n",
"import gobject\n",
"import pango\n",
"\n",
"\n",
"class Exec(object):\n",
" \"\"\"\n",
" Janela principal.\n",
" \"\"\"\n",
"\n",
" def __init__(self):\n",
" \"\"\"\n",
" Inicializa a classe.\n",
" \"\"\"\n",
"\n",
" # Carrega a interface\n",
" self.tree = gtk.glade.XML('cmd.glade', 'main')\n",
"\n",
" # Liga os eventos\n",
" callbacks = {\n",
" 'on_main_destroy': self.on_main_destroy,\n",
" 'on_btn_fechar_clicked': self.on_main_destroy,\n",
" 'on_btn_rodar_clicked': self.on_btn_rodar_clicked\n",
" }\n",
"\n",
" self.tree.signal_autoconnect(callbacks)\n",
"\n",
" def on_btn_rodar_clicked(self, widget):\n",
" \"\"\"\n",
" Roda o comando.\n",
" \"\"\"\n",
"\n",
" ntr_cmd = self.tree.get_widget('ntr_cmd')\n",
" chk_shell = self.tree.get_widget('chk_shell')\n",
"\n",
" cmd = ntr_cmd.get_text()\n",
" if cmd:\n",
" # chk_shell.state ser\u00e1 1 se chk_shell estiver marcado\n",
" if chk_shell.state:\n",
" cmd = 'cmd start cmd /c ' + cmd\n",
" subprocess.Popen(args=cmd)\n",
"\n",
" else:\n",
" # Caixa de dialogo\n",
" dialog = gtk.MessageDialog(parent=self.tree.get_widget('main'),\n",
" flags=gtk.DIALOG_MODAL |\n",
" gtk.DIALOG_DESTROY_WITH_PARENT,\n",
" type=gtk.MESSAGE_OTHER, buttons=gtk.BUTTONS_OK,\n",
" message_format='Digite um comando.')\n",
"\n",
" dialog.set_title('Mensagem')\n",
" dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)\n",
"\n",
" # Exibe a caixa\n",
" dialog.run()\n",
" dialog.destroy()\n",
"\n",
" return True\n",
"\n",
" def on_main_destroy(self, widget):\n",
" \"\"\"\n",
" Termina o programa.\n",
" \"\"\"\n",
"\n",
" raise SystemExit\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
"\n",
" # Inicia a GUI\n",
" exe = Exec()\n",
" gtk.main()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"O arquivo \u201ccmd.glade\u201d:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"\n",
"\n",
"\n",
"\n",
" \n",
" 380\n",
" 100\n",
" True\n",
" Entre com um comando...\n",
" False\n",
" True\n",
" GTK_WIN_POS_CENTER\n",
" \n",
" \n",
" \n",
" 380\n",
" 100\n",
" True\n",
" \n",
" \n",
" 100\n",
" 29\n",
" True\n",
" True\n",
" True\n",
" _Rodar\n",
" True\n",
" 0\n",
" \n",
" \n",
" \n",
" 167\n",
" 61\n",
" \n",
" \n",
" \n",
" \n",
" 100\n",
" 29\n",
" True\n",
" True\n",
" True\n",
" _Fechar\n",
" True\n",
" 0\n",
" \n",
" \n",
" \n",
" 272\n",
" 61\n",
" \n",
" \n",
" \n",
" \n",
" 365\n",
" 29\n",
" True\n",
" True\n",
" \n",
" \n",
" 9\n",
" 14\n",
" \n",
" \n",
" \n",
" \n",
" 136\n",
" 29\n",
" True\n",
" True\n",
" _Janela de texto\n",
" True\n",
" 0\n",
" True\n",
" \n",
" \n",
" 11\n",
" 59\n",
" \n",
" \n",
" \n",
" \n",
" \n",
""
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Al\u00e9m do Glade, tamb\u00e9m existe o [Gaspacho](http://gazpacho.sicem.biz/), outro construtor de interfaces que tamb\u00e9m gera arquivos XML no padr\u00e3o do Glade.\n",
"\n",
"wxPython\n",
"--------\n",
"O pacote [wxPython](http://www.wxpython.org/) \u00e9 basicamente um *wrapper* para o *toolkit* (conjunto de ferramentas e bibliotecas) wxWidgets, desenvolvido em C++. Principais caracter\u00edsticas:\n",
"\n",
"+ Multi-plataforma.\n",
"+ *Look & feel* (apar\u00eancia e comportamento) nativo, ou seja, coerente com o ambiente em que est\u00e1 sendo executado.\n",
"+ Grande cole\u00e7\u00e3o de componentes prontos.\n",
"+ Comunidade bastante ativa.\n",
"\n",
"A forma geral de funcionamento \u00e9 similar ao GTK+: o *framework* controla a intera\u00e7\u00e3o com o usu\u00e1rio atrav\u00e9s de um la\u00e7o que detecta eventos e ativa as rotinas correspondentes.\n",
"\n",
"A maneira mais usual de implementar uma interface gr\u00e1fica atrav\u00e9s do wxPython consiste em:\n",
"\n",
"+ Importar o pacote *wx*.\n",
"+ Criar uma classe de janela atrav\u00e9s de heran\u00e7a. Na inicializa\u00e7\u00e3o da classe podem ser definidos controles e *containers* que fazem parte da janela e as associa\u00e7\u00f5es entre os eventos com as fun\u00e7\u00f5es *callback* correspondentes, que geralmente s\u00e3o m\u00e9todos da pr\u00f3pria classe.\n",
"+ Criar um objeto \u201caplica\u00e7\u00e3o\u201d usando a classe *App* do wxPython.\n",
"+ Criar um objeto a partir da classe de janela.\n",
"+ Iniciar o *loop* de tratamento de eventos da aplica\u00e7\u00e3o.\n",
"\n",
"Exemplo (caixa de texto):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# importa wxPython\n",
"import wx\n",
"\n",
"\n",
"class Main(wx.Frame):\n",
" \"\"\"\n",
" Classe que define a janela principal do programa.\n",
" \"\"\"\n",
" def __init__(self, parent, id, title):\n",
" \"\"\"\n",
" Inicializa a classe.\n",
" \"\"\"\n",
"\n",
" # Define a janela usando o __init__ da classe m\u00e3e\n",
" wx.Frame.__init__(self, parent, id, title, size=(600, 400))\n",
"\n",
" # Cria uma caixa de texto\n",
" self.text = wx.TextCtrl(self, style=wx.TE_MULTILINE)\n",
"\n",
" # Pega o fonte do programa (decodificado para latin1)\n",
" font = file(__file__, 'rb').read().decode('latin1')\n",
" \n",
" # Carrega o fonte do programa na caixa de texto\n",
" self.text.SetLabel(font)\n",
"\n",
" # Mostra a janela\n",
" self.Show(True)\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
"\n",
" # Cria um objeto \"aplica\u00e7\u00e3o\" do wxPython\n",
" app = wx.App()\n",
"\n",
" # Cria um objeto \"janela\" a partir da classe\n",
" frame = Main(None, wx.ID_ANY, 'Fonte')\n",
"\n",
" # Inicia o loop de tratamento de eventos\n",
" app.MainLoop()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Janela do exemplo:\n",
" \n",
"![Janela do wx](files/janela_wx.png)\n",
"\n",
"Exemplo (temporizador):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import wx\n",
"import time\n",
"\n",
"\n",
"class Main(wx.Frame):\n",
"\n",
" def __init__(self, parent, id, title):\n",
" wx.Frame.__init__(self, parent, id, title, size=(150, 80))\n",
" clock = time.asctime().split()[3]\n",
"\n",
" # Cria um r\u00f3tulo de texto\n",
" self.control = wx.StaticText(self, label=clock)\n",
"\n",
" # Muda a fonte\n",
" self.control.SetFont(wx.Font(22, wx.SWISS, wx.NORMAL, wx.BOLD))\n",
"\n",
" # Cria um timer\n",
" TIMER_ID = 100\n",
" self.timer = wx.Timer(self, TIMER_ID)\n",
"\n",
" # Inicia o timer\n",
" self.timer.Start(1000)\n",
"\n",
" # Associa os m\u00e9todos com os eventos\n",
" wx.EVT_TIMER(self, TIMER_ID, self.on_timer)\n",
" wx.EVT_CLOSE(self, self.on_close)\n",
" self.Show(True)\n",
"\n",
" def on_timer(self, event):\n",
" \n",
" # Atualiza o rel\u00f3gio\n",
" clock = time.asctime().split()[3]\n",
" self.control.SetLabel(clock)\n",
"\n",
" def on_close(self, event):\n",
" \n",
" # Para o timer\n",
" self.timer.Stop()\n",
" self.Destroy()\n",
"\n",
"\n",
"app = wx.App()\n",
"Main(None, wx.ID_ANY, 'Rel\u00f3gio')\n",
"app.MainLoop()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Interface:\n",
"\n",
"![Temporizador](files/temporizador.png)\n",
"\n",
"Exemplo (barra de menus):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import wx\n",
"\n",
"# Identificadores para as op\u00e7\u00f5es do menu\n",
"ID_FILE_OPEN = wx.NewId()\n",
"ID_FILE_SAVE = wx.NewId()\n",
"ID_FILE_EXIT = wx.NewId()\n",
"ID_HELP_ABOUT = wx.NewId()\n",
"\n",
"class Main(wx.Frame):\n",
" def __init__(self, parent, id, title):\n",
"\n",
" wx.Frame.__init__(self, parent, id, title)\n",
" # Cria o menu arquivo\n",
" filemenu = wx.Menu()\n",
"\n",
" # Cria as op\u00e7\u00f5es\n",
" filemenu.Append(ID_FILE_OPEN, 'Abrir arquivo...')\n",
" filemenu.Append(ID_FILE_SAVE, 'Salvar')\n",
" filemenu.AppendSeparator()\n",
" filemenu.Append(ID_FILE_EXIT, 'Sair')\n",
"\n",
" # Cria o menu ajuda\n",
" helpmenu = wx.Menu()\n",
" helpmenu.Append(ID_HELP_ABOUT, 'Sobre...')\n",
"\n",
" # Cria o menu\n",
" menubar = wx.MenuBar()\n",
" menubar.Append(filemenu, 'Arquivo')\n",
" menubar.Append(helpmenu, 'Ajuda')\n",
" self.SetMenuBar(menubar)\n",
"\n",
" # Associa m\u00e9todos aos eventos de menu\n",
" wx.EVT_MENU(self, ID_FILE_OPEN, self.on_open)\n",
" wx.EVT_MENU(self, ID_FILE_SAVE, self.on_save)\n",
" wx.EVT_MENU(self, ID_FILE_EXIT, self.on_exit)\n",
" wx.EVT_MENU(self, ID_HELP_ABOUT, self.about)\n",
"\n",
" # Cria uma caixa de texto\n",
" self.control = wx.TextCtrl(self, 1,\n",
" style=wx.TE_MULTILINE)\n",
" self.fn = ''\n",
"\n",
" def on_open(self, evt):\n",
"\n",
" # Abre uma caixa de dialogo escolher arquivo\n",
" dialog = wx.FileDialog(None, style=wx.OPEN)\n",
" d = dialog.ShowModal()\n",
" if d == wx.ID_OK:\n",
"\n",
" # Pega o arquivo escolhido\n",
" self.fn = dialog.GetPath()\n",
"\n",
" # Muda o t\u00edtulo da janela\n",
" self.SetTitle(self.fn)\n",
"\n",
" # Carrega o texto na caixa de texto\n",
" self.control.SetLabel(file(self.fn, 'rb').read())\n",
" dialog.Destroy()\n",
"\n",
" def on_save(self, evt):\n",
"\n",
" # Salva o texto na caixa de texto\n",
" if self.fn:\n",
" file(self.fn, 'wb').write(self.control.GetLabel())\n",
"\n",
" def on_exit(self, evt):\n",
" # Fecha a janela principal\n",
" self.Close(True)\n",
"\n",
" def about(self, evt):\n",
" dlg = wx.MessageDialog(self,\n",
" 'Exemplo wxPython', 'Sobre...',\n",
" wx.OK | wx.ICON_INFORMATION)\n",
" dlg.ShowModal()\n",
" dlg.Destroy()\n",
"\n",
"\n",
"app = wx.App()\n",
"frame = Main(None , wx.ID_ANY, 'Isto \u00e9 quase um editor...')\n",
"frame.Show(True)\n",
"app.MainLoop()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Janela principal:\n",
"\n",
"![Exemplo de menu com wx](files/menu_wx.png)\n",
"\n",
"Exemplo (caixa de mensagem):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import wx\n",
"\n",
"\n",
"class Main(wx.Frame):\n",
" \n",
" def __init__(self, parent, id, title):\n",
"\n",
" # Cria janela\n",
" wx.Frame.__init__(self, parent, id, title, size=(300, 150))\n",
" self.Centre()\n",
" self.Show(True)\n",
"\n",
" # Cria um texto est\u00e1tico\n",
" self.text = wx.StaticText(self, label='Entre com uma express\u00e3o:',\n",
" pos=(10, 10))\n",
"\n",
" # Cria uma caixa de edi\u00e7\u00e3o de texto\n",
" self.edit = wx.TextCtrl(self, size=(250, -1), pos=(10, 30))\n",
"\n",
" # Cria um bot\u00e3o\n",
" self.button = wx.Button(self, label='ok', pos=(10, 60))\n",
"\n",
" # Conecta um m\u00e9todo ao bot\u00e3o\n",
" self.button.Bind(wx.EVT_BUTTON, self.on_button)\n",
"\n",
" def on_button(self, event):\n",
"\n",
" # Pega o valor da caixa de texto\n",
" txt = self.edit.GetValue()\n",
" \n",
" # Tenta resolver e apresentar a express\u00e3o\n",
" try:\n",
" wx.MessageBox(txt + ' = ' + str(eval(txt)), 'Resultado')\n",
" \n",
" # Se algo inesperado ocorrer\n",
" except:\n",
" wx.MessageBox('Express\u00e3o inv\u00e1lida', 'Erro')\n",
"\n",
"\n",
"app = wx.App()\n",
"Main(None, -1, 'Teste de MessageBox')\n",
"app.MainLoop()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Janela principal e caixa de mensagem:\n",
"\n",
"![Janela principal e caixa de mensagem](files/mensagem.png)\n",
"\n",
"O wxPython oferece uma variedade enorme de controles prontos, que ser no programa de demonstra\u00e7\u00e3o que \u00e9 distribu\u00eddo junto com a documenta\u00e7\u00e3o e os exemplos.\n",
"\n",
"PyQt\n",
"----\n",
"[Qt](http://qt.digia.com/) \u00e9 um toolkit desenvolvido em C++ e \u00e9 utilizado por diversos programas, incluindo o ambiente de desktop gr\u00e1fico KDE e seus aplicativos. Embora o Qt seja mais usado para a cria\u00e7\u00e3o de aplicativos GUI, ele tamb\u00e9m inclui bibliotecas com outras funcionalidades, como acesso a banco de dados, comunica\u00e7\u00e3o de rede e controle de *threads*, entre outras. [PyQt](http://www.riverbankcomputing.co.uk/software/pyqt/intro) \u00e9 um *binding* que permite o uso do Qt no Python, dispon\u00edvel sob a licen\u00e7a GPL.\n",
"\n",
"A Qt na vers\u00e3o 4 possui dois m\u00f3dulos principais, chamados *QtGui*, que define as rotinas de interface, e *QtCore*, que define estruturas essenciais para o funcionamento do *toolkit*, como, por exemplo, os sinais (eventos).\n",
"\n",
"Exemplo:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import sys\n",
"from PyQt4 import QtGui, QtCore\n",
"\n",
"\n",
"class Main(QtGui.QWidget):\n",
" \"\"\"\n",
" Janela principal\n",
" \"\"\"\n",
"\n",
" def __init__(self, parent=None):\n",
"\n",
" QtGui.QWidget.__init__(self, parent)\n",
"\n",
" # Muda a geometria da janela\n",
" self.setGeometry(200, 200, 200, 100)\n",
"\n",
" # Muda o t\u00edtulo\n",
" self.setWindowTitle('Teste')\n",
"\n",
" # Cria um bot\u00e3o\n",
" quit = QtGui.QPushButton('Fechar', self)\n",
" quit.setGeometry(10, 10, 60, 35)\n",
"\n",
" # Conecta o sinal gerado pelo bot\u00e3o com a fun\u00e7\u00e3o\n",
" # que encerra o programa\n",
" self.connect(quit, QtCore.SIGNAL('clicked()'),\n",
" QtGui.qApp, QtCore.SLOT('quit()'))\n",
"\n",
"\n",
"# Cria um objeto \"aplica\u00e7\u00e3o Qt\", que trata os eventos\n",
"app = QtGui.QApplication(sys.argv)\n",
"\n",
"# Cria a janela principal\n",
"qb = Main()\n",
"qb.show()\n",
"\n",
"# Inicia a \"aplica\u00e7\u00e3o Qt\"\n",
"sys.exit(app.exec_())"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Janela principal:\n",
"\n",
"![Janela Qt](files/janela_qt.png)\n",
"\n",
"Um dos maiores atrativos do PyQt \u00e9 o GUI Builder (ferramenta para a constru\u00e7\u00e3o de interfaces) Qt Designer. Os arquivos XML gerados pelo Qt Designer (com a extens\u00e3o .ui) podem ser convertidos em m\u00f3dulos Python atrav\u00e9s do utilit\u00e1rio pyuic.\n",
"\n",
"![Qt Designer](files/qt_designer.png)\n",
"\n",
"Para gerar o m\u00f3dulo Python a partir do arquivo criado no Qt Designer:\n",
"\n",
" pyuic dialog.ui -o dialog.py\n",
"\n",
"No qual `dialog.ui` \u00e9 o arquivo de interface e `dialog.py` \u00e9 o m\u00f3dulo.\n",
"\n",
"Exemplo de arquivo gerado pelo Qt Designer (dialog.ui):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"\n",
" Dialog\n",
" \n",
" \n",
" \n",
" 0\n",
" 0\n",
" 116\n",
" 108\n",
" \n",
" \n",
" \n",
" Dialog\n",
" \n",
" \n",
" \n",
" \n",
" 20\n",
" 20\n",
" 75\n",
" 23\n",
" \n",
" \n",
" \n",
" Mensagem\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" 20\n",
" 60\n",
" 75\n",
" 23\n",
" \n",
" \n",
" \n",
" Fechar\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" fim\n",
" clicked()\n",
" Dialog\n",
" close()\n",
" \n",
" \n",
" 57\n",
" 71\n",
" \n",
" \n",
" 57\n",
" 53\n",
" \n",
" \n",
" \n",
" \n",
""
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"O arquivo de interface define uma janela, da classe *QDialog*, chamada \u201cDialog\u201d, com dois bot\u00f5es, da classe *QPushButton*, chamados \u201cmsg\u201d e \u201cfim\u201d.\n",
"\n",
"Exemplo usando o m\u00f3dulo criado pelo Qt Designer:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import sys\n",
"import time\n",
"from PyQt4 import QtCore, QtGui\n",
"\n",
"# M\u00f3dulo gerado pelo pyuic\n",
"from dialog import Ui_Dialog\n",
"\n",
"\n",
"class Main(QtGui.QMainWindow):\n",
" \"\"\"\n",
" Janela principal\n",
" \"\"\"\n",
" def __init__(self, parent=None):\n",
" \"\"\"\n",
" Inicializa\u00e7\u00e3o da janela\n",
" \"\"\"\n",
" QtGui.QWidget.__init__(self, parent)\n",
"\n",
" # Cria um objeto a partir da interface gerada pelo pyuic\n",
" self.ui = Ui_Dialog()\n",
" self.ui.setupUi(self)\n",
"\n",
" # Conecta o m\u00e9todo ao bot\u00e3o que foi definido atrav\u00e9s do Qt Designer\n",
" self.connect(self.ui.msg, QtCore.SIGNAL('clicked()'),\n",
" self.show_msg)\n",
"\n",
" def show_msg(self):\n",
" \"\"\"\n",
" M\u00e9todo que evoca a caixa de mensagem\n",
" \"\"\"\n",
" reply = QtGui.QMessageBox.question(self, 'Messagem',\n",
" 'Hora: ' + time.asctime().split()[3],\n",
" QtGui.QMessageBox.Ok)\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
"\n",
" app = QtGui.QApplication(sys.argv)\n",
" myapp = Main()\n",
" myapp.show()\n",
" sys.exit(app.exec_())"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Janela principal e caixa de mensagem:\n",
"\n",
"![Janela principal e caixa de mensagem](files/mensagem_qt.png)\n",
"\n",
"Tamb\u00e9m est\u00e1 dispon\u00edvel um *binding* LGPL similar ao PyQt, chamado [PySide](http://www.pyside.org/).\n",
"\n",
"Outras funcionalidades associadas a interface gr\u00e1fica podem ser obtidas usando outros m\u00f3dulos, como o [pySystray](http://datavibe.net/~essiene/pysystray/), que implementa a funcionalidade que permite que o aplicativo use a bandeja de sistema no Windows."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"\n",
""
],
"output_type": "pyout",
"prompt_number": 1,
"text": [
""
]
}
],
"prompt_number": 1
}
],
"metadata": {}
}
]
}