Python para Desenvolvedores

2ª edição, revisada e ampliada

Capítulo 33: Interface Gráfica


As Interfaces Gráficas com Usuário (GUI, Graphic User Interface) se popularizaram no ambiente desktop, devido à facilidade de uso e a produtividade. Existem hoje muitas bibliotecas disponíveis para a construção de aplicações GUI, tais como: GTK+, QT, TK e wxWidgets.

Arquitetura

Interfaces gráficas geralmente utilizam a metáfora do desktop, um espaço em duas dimensões, é que ocupado por janelas retangulares, que representam aplicativos, propriedades ou documentos.

Esquema de GUI

As janelas podem conter diversos tipos de controles (objetos utilizados para interagir com o usuário ou para apresentar informações) e containers (objetos que servem de repositório para coleções de outros objetos).

Na maior parte do tempo, a interface gráfica espera por eventos e responde de acordo. Os eventos podem ser resultado da interação do usuário, como cliques e arrastar de mouse ou digitação, ou ainda de eventos de sistema, como o relógio da máquina. A reação a um evento qualquer é definida através de funções callback (funções que são passadas como argumento para outras rotinas).

Controles mais usados:

  • Rótulo (label): retângulo que exibe texto.
  • Caixa de texto (text box): área de edição de texto.
  • Botão (button): área que pode ser ativada interativamente.
  • Botão de rádio (radio button): tipo especial de botão, com o qual são formados grupos aonde apenas um pode ser apertado de cada vez.
  • Botão de verificação (check button): botão que pode ser marcado e desmarcado.
  • Barras de rolagem (scroll bars): controles deslizantes horizontais ou verticais, usados para intervalos de valores numéricos.
  • Botão giratório (spin button): caixa de texto com dois botões com setas ao lado que incrementam e decrementam o número na caixa.
  • Barra de status (status bar): barra para exibição de mensagens, geralmente na parte inferior da janela.
  • Imagem (image): área para exibição de imagens.

Controles podem ter aceleradores (teclas de atalho) associados a eles.

Containers mais usados:

  • Barra de menu (menu bar): sistema de menus, geralmente na parte superior da janela.
  • Fixo (fixed): os objetos permanecem fixos nas mesmas posições.
  • Tabela (table): coleção de compartimentos para fixar os objetos, distribuídos em linhas e colunas.
  • Caixa horizontal (horizontal box): semelhante à tabela, porém apenas com uma linha.
  • Caixa vertical (vertical box): semelhante à tabela, porém apenas com uma coluna.
  • Caderno (notebook): várias áreas que podem ser visualizadas uma de cada vez quando selecionadas através de abas, geralmente na parte superior.
  • Barra de ferramentas (toolbar): área com botões para acesso rápido aos principais recursos do aplicativo.

Para lidar com eventos de tempo, as interfaces gráficas implementam um recurso chamado temporizador (timer) que evoca a função callback depois de um certo tempo programado.

PyGTK

O GTK+ (GIMP Toolkit) é uma biblioteca Open Source escrita em linguagem C. Originalmente concebida para ser usada pelo GIMP, é compatível com as plataformas mais utilizadas atualmente e rica em recursos, entre eles, um construtor de interfaces chamado Glade.

Interface do Glade:

Interface do Glade

