Ir para o conteúdo

Tutorial de Tkinter

Ultimamente temos assistido a um crescer de pequenos scripts aqui na secção de Python que partilham de uma característica em comum: interface gráfica. Porém, para o iniciante, ou mesmo para quem já tem algum calo na programação "a preto e branco" da consola, olhar para um script que cria janelas, botões, e outros pode ser complicado. Complicado mais ainda, é perceber o que faz o quê, e descortinar dali conhecimento útil. Com este pequeno "handicap" em vista, decidimos produzir um pequeno documento de introdução ao desenvolvimento de interfaces gráficas em Python. A biblioteca escolhida foi o Tkinter, pela sua facilidade de uso e por já vir com a instalação normal de Python.

Hello World

Vamos começar pelo tradicional Hello World. Ora, em Python a "preto-e-branco", não pode ser mais trivial:

print 'Hello World'

Passar isto para uma janelinha já acarreta algumas linhas de código, mas nada de complicado. Vejamos então do que precisamos:

  • Uma janela mãe
  • Um widget que nos diga olá :)

Wigdets? Janelas-mãe? Ok, devo-vos alguma explicação. Na criação de um ambiente gráfico, existem alguns novos termos. A janela-mãe, é um elemento base que nos vai permitir a construção de outros "em cima" dele. Ou seja, fazendo a analogia com um quadro, a janela-mãe é a tela de pintura, onde depois pintamos com o pincel. E os widgets? Bem, continuando a analogia, são as pinceladas! Um widget pode ser qualquer coisa: um botão, um texto, uma imagem, uma nova janela. Vamos então ver o código necessário à nossa tarefa de passar o Hello World para um ambiente gráfico. A explicação do código está confortavelmente junto a cada linha num (ou mais) comentário(s).

from Tkinter import * # Importamos o módulo
root = Tk() # Cria a janela-mãe. Só se deve criar uma por programa e sempre primeiro que os widgets (tem lógica não?)
w = Label(root, text='Hello World') # Widget filho (de quem? Da janela-mãe root), com o texto Hello World.
w.pack() # Cria o widget e ajusta as dimensões automaticamente ao conteúdo (neste caso ao nosso texto)
root.mainloop() # Inicia um main loop que mantem a janela aberta indefinidamente

Bem, já está! Mas é pá... que hello world reles. Mal se vê de tão pequenina que é a janela. E se o nosso texto for claustrofóbico? Vamos tratar de lhe dar mais espaço. E já agora, sendo eu um bom estudante de Coimbra, vamos lá por isto com as cores da Briosa! Para fazermos estas alterações, vamos lidar com alguns parâmetros da classe Label que podemos passar quando a criamos. Em relação às dimensões, vou usar os métodos padx e pady para dar um espacinho em redor do texto (x na horizontal e y na vertical). Quanto às cores, são definidas por bg e fg, respectivamente para a cor de fundo e para a cor do texto.

from Tkinter import *
root = Tk()
w = Label(root, text='Hello World', padx=20, pady=20, bg='black', fg='white') # As únicas mudanças são aqui <--------
w.pack()
root.mainloop()

Para uma descrição mais detalhada dos argumentos que a função Label leva, vejam esta página: http://infohost.nmt.edu/tcc/help/pubs/tkinter/label.html. Podemos definir dimensões, sublinhados, imagens e bitmaps, molduras, cores, etc. Está tudo convenientemente à distância de uma variável.

Hello World on Steroids

Vamos então prosseguir. Pegando no exemplo do nosso Hello World, lá por eu ser academista não significa que não seja tolerante. Vamos lá criar uma janelinha que dê para mudar a cor (WARNING: cores escolhidas pelo Heckel). Em primeiro lugar, há que introduzir um novo conceito: programação gráfica com classes. Ora, tal como qualquer outra aplicação da programação orientada a objectos, torna-se mais simples se tomarmos cada widget como um objecto e os fizermos interagir. O nosso código acima, torna-se mais robusto (número de linhas aumenta) mas nem por isso mais complicado:

from Tkinter import *

class App:

    def __init__(self, master):

        self.master = master
        self.texto()

    def texto(self):    
        w = Label(self.master, text='Hello World')
        w.pack()

## Principal ##

root = Tk()

app = App(root)

root.mainloop()