O GTK+ é usado pelo GNOME (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és do pacote PyGTK. Os portes das bibliotecas para Windows podem ser encontrados em:

Embora seja possível criar interfaces inteiramente usando código, é mais produtivo construir a interface em um software apropriado. O Glade gera arquivos XML com extensão “.glade”, que podem ser lidos por programas que usam GTK+, automatizando o processo de criar interfaces gráficas.

Roteiro básico para construir uma interface:

No Glade:

  • Crie uma janela usando algum dos modelos disponíveis em “Níveis Superiores”.
  • Crie containers para armazenar os controles.
  • Crie os controles.
  • Crie os manipuladores para os sinais necessários.
  • Salve o arquivo com a extensão “.glade”.

No Python:

  • Importe os pacotes necessários.
  • Use o GTK para interpretar o arquivo XML do Glade.
  • Crie rotinas para serem usadas como funções callback.
  • Associe as rotinas com os manipuladores correspondentes que foram criados no Glade, através do método signal_autoconnect().
  • Ative o laço para processar eventos com gtk.main().

Exemplo (relógio):

No Glade:

  • Clique em “janela” em “Níveis Superiores”.
  • Nas propriedades da janela:
    • Mude “Nome” para “main” em “Geral”.
    • Mude “Redimensionável” para “Sim”.
    • Mude “Posição da janela” para “Centralizar”.
    • Mude “Visível” para “Sim” em “Comum”.
    • Mude o manipulador para “on_main_destroy” do sinal “destroy” de “GtkObject” em “Sinais”.
  • Clique em “Caixa vertical” em “Containers”, depois clique dentro da janela e escolha o número de itens igual a 3.
  • Clique em “Barra de menu” em “Containers”, depois clique dentro do espaço vazio superior e delete os itens “Editar” e “Ver”.
  • Clique em “Barra de status” em “Controle e Exibição” e depois clique dentro do espaço vazio inferior.
  • Mude o nome da barra de status para “sts_data” em “Geral”.
  • Clique em “Rótulo” em “Controle e Exibição” e depois clique dentro do espaço vazio central.
  • Nas propriedades do rótulo, mude “Nome” para “lbl_hora” e “Rótulo” para vazio em “Geral”, “Solicitação de largura” para “300” e “Solicitação de altura” para “150” em “Comum”.
  • No “Inspetor” (lista em forma de árvore com todos itens), delete:
    • “imagemenuitem1”.
    • “imagemenuitem2”.
    • “imagemenuitem3”.
    • “imagemenuitem4”.
    • “separatormenuitem1”.
  • No “Inspetor”:
    • localize “imagemenuitem5” e mude o manipulador em “Sinais” do sinal “activate” para “on_imagemenuitem5_activate” de “GtkMenuItem”.
    • localize “imagemenuitem10” e mude o manipulador em “Sinais” do sinal “activate” para “on_imagemenuitem10_activate” de “GtkMenuItem”.
  • Salve o arquivo como “relogio.glade”.

Janela principal do relógio:

Janela principal do relogio

Código em Python:

In [ ]:
"""
Um relógio com GTK.
"""

import datetime

# GTK e outros módulos associados
import gtk
import gtk.glade
import gobject
import pango


class Relogio(object):
    """
    Implementa a janela principal do programa.
    """

    def __init__(self):
        """
        Inicializa a classe.
        """

        # Carrega a interface
        self.tree = gtk.glade.XML('relogio.glade', 'main')

        # Liga os eventos
        callbacks = {
            'on_main_destroy': self.on_main_destroy,
            'on_imagemenuitem5_activate': self.on_main_destroy,
            'on_imagemenuitem10_activate': self.on_imagemenuitem10_activate
            }

        self.tree.signal_autoconnect(callbacks)

        # Coloca um título na janela
        self.tree.get_widget('main').set_title('Relógio')

        # O rótulo que reberá a hora
        self.hora = self.tree.get_widget('lbl_hora')

        # A barra de status que reberá a data
        self.data = self.tree.get_widget('sts_data')
        print dir(self.data)

        # Muda a fonte do rótulo
        self.hora.modify_font(pango.FontDescription('verdana 28'))

        # Um temporizador para manter a hora atualizada
        self.timer = gobject.timeout_add(1000, self.on_timer)

    def on_imagemenuitem10_activate(self, widget):
        """
        Cria a janela de "Sobre".
        """

        # Caixa de dialogo
        dialog = gtk.MessageDialog(parent=self.tree.get_widget('main'),
            flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            type=gtk.MESSAGE_OTHER, buttons=gtk.BUTTONS_OK,
            message_format='Primeiro exemplo usando GTK.')

        dialog.set_title('Sobre')
        dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)

        # Exibe a caixa
        dialog.run()
        dialog.destroy()
        return

    def on_timer(self):
        """
        Rotina para o temporizador.
        """

        # Pega a hora do sistema
        hora = datetime.datetime.now().time().isoformat().split('.')[0]

        # Muda o texto do rótulo
        self.hora.set_text(hora)

        # Pega a data do sistema em formato ISO
        data = datetime.datetime.now().date().isoformat()
        data = 'Data: ' + '/'.join(data.split('-')[::-1])

        # Coloca a data na barra de status
        self.data.push(0, data)

        # Verdadeiro faz com que o temporizador rode de novo
        return True

    def on_main_destroy(self, widget):
        """
        Termina o programa.
        """

        raise SystemExit


if __name__ == "__main__":

    # Inicia a GUI
    relogio = Relogio()
    gtk.main()

Arquivo "relogio.glade":

In [ ]:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.3 on Sat May 03 14:06:18 2008 -->
<glade-interface>
  <widget class="GtkWindow" id="main">
    <property name="visible">True</property>
    <property name="resizable">False</property>
    <property name="window_position">GTK_WIN_POS_CENTER</property>
    <signal name="destroy" handler="on_main_destroy"/>
    <child>
      <widget class="GtkVBox" id="vbox1">
        <property name="visible">True</property>
        <child>
          <widget class="GtkMenuBar" id="menubar1">
            <property name="visible">True</property>
            <child>
              <widget class="GtkMenuItem" id="menuitem1">
                <property name="visible">True</property>
                <property name="label" translatable="yes">_Arquivo</property>
                <property name="use_underline">True</property>
                <child>
                  <widget class="GtkMenu" id="menu1">
                    <property name="visible">True</property>
                    <child>
                      <widget class="GtkImageMenuItem" id="imagemenuitem5">
                        <property name="visible">True</property>
                        <property name="label" translatable="yes">gtk-quit</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                        <signal name="activate" handler="on_imagemenuitem5_activate"/>
                      </widget>
                    </child>
                  </widget>
                </child>
              </widget>
            </child>
            <child>
              <widget class="GtkMenuItem" id="menuitem4">
                <property name="visible">True</property>
                <property name="label" translatable="yes">Aj_uda</property>
                <property name="use_underline">True</property>
                <child>
                  <widget class="GtkMenu" id="menu3">
                    <property name="visible">True</property>
                    <child>
                      <widget class="GtkImageMenuItem" id="imagemenuitem10">
                        <property name="visible">True</property>
                        <property name="label" translatable="yes">gtk-about</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                        <signal name="activate" handler="on_imagemenuitem10_activate"/>
                      </widget>
                    </child>
                  </widget>
                </child>
              </widget>
            </child>
          </widget>
          <packing>
            <property name="expand">False</property>
          </packing>
        </child>
        <child>
          <widget class="GtkLabel" id="lbl_hora">
            <property name="width_request">300</property>
            <property name="height_request">150</property>
            <property name="visible">True</property>
            <property name="xpad">5</property>
            <property name="ypad">5</property>
          </widget>
          <packing>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <widget class="GtkStatusbar" id="sts_data">
            <property name="visible">True</property>
            <property name="spacing">2</property>
          </widget>
          <packing>
            <property name="expand">False</property>
            <property name="position">2</property>
          </packing>
        </child>
      </widget>
    </child>
  </widget>
</glade-interface>

Exemplo (rodando programas):

No Glade:

  • Crie uma janela com o nome “main” com o manipulador “on_main_destroy” para o sinal “destroy”.
  • Crie um container fixo para receber os controles.
  • Crie uma caixa de texto chamada “ntr_cmd”. Esta caixa receberá comandos para serem executados.
  • Crie um botão de verificação chamado “chk_shell”, com o texto “Janela de texto”. Se o botão estiver marcado, o comando será executado em uma janela de texto.
  • Crie um botão chamado “btn_rodar” com o manipulador “on_btn_fechar_clicked” para o sinal “clicked”. Quando clicado, o comando da caixa de texto é executado.
  • Crie um botão chamado “btn_fechar” com o manipulador “on_btn_fechar_clicked” para o sinal “clicked”. Quando clicado, o programa termina.

Janela principal:

Janela principal

Código em Python:

In [ ]:
"""
Rodando programas com GTK.
"""

import subprocess