Tornou-se um pouco mais complicado, mas apenas devido à introdução do paradigma de classes. Este paradigma permite, porém, a introdução e manipulação de novos widgets, de maneira simples, quase simples demais para ser verdade. Para acomodar a nossa opção de mudar cores, temos que introduzir 2 novos widgets: Frame e Button. O Frame é, em boa verdade, um contentor de widgets. E para o que vamos precisar dele, ficamos por esta bela definição. Já o Button, é... um botão! Quem diria?! Tal como o Label, o Button tem uma série de argumentos que podemos, ou não, usar. No exemplo que se segue, tiramos partido de um bem útil: command. Este pequenino nome permite-nos associar uma acção ao clique do botão, que no nosso caso vai ser mudar a cor do texto. E, como nem todos estão com paciência para me aturar, vou tomar a liberdade de adicionar um botão que desligue o nosso programinha :)

from Tkinter import *

class App:

    def __init__(self, master):

        frame = Frame(master) # Construimos o Frame
        frame.pack() # Torna-mo-lo visível

        self.button = Button(frame, text="QUIT", fg="red", command=frame.quit) # Criamos um botao associado ao comando quit
        self.button.pack(side=LEFT) # definimos a posicao do botao (LEFT = o mais a esquerda possivel)

        self.button2 = Button(frame, text="Hello", command=self.say_hi) # O botao esta associado a funcao say_hi
        self.button2.pack(side=LEFT)

        self.button3 = Button(frame, text='Oops', command=self.change_color)
        self.button3.pack(side=LEFT)

    def say_hi(self, textcolor='white', bgcolor='black'):
        self.w= Label(root, text="hi there, everyone!", bg=bgcolor, fg=textcolor) # Cria um label com as cores la de cima.
        self.w.pack()

    def change_color(self): # A nossa função de mudar a cor ao texto
        self.w.destroy()
        self.say_hi('blue', 'yellow')

#### Main ####
root = Tk()
app = App(root)
root.mainloop()

Analizemos ao pormenor então os passos mais complicados. Primeiro, reparem que os botões estão alojados na frame enquanto o label está na janela-mãe. Se experimentarmos mudar o Label para dentro da frame (trocar o argumento root, por frame, mas cuidado com as variáveis!), vejam o que acontece :) Pois, há uma cerca hierarquia. A Janela-Mãe (root) está a alojar o Frame e o Label (Root ---> Frame / Label). Na nossa segunda opção, temos: Root --> Frame --> Label. Há que ter uma noção espacial quando desenhamos uma GUI. Depois, temos o widget Button. Tal como na Label, temos vários argumentos que podemos definir. No nosso exemplo defini o texto que aparece no botão (e a sua cor no QUIT) e a acção associada ao botão, mas há mais alguns: http://infohost.nmt.edu/tcc/help/pubs/tkinter/button.html.

Vejam que podemos associar o comando a uma função. Podemos ter um módulo de funções externo e chamar as que precisamos, sem estarmos a encher o nosso código da GUI com funções "Extra-GUI".

Gestores de Geometrias em Tkinter

Por uma questão de simplicidade, vamos voltar ao nosso exemplo do Hello World inicial:

from Tkinter import * 

root = Tk() 
w = Label(root, text='Hello World')
w.pack() 
root.mainloop()

Agora, experimentem comentar a linha que chama o método pack sobre o widget Label, e corram o código de seguida. Então? Onde está o nosso Label? Pois, não está. O que aconteceu foi que, como eu já tinha explicado, o método pack é responsável por “criar” o nosso widget. Na verdade, não é bem assim. O que o método pack faz é, na verdade, “desenhar” um widget já criado num espaço vazio — a nossa root. Ao método pack chamamos um gestor de geometrias, uma vez que ele nos permite organizar os widgets espacialmente. Além do pack, temos o grid e o place, se bem que este último é, a bem dizer, demasiado “picuinhas” e, consequentemente, complicado de utilizar.

Vamos começar pelo grid. Apesar de termos usado o pack até agora, foi apenas pela simplicidade de, se chamado sem argumentos, preencher todo o espaço possível com o widget associado. Num programa mais “a sério” vamos querer delimitar o espaço que cada widget terá, bem como a sua posição, altura onde o pack se torna algo mais complexo de manipular.

Grid

O .grid() trata cada janela e cada Frame como uma tabela, com linhas e colunas, como uma grelha, portanto, tal como indica o nome. Tal como no .pack(), temos que primeiro construir o widget e depois torná-lo visível, associando-o ao gestor de geometria .grid():

whatever = Label(root, text='Hello World')
whatever.grid()

Até aqui, é igual. E aliás, substituindo no nosso exemplo, o resultado é exactamente o mesmo. As opções que nós usámos lá atrás, padx, pady, bg e fg, funcionam também aqui da mesma maneira.