import gtk
import gtk.glade
import gobject
import pango


class Exec(object):
    """
    Janela principal.
    """

    def __init__(self):
        """
        Inicializa a classe.
        """

        # Carrega a interface
        self.tree = gtk.glade.XML('cmd.glade', 'main')

        # Liga os eventos
        callbacks = {
            'on_main_destroy': self.on_main_destroy,
            'on_btn_fechar_clicked': self.on_main_destroy,
            'on_btn_rodar_clicked': self.on_btn_rodar_clicked
            }

        self.tree.signal_autoconnect(callbacks)

    def on_btn_rodar_clicked(self, widget):
        """
        Roda o comando.
        """

        ntr_cmd = self.tree.get_widget('ntr_cmd')
        chk_shell = self.tree.get_widget('chk_shell')

        cmd = ntr_cmd.get_text()
        if cmd:
            # chk_shell.state será 1 se chk_shell estiver marcado
            if chk_shell.state:
                cmd = 'cmd start cmd /c ' + cmd
            subprocess.Popen(args=cmd)

        else:
            # Caixa de dialogo
            dialog = gtk.MessageDialog(parent=self.tree.get_widget('main'),
                flags=gtk.DIALOG_MODAL |
                gtk.DIALOG_DESTROY_WITH_PARENT,
                type=gtk.MESSAGE_OTHER, buttons=gtk.BUTTONS_OK,
                message_format='Digite um comando.')

            dialog.set_title('Mensagem')
            dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)

            # Exibe a caixa
            dialog.run()
            dialog.destroy()

        return True

    def on_main_destroy(self, widget):
        """
        Termina o programa.
        """

        raise SystemExit


if __name__ == "__main__":

    # Inicia a GUI
    exe = Exec()
    gtk.main()

O arquivo “cmd.glade”:

In [ ]:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.3 on Tue May 27 23:44:03 2008 -->
<glade-interface>
  <widget class="GtkWindow" id="main">
    <property name="width_request">380</property>
    <property name="height_request">100</property>
    <property name="visible">True</property>
    <property name="title" translatable="yes">Entre com um comando...</property>
    <property name="resizable">False</property>
    <property name="modal">True</property>
    <property name="window_position">GTK_WIN_POS_CENTER</property>
    <signal name="destroy" handler="on_main_destroy"/>
    <child>
      <widget class="GtkFixed" id="fixed1">
        <property name="width_request">380</property>
        <property name="height_request">100</property>
        <property name="visible">True</property>
        <child>
          <widget class="GtkButton" id="btn_rodar">
            <property name="width_request">100</property>
            <property name="height_request">29</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="label" translatable="yes">_Rodar</property>
            <property name="use_underline">True</property>
            <property name="response_id">0</property>
            <signal name="clicked" handler="on_btn_rodar_clicked"/>
          </widget>
          <packing>
            <property name="x">167</property>
            <property name="y">61</property>
          </packing>
        </child>
        <child>
          <widget class="GtkButton" id="btn_fechar">
            <property name="width_request">100</property>
            <property name="height_request">29</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="label" translatable="yes">_Fechar</property>
            <property name="use_underline">True</property>
            <property name="response_id">0</property>
            <signal name="clicked" handler="on_btn_fechar_clicked"/>
          </widget>
          <packing>
            <property name="x">272</property>
            <property name="y">61</property>
          </packing>
        </child>
        <child>
          <widget class="GtkEntry" id="ntr_cmd">
            <property name="width_request">365</property>
            <property name="height_request">29</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
          </widget>
          <packing>
            <property name="x">9</property>
            <property name="y">14</property>
          </packing>
        </child>
        <child>
          <widget class="GtkCheckButton" id="chk_shell">
            <property name="width_request">136</property>
            <property name="height_request">29</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="label" translatable="yes">_Janela de texto</property>
            <property name="use_underline">True</property>
            <property name="response_id">0</property>
            <property name="draw_indicator">True</property>
          </widget>
          <packing>
            <property name="x">11</property>
            <property name="y">59</property>
          </packing>
        </child>
      </widget>
    </child>
  </widget>
</glade-interface>

Além do Glade, também existe o Gaspacho, outro construtor de interfaces que também gera arquivos XML no padrão do Glade.

wxPython

O pacote wxPython é basicamente um wrapper para o toolkit (conjunto de ferramentas e bibliotecas) wxWidgets, desenvolvido em C++. Principais características:

  • Multi-plataforma.
  • Look & feel (aparência e comportamento) nativo, ou seja, coerente com o ambiente em que está sendo executado.
  • Grande coleção de componentes prontos.
  • Comunidade bastante ativa.

A forma geral de funcionamento é similar ao GTK+: o framework controla a interação com o usuário através de um laço que detecta eventos e ativa as rotinas correspondentes.

A maneira mais usual de implementar uma interface gráfica através do wxPython consiste em:

  • Importar o pacote wx.
  • Criar uma classe de janela através de herança. Na inicialização da classe podem ser definidos controles e containers que fazem parte da janela e as associações entre os eventos com as funções callback correspondentes, que geralmente são métodos da própria classe.
  • Criar um objeto “aplicação” usando a classe App do wxPython.
  • Criar um objeto a partir da classe de janela.
  • Iniciar o loop de tratamento de eventos da aplicação.

Exemplo (caixa de texto):

In [ ]:
# importa wxPython
import wx


class Main(wx.Frame):
    """
    Classe que define a janela principal do programa.
    """
    def __init__(self, parent, id, title):
        """
        Inicializa a classe.
        """

        # Define a janela usando o __init__ da classe mãe
        wx.Frame.__init__(self, parent, id, title, size=(600, 400))

        # Cria uma caixa de texto
        self.text = wx.TextCtrl(self, style=wx.TE_MULTILINE)

        # Pega o fonte do programa (decodificado para latin1)
        font = file(__file__, 'rb').read().decode('latin1')
        
        # Carrega o fonte do programa na caixa de texto
        self.text.SetLabel(font)

        # Mostra a janela
        self.Show(True)


if __name__ == '__main__':

    # Cria um objeto "aplicação" do wxPython
    app = wx.App()

    # Cria um objeto "janela" a partir da classe
    frame = Main(None, wx.ID_ANY, 'Fonte')

    # Inicia o loop de tratamento de eventos
    app.MainLoop()

Janela do exemplo:

Janela do wx

Exemplo (temporizador):

In [ ]:
import wx
import time


class Main(wx.Frame):

    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(150, 80))
        clock = time.asctime().split()[3]

        # Cria um rótulo de texto
        self.control = wx.StaticText(self, label=clock)

        # Muda a fonte
        self.control.SetFont(wx.Font(22, wx.SWISS, wx.NORMAL, wx.BOLD))

        # Cria um timer
        TIMER_ID = 100
        self.timer = wx.Timer(self, TIMER_ID)

        # Inicia o timer
        self.timer.Start(1000)

        # Associa os métodos com os eventos
        wx.EVT_TIMER(self, TIMER_ID, self.on_timer)
        wx.EVT_CLOSE(self, self.on_close)
        self.Show(True)

    def on_timer(self, event):
        
        # Atualiza o relógio
        clock = time.asctime().split()[3]
        self.control.SetLabel(clock)

    def on_close(self, event):
        
        # Para o timer
        self.timer.Stop()
        self.Destroy()


app = wx.App()
Main(None, wx.ID_ANY, 'Relógio')
app.MainLoop()

Interface:

Temporizador

Exemplo (barra de menus):

In [ ]:
import wx

# Identificadores para as opções do menu
ID_FILE_OPEN = wx.NewId()
ID_FILE_SAVE = wx.NewId()
ID_FILE_EXIT =  wx.NewId()
ID_HELP_ABOUT =  wx.NewId()

class Main(wx.Frame):
    def __init__(self, parent, id, title):

        wx.Frame.__init__(self, parent, id, title)
        # Cria o menu arquivo
        filemenu = wx.Menu()

        # Cria as opções
        filemenu.Append(ID_FILE_OPEN, 'Abrir arquivo...')
        filemenu.Append(ID_FILE_SAVE, 'Salvar')
        filemenu.AppendSeparator()
        filemenu.Append(ID_FILE_EXIT, 'Sair')

        # Cria o menu ajuda
        helpmenu =  wx.Menu()
        helpmenu.Append(ID_HELP_ABOUT, 'Sobre...')

        # Cria o menu
        menubar =  wx.MenuBar()
        menubar.Append(filemenu, 'Arquivo')
        menubar.Append(helpmenu, 'Ajuda')
        self.SetMenuBar(menubar)

        # Associa métodos aos eventos de menu
        wx.EVT_MENU(self, ID_FILE_OPEN, self.on_open)
        wx.EVT_MENU(self, ID_FILE_SAVE, self.on_save)
        wx.EVT_MENU(self, ID_FILE_EXIT, self.on_exit)
        wx.EVT_MENU(self, ID_HELP_ABOUT, self.about)

        # Cria uma caixa de texto
        self.control = wx.TextCtrl(self, 1,
            style=wx.TE_MULTILINE)
        self.fn = ''

    def on_open(self, evt):

        # Abre uma caixa de dialogo escolher arquivo
        dialog = wx.FileDialog(None, style=wx.OPEN)
        d = dialog.ShowModal()
        if d == wx.ID_OK:

            # Pega o arquivo escolhido
            self.fn = dialog.GetPath()

            # Muda o título da janela
            self.SetTitle(self.fn)

            # Carrega o texto na caixa de texto
            self.control.SetLabel(file(self.fn, 'rb').read())
        dialog.Destroy()

    def on_save(self, evt):

        # Salva o texto na caixa de texto
        if self.fn:
            file(self.fn, 'wb').write(self.control.GetLabel())

    def on_exit(self, evt):
        # Fecha a janela principal
        self.Close(True)

    def about(self, evt):
        dlg = wx.MessageDialog(self,
            'Exemplo wxPython', 'Sobre...',
            wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()


app = wx.App()
frame = Main(None , wx.ID_ANY, 'Isto é quase um editor...')
frame.Show(True)
app.MainLoop()

Janela principal:

Exemplo de menu com wx

Exemplo (caixa de mensagem):

In [ ]:
import wx


class Main(wx.Frame):
    
    def __init__(self, parent, id, title):

        # Cria janela
        wx.Frame.__init__(self, parent, id, title, size=(300, 150))
        self.Centre()
        self.Show(True)

        # Cria um texto estático
        self.text = wx.StaticText(self, label='Entre com uma expressão:',
            pos=(10, 10))

        # Cria uma caixa de edição de texto
        self.edit = wx.TextCtrl(self, size=(250, -1), pos=(10, 30))

        # Cria um botão
        self.button = wx.Button(self, label='ok', pos=(10, 60))

        # Conecta um método ao botão
        self.button.Bind(wx.EVT_BUTTON, self.on_button)

    def on_button(self, event):

        # Pega o valor da caixa de texto
        txt = self.edit.GetValue()
        
        # Tenta resolver e apresentar a expressão
        try:
            wx.MessageBox(txt + ' = ' + str(eval(txt)), 'Resultado')
        
        # Se algo inesperado ocorrer
        except:
            wx.MessageBox('Expressão inválida', 'Erro')


app = wx.App()
Main(None, -1, 'Teste de MessageBox')
app.MainLoop()

Janela principal e caixa de mensagem:

Janela principal e caixa de mensagem

O wxPython oferece uma variedade enorme de controles prontos, que ser no programa de demonstração que é distribuído junto com a documentação e os exemplos.

PyQt

Qt é um toolkit desenvolvido em C++ e é utilizado por diversos programas, incluindo o ambiente de desktop gráfico KDE e seus aplicativos. Embora o Qt seja mais usado para a criação de aplicativos GUI, ele também inclui bibliotecas com outras funcionalidades, como acesso a banco de dados, comunicação de rede e controle de threads, entre outras. PyQt é um binding que permite o uso do Qt no Python, disponível sob a licença GPL.

A Qt na versão 4 possui dois módulos 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).

Exemplo:

In [ ]:
import sys
from PyQt4 import QtGui, QtCore


class Main(QtGui.QWidget):
    """
    Janela principal
    """

    def __init__(self, parent=None):

        QtGui.QWidget.__init__(self, parent)

        # Muda a geometria da janela
        self.setGeometry(200, 200, 200, 100)

        # Muda o título
        self.setWindowTitle('Teste')

        # Cria um botão
        quit = QtGui.QPushButton('Fechar', self)
        quit.setGeometry(10, 10, 60, 35)

        # Conecta o sinal gerado pelo botão com a função
        # que encerra o programa
        self.connect(quit, QtCore.SIGNAL('clicked()'),
            QtGui.qApp, QtCore.SLOT('quit()'))


# Cria um objeto "aplicação Qt", que trata os eventos
app = QtGui.QApplication(sys.argv)

# Cria a janela principal
qb = Main()
qb.show()

# Inicia a "aplicação Qt"
sys.exit(app.exec_())

Janela principal:

Janela Qt

Um dos maiores atrativos do PyQt é o GUI Builder (ferramenta para a construção de interfaces) Qt Designer. Os arquivos XML gerados pelo Qt Designer (com a extensão .ui) podem ser convertidos em módulos Python através do utilitário pyuic.

Qt Designer

Para gerar o módulo Python a partir do arquivo criado no Qt Designer:

pyuic dialog.ui -o dialog.py

No qual dialog.ui é o arquivo de interface e dialog.py é o módulo.

Exemplo de arquivo gerado pelo Qt Designer (dialog.ui):

In [ ]:
<ui version="4.0" >
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog" >
  <property name="geometry" >
   <rect>
    <x>0</x>
    <y>0</y>
    <width>116</width>
    <height>108</height>
   </rect>
  </property>
  <property name="windowTitle" >
   <string>Dialog</string>
  </property>
  <widget class="QPushButton" name="msg" >
   <property name="geometry" >
    <rect>
     <x>20</x>
     <y>20</y>
     <width>75</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text" >
    <string>Mensagem</string>
   </property>
  </widget>
  <widget class="QPushButton" name="fim" >
   <property name="geometry" >
    <rect>
     <x>20</x>
     <y>60</y>
     <width>75</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text" >
    <string>Fechar</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>fim</sender>
   <signal>clicked()</signal>
   <receiver>Dialog</receiver>
   <slot>close()</slot>
   <hints>
    <hint type="sourcelabel" >
     <x>57</x>
     <y>71</y>
    </hint>
    <hint type="destinationlabel" >
     <x>57</x>
     <y>53</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>

O arquivo de interface define uma janela, da classe QDialog, chamada “Dialog”, com dois botões, da classe QPushButton, chamados “msg” e “fim”.

Exemplo usando o módulo criado pelo Qt Designer:

In [ ]:
import sys
import time
from PyQt4 import QtCore, QtGui

# Módulo gerado pelo pyuic
from dialog import Ui_Dialog


class Main(QtGui.QMainWindow):
    """
    Janela principal
    """
    def __init__(self, parent=None):
        """
        Inicialização da janela
        """
        QtGui.QWidget.__init__(self, parent)

        # Cria um objeto a partir da interface gerada pelo pyuic
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)

        # Conecta o método ao botão que foi definido através do Qt Designer
        self.connect(self.ui.msg, QtCore.SIGNAL('clicked()'),
            self.show_msg)

    def show_msg(self):
        """
        Método que evoca a caixa de mensagem
        """
        reply = QtGui.QMessageBox.question(self, 'Messagem',
            'Hora: ' + time.asctime().split()[3],
            QtGui.QMessageBox.Ok)


if __name__ == "__main__":

    app = QtGui.QApplication(sys.argv)
    myapp = Main()
    myapp.show()
    sys.exit(app.exec_())

Janela principal e caixa de mensagem:

Janela principal e caixa de mensagem

Também está disponível um binding LGPL similar ao PyQt, chamado PySide.

Outras funcionalidades associadas a interface gráfica podem ser obtidas usando outros módulos, como o pySystray, que implementa a funcionalidade que permite que o aplicativo use a bandeja de sistema no Windows.

In [1]:
 
Out[1]: