Você está na página 1de 49

As trs Fates

Steven Ferg (steve@ferg.org)

Traduzido e adaptado por J. Labaki


labaki@feis.unesp.br
Grupo Python
Departamento de Engenharia Mecnica
UNESP Ilha Solteira

STEVEN FERG

PENSANDO EM TKINTER

ndice

Sobre Pensando em Tkinter


Os programas
TT000 Introduo
As quatro questes bsicas da programao de GUIs
Alguns jarges da programao de GUIs
Sobre o Event Loop
TT010 O programa em Tkinter mais simples possvel: trs linhas.
TT020 Criando um objeto GUI e fazendo pack; containeres versus widgets.
Frames so elsticos
TT030 Criando um Widget e colocando-o no frame.
TT035 Usando a estrutura de classes no programa.
Por que estruturar sua aplicao como uma classe?
Quando introduzir a estrutura de classes
TT040 Algumas formas diferentes de definir um widget.
TT050 Empacotando.
Porque os botes apareceram verticalmente no ltimo programa
Alguns termos tcnicos Orientao
TT060 Fazendo Binding.
TT070 Mexendo com foco e ligando eventos de teclado a widgets.
TT074 Command Binding.
TT075 Usando event binding e command binding juntos.
Para quais eventos serve command binding?
Usando event binding e command binding juntos
TT076 Compartilhando informaes entre alimentadores de eventos.
Compartilhando informaes entre funes alimentadoras de eventos
Primeira soluo usar variveis globais
Segunda soluo usar variveis instanciadas
TT077 Transmitindo argumentos para alimentadores de eventos I: O problema.
Caractersticas mais avanadas de command binding
TT078 Transmitindo argumentos para alimentadores de eventos II: Usando Lambda.
TT079 Transmitindo argumentos para alimentadores de eventos III: Usando Currying.
Sobre Curry
Curry como us-lo
Lambda versus Curry & event_lambda: qual devo usar?
TT080 Opes de widget e configuraes de pack
Trs tcnicas de controlar o layout de uma GUI
TT090 Posicionando frames
TT095 Mtodos gerenciadores de janelas & controlando o tamanho de janelas com a opo
geometry.
TT100 Opes de pack: side, expand, fill e anchor.
Um jeito prtico de encontrar erros

GRUPO PYTHON UNESP ILHA SOLTEIRA

3
3
4
4
4
5
6
7
9
9
11
11
12
13
14
14
15
16
19
22
24
24
25
26
26
27
27
29
29
31
35
35
36
38
40
40
43
46
49
50

STEVEN FERG

PENSANDO EM TKINTER

Sobre Pensando em Tkinter


Tenho tentado aprender sozinho Tkinter por vrios livros, e tenho encontrado mais
dificuldade do que poderia pensar.
O problema que os autores desses livros j comeam dizendo tudo sobre os widgets
disponveis em Tkinter, mas nunca do uma parada para explicar os conceitos bsicos. Eles
no explicam como pensar em Tkinter. Ento achei que deveria tentar escrever um livro
do tipo que eu mesmo estou procurando. Ou ao menos rascunhar esse tipo de livro.
Pensando em Tkinter consiste de pequenos programas que comeam a explicar como
pensar em Tkinter. Nestes, no me preocupo em catalogar todos os tipos de widgets,
atributos e mtodos disponveis em Tkinter, somente em dar um empurrozinho na direo
dos conceitos bsicos do assunto.
Estes programas no tm inteno de introduzir todos os aspectos da programao de
Tkinter. Para este fim, sugiro que leia An Introduction to Tkinter, de Frederik Lundh e
Tkinter, GUI Programming with Python, da New Mxico Tech Computer Center.
Acima de tudo, quero enfatizar que Practical Programming in Tcl and Tk de Brend
Welch absolutamente essencial para trabalhar com Tk e Tkinter. Arranje esse livro!
Note que voc no deve rodar estes programar usando o IDLE. O IDLE por si s uma
aplicao de Tkinter; possui seu prprio mainloop que entrar em conflito com o
mainloop includo nestes programas. Se voc insiste em ver e rodar estes programas usando
IDLE, ento para cada programa dever apagar a linha correspondente ao mainloop
antes de rod-lo.
Este material tem sido substancialmente melhorado pelo feedback dos entusiastas e
comunidade pythoniana. Meu grande Obrigado! a Alan Colburn, Jeff Epler, Greg Ewing,
Tom Good, Steve Holden, Joseph Knapka, Gerrit Mller, Russel Owen, e Chad Netzer.
Obrigado a Terry Carroll por recolher e organizar esse material.
Finalmente, se voc novo na programao orientada a eventos (que Tkinter requer), deve
dar uma olhada neste assunto antes.
Os Programas
Voc deve baixar o arquivo zip que contm todos os programas. Pode ser atravs do
seguinte endereo:
http://geocities.yahoo.com.br/grupopython/pensando_em_tkinter.zip

O pacote contm os programas de Pensando em Tkinter. Para instalar estes arquivos,


simplesmente descompacte o pacote de arquivos em um diretrio de sua escolha.

GRUPO PYTHON UNESP ILHA SOLTEIRA

STEVEN FERG

PENSANDO EM TKINTER

TT000 Introduo.
Eis alguns programas que comearo a explicar como pensar em Tkinter. Neles, como j
disse, no atento para todos os tipos de widgets, atributos e mtodos disponveis em
Tkinter, tampouco tento dar uma introduo detalhada sobre Tkinter, somente tento iniciar
voc no caminho para entender os seus conceitos bsicos.
No decorrer do programa de ensino, a discusso dedicada exclusivamente ao gerenciador
de geometria pack. No falaremos sobre os gerenciadores grid ou place.
As quatro questes bsicas na programao de GUIs.
Quando voc desenvolve uma interface com o usurio (IU) h um conjunto padro de
questes que voc deve satisfazer.
1) Voc deve especificar como voc quer que a IU se parea. Isto , voc precisa
escrever um cdigo que determina o que o usurio ver na tela do computador;
2) Voc deve decidir o que quer que a IU faa. Isto , voc deve escrever
procedimentos que satisfaam as necessidades do programa;
3) Voc precisa associar o parecer com o fazer. Isto , voc deve escrever um cdigo
que associa as coisas que o usurio v na tela com os procedimentos que voc
escreveu para desempenhar os papis requeridos pelo programa;
4) Finalmente, voc deve escrever um cdigo que senta e espera pela entrada do
usurio.
Alguns jarges da programao de GUIs.
A programao GUI (Graphic User Interface Interface Grfica com o Usurio) tem alguns
jarges especiais associados s suas questes bsicas.
1) Ns especificamos como queremos que um GUI se parea descrevendo os widgets
que queremos exibir, e suas relaes especiais (por exemplo, o quanto um widgets
est abaixo ou acima, ou direita ou esquerda de outros widgets). A palavra
widget um termo sem traduo que designa componentes de interface grfica
com o usurio de um modo geral. Widgets inclui elementos como janelas, botes,
menus e itens de menus, cones, listas rolantes, barras de rolagem, etc.
2) Os procedimentos que executam as tarefas dos GUIs so chamados event handler.
Events so formas de entrada de dados como cliques de mouse ou digitao no
teclado. Esses procedimentos so chamados handlers (alimentadores) porque eles
alimentam (isto , respondem a) estes eventos.
3) A associao de um event handler a um widget chamada binding. De modo
geral o processo de binding envolve a associao entre trs coisas diferentes:

GRUPO PYTHON UNESP ILHA SOLTEIRA

STEVEN FERG

PENSANDO EM TKINTER

a. Um tipo de evento (por exemplo, um clique no boto esquerdo do mouse, ou


pressionar a tecla ENTER),
b. Um widget (por exemplo, um boto) e
c. Um procedimento event handler.
Por exemplo, ns precisamos fazer binding (a) num clique com o boto esquerdo do
mouse no (b) boto FECHAR na tela para (c) executar o procedimento
fechePrograma, que fechar a janela e desligar o programa.
4) O cdigo que senta e espera pela entrada de dados chamada de event loop.
Sobre o Event Loop
Se voc tem visto filmes, sabe que toda cidadezinha tem uma vov que perde todo seu
tempo debruada na janela, s olhando. Ela v tudo que acontece na vizinhana. Uma parte
disso no tem graa, claro s pessoas subindo e descendo a rua. Mas a outra
interessante como uma bela briga entre os pombinhos recm-casados do outro lado da
rua. Quando algo interessante acontece, essa vovozinha co-de-guarda imediatamente corre
para o telefone contar tudo polcia da vizinhana.
O Event Loop algo como essa vovozinha: gasta todo seu tempo esperando que eventos
aconteam. Muitos dos eventos no tm importncia (como clicar num ponto neutro da
janela), e quando os v, no faz nada. Mas quando v alguma coisa interessante um
evento que ele sabe, por meio de um binding de event handler, que interessante (como um
clique num dos botes da janela) ento imediatamente contata os event handler e faz com
que saibam que o evento aconteceu.
Comportamento do Programa
Este programa facilita a voc o entendimento da programao de interfaces com o usurio
mostrando como estes conceitos bsicos so implementados em um programa muito
simples. Este programa no usa Tkinter ou qualquer forma de programao GUI, somente
coloca um menu e um console, e recebe caracteres digitados no teclado como entrada.
Assim, como voc pode ver, ele satisfaz as quatro questes bsicas da programao de
interfaces com o usurio.
#Questo 2: Define os procedimentos de event handler
def handle_A():
print "Wrong! Try again!"
def handle_B():
print "Absolutely right!

Trillium is a kind of flower!"

def handle_C():
print "Wrong! Try again!"
#Questo 1: Define a aparncia da tela
print "\n"*100
# clear the screen
print "
VERY CHALLENGING GUESSING GAME"
print "========================================================"

GRUPO PYTHON UNESP ILHA SOLTEIRA

STEVEN FERG

PENSANDO EM TKINTER

print
print
print
print
print
print
print
print
print
print

"Press the letter of your answer, then the ENTER key."


"
"
"

A.
B.
C.

Animal"
Vegetable"
Mineral"

"

X.

Exit from this program"

"========================================================"
"What kind of thing is 'Trillium'?"

#Questo 4: O Event Loop. Loop eterno, esperando que algo acontea.


while 1:
# Observamos o prximo evento
answer = raw_input().upper()
# -------------------------------------------------------------# Questo 3: Associamos os eventos de teclado que nos interessam
# com seus event handlers. Uma forma simples de binding.
# -------------------------------------------------------------if answer == "A": handle_A()
if answer == "B": handle_B()
if answer == "C": handle_C()
if answer == "X":
# clear the screen and exit the event loop
print "\n"*100
break
#Perceba que
ignorados.

quaisquer

outros

eventos

no

interessam,

por

isso

so

TT010 O programa em Tkinter mais simples possvel: trs linhas.


Das quatro questes bsicas na programao de GUIs que ns vimos no ltimo programa,
este programa cumpre somente uma ele roda o event loop.
(1)
A primeira linha importa o mdulo Tkinter e deixa-o disponvel para uso. Perceba que a
forma de importar (from Tkinter import *) significa que ns no queremos ter que
usar a forma Tkinter. para especificar nada que quisermos utilizar.
(2)
A segunda linha cria uma janela toplevel (que pode ou no se tornar visvel). Tecnicamente,
o que esta linha faz criar uma instncia da classe Tkinter.Tk.
Esta janela toplevel o componente GUI de mais alto nvel1 de qualquer aplicao de
Tkinter. Por conveno, esta janela normalmente chamada de raiz.
(3)
A terceira linha executa o mtodo mainloop (isso , o event loop) deste objeto raiz. Assim
que roda, o mainloop espera que eventos aconteam no objeto raiz. Se um evento ocorre,
ento ele alimentado (o event handler executado) e o loop continua rodando, esperando
1

Entenda por de alto nvel a aplicao que tem mais relao com o usurio que com o cdigo (N do T).

GRUPO PYTHON UNESP ILHA SOLTEIRA

STEVEN FERG

PENSANDO EM TKINTER

pelo prximo evento, ou at que acontea um evento que destrua a raiz. Um evento
destruidor pode ser o fechamento da janela pelo boto X de fechamento. Quando a raiz
destruda, a janela fechada e o event loop cancelado.
Comportamento do programa
Ao rodar este programa, voc ver (graas ao Tk) a janela toplevel automaticamente com os
widgets para minimizar, maximizar e fechar a janela. Tente us-los voc ver que eles
realmente funcionam.
Clicando no widget fechar (o X em uma caixa, do lado direito da barra de ttulo) ser
gerado um evento destruidor terminando o event loop principal, que no caso deste programa
mainloop. E desde que no haja mais nada depois da linha root.mainloop(), como
neste caso, o programa no faz mais nada e se encerra.

from Tkinter import * ###(1)


root = Tk()
###(2)
root.mainloop()
###(3)

TT020 Criando um objeto GUI e fazendo pack; containeres versus


widgets.
Agora daremos uma pincelada em outra das quatro questes bsicas da programao GUI
especificar como a GUI dever parecer.
Neste programa, introduzimos trs dos maiores conceitos da programao em Tkinter:

criar um objeto GUI e associ-lo com seus mestres;


pack e
container versus widget.

De agora em diante, distinguiremos os componentes entre containeres e um widgets. Como


usarei sempre estes termos, um widget um componente GUI que (geralmente) visvel e
faz coisas. J um container simplesmente um container uma cesta, como queira dentro
do qual dispem-se os widgets.
Tkinter oferece vrios tipos de containeres. Canvas um container para aplicaes de
desenho, e o container mais freqentemente utilizado o Frame.
Frames so oferecidos pelo Tkinter como uma classe chamada Frame. Uma expresso
como:
Frame(meuMestre)

GRUPO PYTHON UNESP ILHA SOLTEIRA

STEVEN FERG

PENSANDO EM TKINTER

cria uma instncia da classe Frame (isto , cria um frame), e associa a instncia ao seu
mestre, meuMestre. Outra maneira de ver isso como uma expresso que adiciona um
frame escravo ao componente meuMestre.
Ento, neste programa (linha 1),
myContainer1 = Frame(myParent)

cria um frame cujo mestre myParent (isto , a raiz), e d a ele o nome de


myContainer1. Resumindo, ele cria um container dentro do qual podemos agora colocar
widgets. (Ns no colocaremos nenhum widget neste programa, somente posteriormente).
Perceba que a relao mestre/escravo aqui somente lgica, no tem nada de visual. Esta
relao existe para otimizar eventos do tipo destrutivo isso porque quando um
componente mestre (como root) destrudo, o mestre sabe quem so seus escravos, e pode
destru-los antes de se destruir.
(2)
A prxima linha define o gerenciador de geometria pack para administrar myContainer1.
myContainer1.pack()

Simplesmente designado, pack um mtodo que transforma em visuais as relaes entre


os componentes GUI e seus mestres. Se voc no definir o componente pack ou outro
gerenciador de geometria, nunca ver a GUI.
Um gerenciador de geometria essencialmente um API um meio de dizer ao Tkinter
como voc quer que containeres e widgets se apresentem visualmente. Tkinter oferece trs
gerenciadores para esta finalidade: pack, grid e place. Pack e grid so os mais usados por
serem mais simples. Todos os exemplos em Pensando em Tkinter usam pack como
gerenciador de geometria.
O esquema padro bsico da programao em Tkinter, que veremos ainda diversas vezes,
funciona mais ou menos assim:

uma instncia (de um widget ou um container) criada, e associada ao seu


mestre;
a instncia administrada por um gerenciador de geometria.

Comportamento do programa
Quando voc roda este programa, ele se parecer muito com seu antecessor, exceto pelo
tamanho. Isso porque...

GRUPO PYTHON UNESP ILHA SOLTEIRA

STEVEN FERG

PENSANDO EM TKINTER

Frames so elsticos
Um frame basicamente um container. O interior de um container o espao existente
dentro dele chamado cavidade, um termo tcnico que Tkinter herdou de Tk. Essa
cavidade extensvel (ou elstica) como uma borracha. A menos que voc especifique um
tamanho mximo ou mnimo para o frame, a cavidade ser esticada ou comprimida at
acomodar qualquer coisa que o frame contiver.
No programa anterior, por no termos colocado nada dentro dela, a raiz mostrou a si mesma
na tela com seu tamanho padro, mas neste programa, ns preenchemos sua cavidade com o
myContainer1. Agora, a raiz se estica para acomodar o tamanho de myContainer1, mas
como no colocamos nenhum widget neste frame, nem especificamos um tamanho mnimo
para ele, a cavidade da root se encolhe at o limite. Por isso no h nada para ser visto
abaixo da barra de ttulo desse programa.
Nos prximos programas, colocaremos widgets e outros containeres dentro do Container1,
e voc ver como ele se arranja para acomod-los.
from Tkinter import *
root = Tk()
myContainer1 = Frame(root)
myContainer1.pack()

###(1)
###(2)

root.mainloop()

TT030 Criando um Widget e colocando-o no frame.


Neste programa, ns criamos nosso primeiro widget e o colocamos dentro de
myContainer1.
(1)
O Widget ser um boto isto , ele ser uma instncia da classe Button de Tkinter. A
linha:
Button1=Button(myContainer1)

cria o boto, dando-o o nome de button1, e associa-o ao seu mestre, o objeto container
chamado myContainer1.
(2)(3)
Os widgets tem muitos atributos, todos disponveis no dicionrio do namespace local. O
widget Button tem atributos para controlar seu tamanho, sua cor de fundo e de fonte, seu
texto, como suas bordas se parecero, etc. Neste exemplo, ns iremos ajustar somente dois

GRUPO PYTHON UNESP ILHA SOLTEIRA

STEVEN FERG

PENSANDO EM TKINTER

atributos de button: a cor de fundo e o texto. Faremos isso mudando os valores do seu
dicionrio referentes s chaves text e background.
button1["text"] = "Hello, World!
button1["background"] = "green"

"

(4)
E, claro, ns fazemos pack no boto button1.
button1.pack()

Comportamento do programa
Quando voc rodar este programa, dever ver que o Container1 agora contm um boto
verde com o texto Hello, World!. Quando voc clica nele no acontece nada, porque ns
ainda no especificamos o que queremos que acontea quando o boto for clicado, se bem
que o faremos mais tarde.
Por ora, voc dever clicar no boto X da barra de ttulo para fech-lo, como antes.
from Tkinter import *
root = Tk()
myContainer1 = Frame(root)
myContainer1.pack()
button1 = Button(myContainer1)
button1["text"]= "Hello, World!"
button1["background"] = "green"
button1.pack()

### (1)
### (2)
### (3)

### (4)

root.mainloop()

TT035 Usando a estrutura de classes no programa.


Neste programa, introduziremos o conceito de aplicaes de Tkinter estruturadas como
classes.
Nele, criamos uma classe chamada MyApp e transcrevemos alguns cdigos dos programas
anteriores para dentro de seu mtodo construtor (__init__). Nesta verso reestruturada do
programa, fazemos 3 coisas diferentes:
(1)
Em nosso cdigo, designamos uma classe (MyApp) que define como queremos que a GUI
se parea e que tipo de coisa queremos fazer com isso. Todo este cdigo inserido no
mtodo construtor da classe.

GRUPO PYTHON UNESP ILHA SOLTEIRA

10

STEVEN FERG

PENSANDO EM TKINTER

(2)
Quando o programa executado, a primeira coisa que ele faz criar uma instncia da
classe. A linha que cria a instncia
myapp=MyApp(root)

Perceba que o nome da classe MyApp (observe o jogo de maisculas) e o nome da


instncia myapp (agora tudo minsculo).
Perceba tambm que essa linha faz de root, a raiz, um argumento dentro do mtodo
construtor de MyApp. O mtodo construtor reconhece root sob o nome myParent.
(3)
Finalmente, deixamos a raiz em mainloop.
Por que estruturar sua aplicao como uma classe?
Uma das razes para usar uma estrutura de classes em seu programa simplesmente
control-lo melhor. Um programa estruturado em classes provavelmente especialmente
se seu programa for muito grande muito mais fcil de ser entendido.
Uma considerao muito importante que estruturar sua aplicao como uma classe
ajudar voc a evitar o uso de variveis globais. Eventualmente, conforme seu programa for
crescendo, voc provavelmente ir querer que alguns de seus event handler consigam
compartilhar informaes entre si. Uma maneira usar variveis globais, mas uma tcnica
muito maante. Um caminho muito melhor usar instncias (isto , usar self. nas
variveis), e para isso voc precisa estruturar sua aplicao como classes. Exploraremos
esse tpico em outros programas desta srie.
Quando introduzir a estrutura de classes
Temos introduzido a noo de estrutura de classes para programas de Tkinter logo cedo
para podermos explic-la e partir para outros assuntos. Porm, no andar da carruagem, voc
poder escolher proceder de outra forma.
Em muitos casos, um programa em Tkinter comea com um script simples. Todo o cdigo
acontece numa nica linha, como nossos programas anteriores. Ento, conforme a aplicao
toma corpo, o programa cresce, de forma que em pouco tempo voc se ver envolvo por
uma porrada de cdigo. Voc pode comear a usar variveis globais... talvez uma porrada
de variveis globais. O programa comea a ficar difcil de entender e editar. Quando isso
acontece, hora de voltar prancheta e refazer tudo, desta vez usando classes.
Por outro lado, se voc se sentir bem com classes, e tem uma boa idia da forma final do
seu programa, comece estruturando-o em classes desde o comeo.
Por outro lado (voltamos assim ao lado anterior?), logo no comeo do processo de
desenvolvimento (como observou Gerrit Muller) freqentemente voc ainda no sabe a

GRUPO PYTHON UNESP ILHA SOLTEIRA

11

STEVEN FERG

PENSANDO EM TKINTER

melhor estrutura de classes a usar logo no comeo, voc simplesmente no tem uma idia
clara o suficiente do problema e soluo. Comear a usar classes cedo demais no processo
pode gerar muitas estruturas desnecessrias que s servem para bagunar o cdigo, alm de
geralmente exigir mais consertos.
Tudo depende do gosto pessoal e das circunstncias. Faa o que parecer bem a voc. E
no importa o que voc escolha no se assuste com as reestruturaes quando se tornarem
necessrias.
Comportamento do programa
Quando voc rodar este programa, ele ter uma aparncia exatamente igual anterior. Nada
funcional foi mudado somente como o cdigo foi estruturado. Agora ele est em forma de
classes.
from Tkinter import *
class MyApp:
### (1)
def __init__(self, myParent):
### (1a)
self.myContainer1 = Frame(myParent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1)
self.button1["text"]= "Hello, World!"
self.button1["background"] = "green"
self.button1.pack()
root = Tk()
myapp = MyApp(root)
root.mainloop()

### (2)
### (3)

TT040 Algumas formas diferentes de definir um widget.


(1)
No programa anterior, ns criamos um objeto Button, button1, e ento mudamos seu texto e
cor de fundo mais ou menos assim:
self.button1["text"]= "Hello, World!"
self.button1["background"] = "green"

Neste programa, adicionamos mais trs botes ao Container1, usando mtodos ligeiramente
diferentes.
(2)
Para o boto button2, o processo essencialmente o mesmo que no boto button1, mas em
vez de acessar o dicionrio de Button, usamos o mtodo configure atribudo aos objetos
Button.

GRUPO PYTHON UNESP ILHA SOLTEIRA

12

STEVEN FERG

PENSANDO EM TKINTER

(3)
Para o boto button3, vemos que o mtodo configure pode ter muitos argumentos, ento
designamos vrias opes em uma nica linha.
(4)
Nos exemplos anteriores, fazer um boto era uma tarefa em dois passos: primeiro criar o
boto e ento configurar suas propriedades. Tambm possvel configurar estas
propriedades ao mesmo tempo em que criamos o boto. O widget Button, como qualquer
widget, espera como primeiro argumento o nome do seu mestre. Depois desse argumento
voc pode, se desejar, adicionar um ou mais argumentos sobre as propriedades do widget.
Comportamento do programa
Quando voc rodar este programa, dever ver que o Container1 agora contm, junto com o
antigo boto verde, mais trs botes. Veja como myContainer1 (o frame) se estica para
acomodar todos estes botes.
Perceba tambm que esses botes so empilhados um sobre o outro. No prximo programa,
veremos por que eles se arranjam dessa forma, e veremos como arranj-los de forma
diferente.
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1)
self.button1["text"] = "Hello, World!"
###(1)
self.button1["background"] = "green"
###(1)
self.button1.pack()
self.button2 = Button(self.myContainer1)
self.button2.configure(text="Off to join the circus!") ###(2)
self.button2.configure(background="tan")
###(2)
self.button2.pack()
self.button3 = Button(self.myContainer1)
self.button3.configure(text="Join me?",background="cyan")#(3)
self.button3.pack()
self.button4 = Button(self.myContainer1, text="Goodbye!",
background="red"
) ###(4)
self.button4.pack()
root = Tk()
myapp = MyApp(root)
root.mainloop()

GRUPO PYTHON UNESP ILHA SOLTEIRA

13

STEVEN FERG

PENSANDO EM TKINTER

TT050 Packing.
No ltimo programa, ns vimos quatro botes, empilhados um sobre o outro. Entretanto,
provavelmente gostaramos de v-los lado a lado em alguma ocasio. Neste programa
fazemos isso, e comearemos a ver as possibilidades que pack() oferece.
(1) (2) (3) (4)
Fazer pack um das maneiras de controlar a relao visual entre os componentes da GUI. O
que faremos agora usar a opo side como argumento de pack para colocar os botes
lado a lado, desse jeito:
self.button1.pack(side=LEFT)

Veja que LEFT (assim como RIGHT, TOP e BOTTOM) so constantes de nomes bem
amigveis definidas em Tkinter. Isto , LEFT deveria aparecer no cdigo como
Tkinter.LEFT, mas por causa da nossa maneira de importar o mdulo Tkinter (pgina 6),
no precisamos mais usar o prefixo Tkinter..
Porque os botes apareceram verticalmente no ltimo programa
Como voc se lembra, no ltimo programa ns simplesmente empacotamos os botes sem
especificar a opo side, e os botes ficaram daquela forma, empilhados. Isso aconteceu
porque a opo pr-designada da opo side TOP.
Ento, quando fizemos pack no boto button1, ele foi colocado no topo da cavidade do
frame myContainer1. Ao fazer pack sem argumentos sobre o boto button2, ele tambm
colocado no topo da cavidade deste frame, que neste caso fica exatamente abaixo do boto
button1, e assim por diante.
Se ns tivssemos feito pack nos botes em ordem diferente por exemplo, se tivssemos
feito pack em button2 primeiro, e depois em button1 suas posies teriam sido invertidas:
o boto button2 estaria em cima.
Ento, como voc pode ver, uma das maneiras de controlar como sua GUI vai se parecer
controlando a ordem de fazer pack em cada um dos widgets dentro do container.
Alguns termos tcnicos Orientao
Orientao vertical inclui os lados TOP (de cima) e BOTTOM (de baixo).
Orientao horizontal inclui os lados LEFT (esquerdo) e RIGHT (direito).
Quando voc est empacotando widgets e containeres, possvel mesclar dois tipos de
orientao. Por exemplo, podemos precisar posicionar um boto com orientao vertical
(como TOP) e outro com orientao horizontal (como LEFT), mas fazer isso misturando
orientaes dentro do container no uma boa idia. Se voc misturar orientaes poder
prejudicar a maneira como os objetos aparecero na tela, alm da bela surpresa que ter se
precisar redimensionar a janela depois.

GRUPO PYTHON UNESP ILHA SOLTEIRA

14

STEVEN FERG

PENSANDO EM TKINTER

Por estes motivos uma boa prtica de projeto nunca misturar orientaes, mas sem voc
precisar mesmo fazer isso, melhor usar um container dentro do outro. Exploraremos este
tpico no prximo programa.
Comportamento do programa
Quando voc rodar este programa, ver quatro botes lado a lado.
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1)
self.button1["text"]= "Hello, World!"
self.button1["background"] = "green"
self.button1.pack(side=LEFT) ### (1)
self.button2 = Button(self.myContainer1)
self.button2.configure(text="Off to join the circus!")
self.button2.configure(background="tan")
self.button2.pack(side=LEFT)
### (2)
self.button3 = Button(self.myContainer1)
self.button3.configure(text="Join me?", background="cyan")
self.button3.pack(side=LEFT)
### (3)
self.button4 = Button(self.myContainer1, text="Goodbye!",
background="red"
)
self.button4.pack(side=LEFT)
### (4)
root = Tk()
myapp = MyApp(root)
root.mainloop()

TT060 Binding.
Agora hora de colocar nossos botes para trabalhar. Chamamos sua ateno para as duas
ltimas (das quatro) questes bsicas da programao GUI escrever procedimentos de
alimentao de eventos para fazer os trabalhos necessrios no seu programa, e ligar estes
procedimentos a widgets e eventos.
Perceba que neste programa rejeitamos todos os botes que tnhamos criado no ltimo
programa, e retornamos a uma GUI muito simples contendo dois botes: OK e
CANCEL.

GRUPO PYTHON UNESP ILHA SOLTEIRA

15

STEVEN FERG

PENSANDO EM TKINTER

Voc deve se lembrar da discusso de nosso primeiro programa, de que uma das questes
bsicas da programao GUI o binding. Binding o processo de definir a conexo ou
relao (geralmente) entre:

um widget;
um tipo de evento e
um alimentador de eventos.

Um alimentador de eventos um mtodo ou sub-rotina que alimenta eventos quando eles


ocorrem. Talvez ajude dizer que em Java, alimentadores de eventos so chamados
listeners (vigilantes), um nome bem sugestivo considerando sua funo estar atento aos
eventos e responder a eles.
Em Tkinter, uma maneira de criar bindings pelo mtodo bind(), da seguinte forma:
widget.bind(nome_do_tipo_de_evento,nome_do_alimentador_de_eventos)

Esse tipo de ligao chamado event binding.


H outro jeito de ligar um alimentador de eventos a um widget, chamado command
binding e ns a veremos em alguns programas daqui pra frente. Por ora, vejamos melhor o
event binding, e tendo entendido este, ser moleza explicar o command binding.
Antes de comearmos, vamos esclarecer uma coisa: a palavra boto pode ser usada para
designar duas coisas inteiramente diferentes:

um widget, Button uma GUI que mostrada no monitor do computador e


um boto no seu mouse aquele que voc pressiona com o dedo.

Para evitar confuso, procuraremos distingui-los como o boto e o boto do mouse, em


vez de simplesmente boto.
(1)
Ns ligamos o evento <Button-1> (um clique com o boto esquerdo do mouse) sobre o
boto button1 ao mtodo self.button1Click. Quando button1 clicado com o boto
esquerdo do mouse, o mtodo self.button1Click() chamado para alimentar o evento.
(3)
Veja que, embora no tenhamos especificado na operao de binding, self.button1Click
() receber dois argumentos. O primeiro, claro, ser self, primeiro argumento para
qualquer mtodo de classes em Python. O segundo ser um objeto evento. Esta tcnica de
binding e evento isto , usando o mtodo bind() sempre implica na utilizao de um
objeto evento como argumento.
Em Python/Tkinter, quando um evento ocorre, toma forma de um objeto evento. Um objeto
evento extremamente til porque carrega consigo uma coleo de informaes teis e

GRUPO PYTHON UNESP ILHA SOLTEIRA

16

STEVEN FERG

PENSANDO EM TKINTER

mtodos. Voc pode examinar o objeto evento para encontrar que tipo de evento ocorreu, o
widget onde ocorreu, e outras informaes teis.
(4)
Ento, o que acontece quando button1 clicado? Bem, neste caso ns o designamos para
fazer algo bem simples: mudar sua cor de verde para amarelo e vice-versa.
(2)
Vamos fazer o button2 (o boto do Tchau!) fazer algo mais til: fechar a janela. Para isso
ligamos o evento de clicar com boto esquerdo do mouse sobre o button2 ao mtodo
button2Click() e
(6)
Fazemos o mtodo button2Click() destruir a janela raiz, a mestra de myapp. Isso vai ter
um efeito bem devastador, porque todos os escravos tambm sero destrudos. Na verdade,
toda a GUI ser destruda.
claro que para isso, myapp tem que saber quem sua mestra (aqui, a janela raiz). Ento
adicionamos um cdigo (7) ao construtor da classe para lembr-la disso.
Comportamento do programa
Quando voc roda este programa, v dois botes. Clicar no boto OK o faz mudar de cor.
Clicar no boto Cancel fecha a aplicao.
Quando a GUI aberta, se voc apertar TAB, ver que o foco do teclado vai ficar pulando
entre os dois botes, embora apertar ENTER no teclado no faa nada. Isso porque temos
ligado somente cliques de mouse aos nossos botes, no eventos de teclado. Os prximos
tpicos falaro deste assunto.
Finalmente, observamos que os botes tm tamanhos diferentes porque os textos que
contm tm tamanhos diferentes. S por causa disso. No fica muito elegante assim, por
isso consertaremos esse problema no prximo programa.
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.myParent = parent ### (7) lembra seu mestre, a raiz
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1)
self.button1.configure(text="OK", background= "green")
self.button1.pack(side=LEFT)
self.button1.bind("<Button-1>", self.button1Click) ### (1)
self.button2 = Button(self.myContainer1)
self.button2.configure(text="Cancel", background="red")

GRUPO PYTHON UNESP ILHA SOLTEIRA

17

STEVEN FERG

PENSANDO EM TKINTER

self.button2.pack(side=RIGHT)
self.button2.bind("<Button-1>", self.button2Click) ### (2)
def button1Click(self, event):
### (3)
if self.button1["background"] == "green": ### (4)
self.button1["background"] = "yellow"
else:
self.button1["background"] = "green"
def button2Click(self, event):
self.myParent.destroy()

### (5)
### (6)

root = Tk()
myapp = MyApp(root)
root.mainloop()

TT070 Mexendo com foco e ligando eventos de teclado a widgets.


No programa anterior, voc pde fazer botes fazerem alguma coisa clicando neles com o
mouse, mas no pde faz-los trabalhar pressionando teclas no teclado. Neste programa,
veremos como faz-los reagir a eventos de teclado como reagiram aos de mouse.
Primeiramente, precisamos definir o que vem a ser foco.
Se voc tem alguma intimidade com a mitologia grega (ou se voc viu Hrcules, aquele
filme da Disney), se lembrar das Fates. As Fates eram trs velhas que controlavam o
destino dos homens. Cada humano vivo era uma linha em suas mos, e quando uma delas
cortava a linha, a vida do humano terminava.

As Fates

Um fato notvel sobre as Fates era que havia somente um olho para as trs. A que estava
com o olho tinha que dizer para as outras duas tudo o que estava vendo. O olho poderia

GRUPO PYTHON UNESP ILHA SOLTEIRA

18

STEVEN FERG

PENSANDO EM TKINTER

passar de uma Fate a outra, assim elas podiam se revezar entre ver ou no. claro, se voc
conseguisse roubar esse olho, teria algo valiosssimo para barganhar o que quisesse com
elas.
Foco o que permite aos widgets da sua GUI ver os eventos de teclado. O foco est para
os widgets da sua GUI como o olho estava para as Fates. Somente um widget por vez pode
ter o foco, e o widget que o tem o que v, e responde a, os eventos de teclado.
Neste programa, por exemplo, nossa GUI tem dois botes, OK e Cancel. Supondo que
eu pressione o boto ENTER no teclado, ser que ele ser visto pelo boto OK indicando
que o usurio est confirmando sua escolha? Ou ser que o ato de pressionar esta tecla ser
visto pelo boto Cancel, indicando que o usurio est cancelando (e destruindo) a
aplicao? Tudo isso depende de onde est o foco. Isto , depende de que boto tem foco.
Como as Fates, que passavam seu olho de uma a outra, o foco pode ser passado de um
widget na GUI para outro. H vrias formas de fazer isso. Uma delas clicando no widget
com o boto esquerdo do mouse (esse modo funciona em Windows e Macintosh, em Tk e
Tkinter. H alguns sistemas que usam a conveno foco segue o mouse, em que o widget
que est sob o mouse tem foco automaticamente no necessrio clicar)
Outra maneira de mudar o foto usando o fato de que os widgets so adicionados a uma
lista conforme so criados. Pressionando a tecla TAB, o foco se move do widget atual para
o prximo da lista. O widget seguinte ao ltimo o primeiro. Pressionando SHIFT+TAB, o
foco se move para o item anterior da lista.
Quando um boto GUI tem foco, isso indicado por uma caixinha de pontos em torno do
texto do boto. Para ver isso, rode o programa anterior. Quando o programa comea,
nenhum dos botes tem foco, por isso nenhum deles tem a caixa de pontos. Pressione TAB
e voc ver essa caixa em torno do texto do boto esquerdo, mostrando que o foco est
sobre ele. Agora pressione vrias vezes TAB e observe o foco pular de um boto a outro
repetidamente.
(0)
Neste programa, queremos que o boto OK tenha foco desde o comeo. Para isso usamos o
mtodo focus_force(), que fora o foco a comear no boto OK. Quando voc roda o
programa, ver a caixa de pontos sobre o boto OK desde que a aplicao se abrir.
No ltimo programa, nossos botes respondiam ao evento de teclado pressionar a tecla
TAB, que movia o foco entre os botes, mas ao pressionar ENTER, nada acontecia. Isso
porque s foram ligados cliques de mouse, no eventos de teclado, aos botes.
Neste programa ns iremos ligar tambm alguns eventos de teclado aos botes.

GRUPO PYTHON UNESP ILHA SOLTEIRA

19

STEVEN FERG

PENSANDO EM TKINTER

(1)(2)
As linhas para ligar eventos de teclado aos botes so bem simples elas tm o mesmo
formato que aquelas que ligam eventos de mouse. A nica diferena que o nome do
evento agora o nome de um evento de teclado (neste caso, <Return2>) .
Quando queremos que a tecla ENTER ou um clique no boto esquerdo do mouse tenham o
mesmo efeito no widget, ligamos o mesmo alimentador de eventos a ambos eventos.
Este programa mostra que voc pode ligar vrios tipos de eventos a um nico widget (como
um boto), ou mesmo ligar vrias combinaes de widgets a um mesmo alimentador de
eventos.
(3)(4)
Agora que nossos botes respondem a vrios tipos de eventos, podemos demonstrar como
receber informaes de um objeto evento. O que vamos fazer passar o objeto evento para
(5) a rotina report_event que ir (6) imprimir uma informao sobre o evento, obtida a
partir dos atributos do evento.
Perceba que para vermos estas informaes impressas na tela, necessrio rodar o
programa usando python (nunca pythonw como IDLE).
Comportamento do programa
Quando voc roda o programa, v dois botes. Clicando no boto da esquerda, ou
pressionando ENTER quando o foco estiver neste boto, a sua cor ser mudada. Clicando
no boto direito, ou pressionando ENTER quando o foco estiver neste boto, a aplicao
ser fechada. Para qualquer um desses eventos de mouse ou teclado, voc dever ver uma
mensagem impressa na tela informando a hora e descrevendo o evento.
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1)
self.button1.configure(text="OK", background= "green")
self.button1.pack(side=LEFT)
self.button1.focus_force()
### (0)
self.button1.bind("<Return>", self.button1Click)
self.button1.bind("<Return>", self.button1Click) ### (1)
self.button2 = Button(self.myContainer1)
self.button2.configure(text="Cancel", background="red")
self.button2.pack(side=RIGHT)
self.button2.bind("<Return>", self.button2Click)
2

Os americanos chamam a tecla ENTER de RETURN, da o evento de teclado pressionar ENTER receber a
sintaxe <Return>. (Nota do Tradutor)

GRUPO PYTHON UNESP ILHA SOLTEIRA

20

STEVEN FERG

PENSANDO EM TKINTER

self.button2.bind("<Return>", self.button2Click)

### (2)

def button1Click(self, event):


report_event(event)
### (3)
if self.button1["background"] == "green":
self.button1["background"] = "yellow"
else:
self.button1["background"] = "green"
def button2Click(self, event):
report_event(event)
### (4)
self.myParent.destroy()
def report_event(event):
### (5)
"""Imprime a descrio de um evento, baseado em seus atributos.
"""
event_name = {"2": "KeyPress", "4": "ButtonPress"}
print "Time:", str(event.time)
### (6)
print "EventType=" + str(event.type), \
event_name[str(event.type)],\
"EventWidgetId=" + str(event.widget), \
"EventKeySymbol=" + str(event.keysym)
root = Tk()
myapp = MyApp(root)
root.mainloop()

TT074 Command Binding.


H alguns programas atrs, introduzimos o conceito de event binding. H outro jeito de
ligar um alimentador de eventos a um widget chamado command binding. Falaremos
sobre isso neste programa.
Command Binding
Voc deve se lembrar que nos nossos programas anteriores, ns ligamos o evento de mouse
<Button-1> ao widget boto. Button outro nome para um evento de mouse
ButtonPress, que diferente do evento de mouse ButtonRelease3. O evento
ButtonPress o ato de pressionar o boto do mouse, mas sem solt-lo, e ButtonRelease
e o ato de soltar o boto, deixando-o retornar sua posio inicial.
Temos que distinguir ButtonPress de ButtonRelease para podermos explicar manipulaes
como arrastar e soltar, nas quais fazemos um ButtonPress (pressionamos o boto do
mouse sem soltar) sobre um widget, o arrastamos para algum lugar, e ento o soltamos
(ButtonRelease).
Os widgets Button, no entanto, no podem ser arrastados e soltos. Se um usurio quiser
arrastar e soltar um boto, ele precisar executar um ButtonPress sobre o boto e arrast-lo
com o ponteiro do mouse para um lugar qualquer na tela, e ento soltar o boto. Isso no
3

Release = Liberar, soltar (N do T).

GRUPO PYTHON UNESP ILHA SOLTEIRA

21

STEVEN FERG

PENSANDO EM TKINTER

um tipo de atividade que ns chamamos de pressionar (ou em termos tcnicos


invocar) um widget Button. S considerada uma invocao do boto quando o
usurio faz um ButtonPress no widget, sem arrast-lo, e executa imediatamente
ButtonRelease.
Esta uma noo mais complicada de invocao de botes do que ns temos usado at
agora, onde simplesmente ligamos o evento Button-1 ao widget boto usando event
binding.
Por sorte, h outra forma de fazer binding que suporta essa noo mais complicada de
invocao de widgets. o command binding.
Neste programa, veja as linhas com comentrios (1) e (2) para ver como o command
binding feito. Nestas linhas, usamos a opo command para ligar o button1 ao
alimentador de eventos self.button1Click, e para ligar o button2 ao alimentador de
eventos self.button2Click.
(3)(4)
D uma olhada em como os alimentadores de eventos se definem. Veja que diferente dos
alimentadores dos programas anteriores eles no esperam por um objeto evento como
argumento. Isso porque o command binding, diferente do event binding, no faz
automaticamente a passagem do objeto evento como um argumento. Isso faz sentido porque
o command binding no liga um nico evento a um alimentador, e sim mltiplos eventos.
Para um boto, por exemplo, ele liga o ButtonPress seguido de um ButtonRelease a um
alimentador.
Daremos uma olhada um pouco melhor nas diferenas entre event binding e command
binding no prximo programa, mas por ora, rodemos o programa.
Comportamento do programa
Quando voc roda o programa, o boto que aparece exatamente igual ao dos programas
anteriores... s na aparncia.
Para comparar seu comportamento com os botes anteriores, leve o ponteiro do mouse at o
boto esquerdo e clique, mas no solte o boto do mouse.
Se fosse feito isso no programa anterior, imediatamente o boto mudaria de cor e a
mensagem seria impressa. Neste, nada acontece... at que o boto do mouse seja solto. A
sim, o boto muda de cor e a mensagem impressa.
H tambm outra diferena. Compare seu comportamento quando voc pressiona a barra de
espao e ENTER. Por exemplo, use TAB para focar o boto OK e pressione espao ou
ENTER.
No programa anterior (onde ligamos o boto OK ao evento de pressionar ENTER),
pressionar a barra de espao no tinha efeito, mas pressionar ENTER fazia o boto mudar

GRUPO PYTHON UNESP ILHA SOLTEIRA

22

STEVEN FERG

PENSANDO EM TKINTER

de cor. Neste programa, por outro lado, o comportamento exatamente o oposto


pressionando a barra de espaos faz o boto mudar de cor, enquanto ENTER no tem efeito
algum.
Vamos verificar melhor estes comportamentos no nosso prximo programa.
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1,
command=self.button1Click
) ### (1)
self.button1.configure(text="OK", background= "green")
self.button1.pack(side=LEFT)
self.button1.focus_force()
self.button2 = Button(self.myContainer1,
command=self.button2Click
) ### (2)
self.button2.configure(text="Cancel", background="red")
self.button2.pack(side=RIGHT)
def button1Click(self): ### (3)
print "button1Click event handler"
if self.button1["background"] == "green":
self.button1["background"] = "yellow"
else:
self.button1["background"] = "green"
def button2Click(self): ### (4)
print "button2Click event handler"
self.myParent.destroy()
root = Tk()
myapp = MyApp(root)
root.mainloop()

TT075 Usando event binding e command binding juntos.


No programa anterior, introduzimos o conceito de command binding e delimitamos
algumas das diferenas com event binding. Neste, exploraremos suas diferenas um pouco
mais detalhadamente.
Para quais eventos serve command bind?
No programa anterior, se voc usar TAB para focar o boto OK e pressionar a barra de
espao, o boto mudar de cor, mas pressionar ENTER no tem efeito algum.

GRUPO PYTHON UNESP ILHA SOLTEIRA

23

STEVEN FERG

PENSANDO EM TKINTER

A razo para isto que a opo command d a um widget Button distino sobre eventos
de teclado, assim como eventos de mouse. Neste caso, o evento de teclado aguardado pelo
boto o acionamento da barra de espao, no da tecla ENTER. Isso quer dizer que, com
command binding, pressionar a barra de espaos faz o boto OK mudar de cor, enquanto
ENTER no tem efeito.
Este comportamento parece (ao menos para mim, usurio Windows) incomum. Parte da
moral da histria aqui que se voc est fazendo uso de command binding, uma boa idia
entender exatamente o que voc quer que seu binding faa. Isto , uma boa idia entender
exatamente que eventos de mouse e/ou teclado devero causar os comandos desejados.
Infelizmente, a nica fonte confivel dessa informao o cdigo Tk em si. Para
informaes mais acessveis, d uma olhada nos livros sobre Tk (Practical Programming
in Tcl and Tk, de Brent Welch, especialmente bom) ou sobre Tkinter. A documentao
sobre Tk difusa, mas disponvel on-line.
Voc deve tambm saber que nem todos os widgets oferecem a opo command. Muitos
dos widgets Button o fazem (RadioButton, CheckButton, etc.) e outros oferecem opes
similares (por exemplo, scrollcommand), mas voc realmente deve investigar cada um dos
diferentes tipos de widgets para saber se suportam ou no command binding. Isso dar um
controle maior sobre o comportamento da sua GUI e tornar mais fcil sua vida de
programador.
Usando Event Binding e Command Binding juntos.
Percebemos no ltimo programa que command binding, diferente de event binding, no faz
automaticamente a passagem evento objeto argumento. Isso pode tornar sua vida um
pouco complicada se voc deseja ligar um alimentador de eventos a um widget usando
simultaneamente event binding e command binding.
Por exemplo, neste programa realmente desejaremos que nossos botes respondam to bem
ativao da tecla ENTER quando da barra de espaos. Para isso, teremos que usar event
binding para o evento de teclado <Return>, como foi feito no ltimo programa (1).
O problema que o command binding no transformar um objeto evento num argumento,
mas o event binding sim. Ento como deveremos escrever nosso alimentador de eventos?
H algumas solues para este problema, mas a mais simples escrever dois alimentadores
de eventos. O alimentador real (2) ser o nico usado pelo command binding, que no
esperar por um objeto evento.
O outro alimentador (3) vai ser s um wrapper do real. Este wrapper esperar o argumento
do objeto evento, mas o ignorar e chamar o alimentador real. Daremos ao wrapper o
mesmo nome do alimentador real, porm adicionando o sufixo _a.

GRUPO PYTHON UNESP ILHA SOLTEIRA

24

STEVEN FERG

PENSANDO EM TKINTER

Comportamento do programa
Se voc rodar este programa, o comportamento ser o mesmo do programa anterior, exceto
pelo fato de que agora os botes iro responder tanto ao ENTER quando barra de espaos.
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1,
command=self.button1Click
)
self.button1.bind("<Return>", self.button1Click_a) ### (1)
self.button1.configure(text="OK", background= "green")
self.button1.pack(side=LEFT)
self.button1.focus_force()
self.button2 = Button(self.myContainer1,
command=self.button2Click
)
self.button2.bind("<Return>", self.button2Click_a) ### (1)
self.button2.configure(text="Cancel", background="red")
self.button2.pack(side=RIGHT)
def button1Click(self): ### (2)
print "button1Click event handler"
if self.button1["background"] == "green":
self.button1["background"] = "yellow"
else:
self.button1["background"] = "green"
def button2Click(self): ### (2)
print "button2Click event handler"
self.myParent.destroy()
def button1Click_a(self, event): ### (3)
print "button1Click_a event handler (a wrapper)"
self.button1Click()
def button2Click_a(self, event): ### (3)
print "button2Click_a event handler (a wrapper)"
self.button2Click()
root = Tk()
myapp = MyApp(root)
root.mainloop()

TT076 Compartilhando informaes entre alimentadores de eventos.


Nos ltimos programas, exploramos maneiras de fazer nossos programas realmente
trabalhar com alimentadores de eventos. Agora daremos uma olhadinha em como
compartilhar informaes entre estes alimentadores.
GRUPO PYTHON UNESP ILHA SOLTEIRA

25

STEVEN FERG

PENSANDO EM TKINTER

Compartilhando informaes entre funes alimentadoras de eventos.


H uma variedade de situaes nas quais voc pode querer que um alimentador de eventos
realize alguma tarefa e divida os resultados desta tarefa com outros alimentadores em seu
programa.
Um exemplo comum quando voc tem uma aplicao com dois grupos de widgets. Um
deles seleciona alguma parte da informao, e ento o outro faz alguma coisa com esta
informao.
Por exemplo, talvez voc queira ter um widget que permita a um usurio escolher um nome
de arquivo de uma lista, e outro conjunto de widgets que oferea vrias operaes sobre o
arquivo escolhido abrir, deletar, copiar, renomear, etc.
Ou voc pode ter uma srie de widgets que modifique vrias configuraes da sua
aplicao, e outra srie de widgets (oferecendo opes de SALVAR e CANCELAR, talvez)
que salve em disco estas modificaes de configurao ou as cancele sem salvar.
Quem sabe ainda um conjunto de widgets que mudem alguns parmetros de um programa
que voc deseje rodar, enquanto outro widget (provavelmente um boto com um nome
RODAR ou EXECUTAR) que rode o programa considerando os parmetros que voc
escolheu.
Voc pode precisar tambm requerer de uma funo alimentadora de eventos o
reconhecimento de alguma informao de uma execuo anterior da mesma funo.
Considere um alimentador que simplesmente alterna uma varivel entre dois diferentes
valores. Para que ela assimile um novo valor da varivel, ter que saber qual valor atribuiu
varivel da ltima vez que rodou.
O problema
O problema aqui que cada um dos alimentadores uma funo separada. Cada um deles
tem suas prprias variveis locais que no fazem parte das outras funes alimentadoras,
nem mesmo de invocaes anteriores delas mesmas. Eis o problema: como pode uma
funo alimentadora de eventos partilhar dados com outros alimentadores, se ela no pode
partilhar suas variveis locais com eles?
A soluo, claro, que as variveis que precisamos compartilhar no sejam locais s
funes alimentadoras de eventos. Elas precisam ser armazenadas fora destas funes.
Primeira soluo usar variveis globais
Uma tcnica para conseguir isto fazer delas (as variveis que queremos compartilhar)
variveis globais. Por exemplo, em cada alimentador que precise modificar ou ver
minhaVariavel1 e minhaVariavel2, voc pode escrever o seguinte:
global minhaVariavel1, minhaVariavel2

GRUPO PYTHON UNESP ILHA SOLTEIRA

26

STEVEN FERG

PENSANDO EM TKINTER

Cuidado: o uso de variveis globais potencialmente perigoso por causa de conflitos, e


geralmente reservado para programas pequenos.
Segunda soluo usar variveis instanciadas
Uma boa tcnica usar variveis instanciadas (isto , self.nome_da_variavel) para
trocar informaes que voc quer compartilhar entre os alimentadores de eventos. Para
fazer isso, claro, sua aplicao dever ser implementada em uma classe, e no num
simples cdigo estruturado.
Este uma das razes pelas quais ns desenvolvemos as aplicaes deste tutorial em forma
de classes. por termos comeado neste formato logo de incio que neste ponto nossa
aplicao j temos uma infra-estrutura que nos permitir usar variveis instanciadas.
Neste programa, iremos compartilhar uma informao bem simples: o nome do ltimo
boto pressionado. Armazenaremos esta informao numa varivel instanciada chamada
self.myLastButtonInvoked (veja o comentrio ### 1).
Para mostrar que realmente estamos lembrando desta informao, toda vez que o
alimentador do boto for solicitado, ela ser impressa (veja o comentrio ### 2).
Comportamento do programa
Este programa mostra trs botes. Quando voc coloc-lo para rodar, se voc clicar em
qualquer um dos botes, ser mostrado seu prprio nome, e o nome do boto que foi
clicado anteriormente.
Perceba que nenhum dos botes fechar a aplicao, ento se voc desejar fech-la, dever
clicar no widget FECHAR (o cone com um X em uma caixa, do lado direito da barra de
ttulo).
from Tkinter import *
class MyApp:
def __init__(self, parent):
### 1 Ainda no solicitamos o alimentador do boto
self.myLastButtonInvoked = None
self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.yellowButton=Button(self.myContainer1,
command=self.yellowButtonClick
)
self.yellowButton.configure(text="YELLOW",
background="yellow"
)
self.yellowButton.pack(side=LEFT)

GRUPO PYTHON UNESP ILHA SOLTEIRA

27

STEVEN FERG

PENSANDO EM TKINTER

self.redButton=Button(self.myContainer1,
command=self.redButtonClick
)
self.redButton.configure(text="RED", background="red")
self.redButton.pack(side=LEFT)
self.whiteButton=Button(self.myContainer1,
command=self.whiteButtonClick
)
self.whiteButton.configure(text="WHITE",background="white")
self.whiteButton.pack(side=LEFT)
def redButtonClick(self):
print "RED
button clicked. Previous button invoked was",
self.myLastButtonInvoked ### 2
self.myLastButtonInvoked = "RED" ### 1
def yellowButtonClick(self):
print "YELLOW button clicked. Previous button invoked was",
self.myLastButtonInvoked ### 2
self.myLastButtonInvoked = "YELLOW" ### 1
def whiteButtonClick(self):
print "WHITE button clicked. Previous button invoked was",
self.myLastButtonInvoked ### 2
self.myLastButtonInvoked = "WHITE" ### 1
print "\n"*100 # um jeito simples de limpar a tela
print "Starting..."
root = Tk()
myapp = MyApp(root)
root.mainloop()
print "... Done!"

TT077 Transmitindo argumentos para alimentadores de eventos I


O problema
Neste programa exploraremos um pouco
Caractersticas mais avanadas de command binding
No programa tt075.py, usamos a opo command para ligar um alimentador de eventos a
um widget. Por exemplo, neste programa a linha
self.button1 = Button(self.myContainer1, command=self.button1Click)

ligou a funo button1Click ao widget button1.


Tambm usamos event binding para ligar nossos botes ao evento de teclado <Return>.
self.button1.bind("<Return>", self.button1Click_a)

GRUPO PYTHON UNESP ILHA SOLTEIRA

28

STEVEN FERG

PENSANDO EM TKINTER

Em um dos ltimos programas, os alimentadores de eventos para os dois botes realizaram


duas funes diferentes.
Mas suponha que a situao seja diferente. Suponha que temos diversos botes, todos eles
desenvolvendo essencialmente o mesmo tipo de ao. A melhor maneira de alimentar este
tipo de situao ligar os eventos de todos os botes a um nico alimentador de eventos.
Cada um dos botes dever invocar a mesma rotina alimentadora, mas fornecendo a ela
diferentes argumentos contando o que fazer.
isso que fazemos neste programa.
Command binding
Neste programa, como voc pode ver, temos dois botes, e usamos a opo command
para lig-los todos ao mesmo alimentador de eventos a rotina buttonHandler.
Fornecemos a esta rotina trs argumentos: o nome do boto (na varivel button_name), um
nmero e uma string.
self.button1=Button(self.myContainer1,
command=self.buttonHandler(button_name,1,
"Good stuff!"
)
)

Em aplicaes srias, a rotina buttonHandler iria, claro, trabalhar seriamente, mas neste
programa ela meramente imprime os argumentos que recebeu.
Event binding
Chega de command binding. O que diremos sobre event binding?
Voc perceber que comentamos as duas linhas que fazem event binding no evento
<Return>.
self.button1.bind("<Return>",self.buttonHandler_a(event,
button_name, 1,
"Good stuff!"
)
)

Este o primeiro sinal de um problema. O event binding automaticamente transmite o


argumento de evento mas simplesmente no h como incluir que o argumento de evento
em nossa lista de argumentos.
Teremos que voltar a este problema mais tarde. Por ora, vamos simplesmente rodar o
programa e ver o que acontece.

GRUPO PYTHON UNESP ILHA SOLTEIRA

29

STEVEN FERG

PENSANDO EM TKINTER

Comportamento do programa
Vendo o cdigo, o programa at que no parece to mal, mas quando voc o roda, percebe
que ele no trabalha direito. A rotina buttonHandler solicitada antes mesmo que a GUI
seja exibida. Na verdade, ela solicitada duas vezes!
Se voc clicar com o boto esquerdo do mouse em qualquer boto, descobrir que nada
acontece a rotina eventHandler no est sendo solicitada.
Veja que a nica maneira de fechar o programa clicando no cone FECHAR (o X na
caixa) no lado direito da barra de ttulos.
Ento rode o programa agora, e veja o que acontece. No prximo programa veremos porque
isso acontece.
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
button_name = "OK"
self.button1=Button(self.myContainer1,
command=self.buttonHandler(button_name,
1,"Good stuff!"
)
)
# self.button1.bind("<Return>", self.buttonHandler_a(event,
# button_name, 1, "Good stuff!"))
self.button1.configure(text=button_name, background="green")
self.button1.pack(side=LEFT)
self.button1.focus_force() # Foca o teclado em button1
button_name = "Cancel"
self.button2=Button(self.myContainer1,
command=self.buttonHandler(button_name,
2,"Bad stuff!"
)
)
# self.button2.bind("<Return>",self.buttonHandler_a(event,
# button_name, 2, "Bad stuff!"))
self.button2.configure(text=button_name, background="red")
self.button2.pack(side=LEFT)
def buttonHandler(self, arg1, arg2, arg3):
print "buttonHandler routine received arguments:", arg1.ljust
(8), arg2, arg3
def buttonHandler_a(self, event, arg1, arg2, arg3):
print "buttonHandler_a received event", event
self.buttonHandler(arg1, arg2, arg3)

GRUPO PYTHON UNESP ILHA SOLTEIRA

30

STEVEN FERG

PENSANDO EM TKINTER

print "\n"*100 # limpa a tela


print "Starting program tt077."
root = Tk()
myapp = MyApp(root)
print "Ready to start executing the event loop."
root.mainloop()
print "Finished
executing the event loop."

TT078 Transmitindo argumentos para alimentadores de eventos II


Usando Lambda
Vendo a execuo do ltimo programa, nos perguntamos: O que est acontecendo aqui???
A rotina buttonHandler est sendo executada por cada um dos botes, mesmo antes do
event loop entrar em ao!
A razo que uma linha como
self.button1 = Button(self.myContainer1,
command = self.buttonHandler(button_name,
1, "Good stuff!"
)
)

foi chamada de funo buttonHandler, embora fosse melhor t-la chamado de rechamada.
Embora no seja o que pretendamos, o que realmente acontece.
Veja que:

buttonHandler um objeto funo, e pode ser usado como uma ligao de


rechamada.
buttonHandler() (viu os parnteses?), por outro lado, uma chamada real da funo
buttonHandler.

Quando a linha
self.button1 = Button(self.myContainer1,
command = self.buttonHandler(button_name,
1, "Good stuff!"
)
)

executada, ela est na verdade fazendo a chamada da rotina buttonHandler. A rotina


ento executada, imprimindo uma mensagem, e retornando o resultado da chamada (neste
caso, o objeto None). A a opo command do boto ligada ao resultado da chamada.
Em resumo, command ligada ao objeto None. por causa disso que, quando voc
clica em qualquer um dos botes, nada acontece.

GRUPO PYTHON UNESP ILHA SOLTEIRA

31

STEVEN FERG

PENSANDO EM TKINTER

H uma soluo?
Ento... qual a soluo? H algum meio de parametrizar, e reutilizar, uma funo
alimentadora de eventos?
Sim. H uma srie de tcnicas reconhecidas de fazer isso. Uma delas usa a funo Lambda,
uma funo built-in de Python, e outra usa uma tcnica chamada de currying.
Neste programa discutiremos como trabalhar com lambda, e no prximo programa daremos
uma olhada em currying.
No vou tentar explicar como lambda e currying trabalham isso muito complicado e est
bem longe do nosso objetivo principal, que ter programas Tkinter rodando. Vamos ento
simplesmente trat-las como caixas-pretas. No vou dizer como elas trabalham s como
trabalhar com elas.
Vejamos lambda.
Command binding
H pouco tempo, achvamos que a seguinte linha devesse funcionar:
self.button1 = Button(self.myContainer1,
command = self.buttonHandler(button_name,
1, "Good stuff!"
)
)

mas j descobrimos que ela no faz o que pensamos que faria.


O jeito de fazer o que queremos reescrever esta linha da seguinte forma:
self.button1 = Button(self.myContainer1,
command = lambda,
arg1=button_name,arg2=1,
arg3="Good Stuff!":self.buttonHandler(arg1,
arg2,
arg3)
)

Event binding
Felizmente, lambda tambm nos d um jeito de parametrizar event binding. Em vez de:
self.button1.bind("<Return>",
self.buttonHandler_a(event, button_name, 1, "Good stuff!")
)

(que no funcionaria porque no possvel incluir argumentos de evento dentro da lista de


argumentos), podemos usar lambda, dessa forma:

GRUPO PYTHON UNESP ILHA SOLTEIRA

32

STEVEN FERG

PENSANDO EM TKINTER

# event binding transmitindo o evento como um argumento


self.button1.bind("<Return>",
lambda
event, arg1=button_name, arg2=1, arg3="Good stuff!" :
self.buttonHandler_a(event, arg1, arg2, arg3)
)

(Veja que event aqui o nome de uma varivel no uma keyword de Python ou algo
que o valha. Este exemplo usa o nome event para o argumento de evento; alguns autores
usam o nome e para isso, mas tanto faz. Poderamos ter escrito event_arg, se
quisssemos.)
Uma das caractersticas mais elegantes quando se usa lambda que podemos (se
quisermos), simplesmente no transmitir o argumento de evento. Se fizermos isso,
poderemos ento chamar a funo self.buttonHandler diretamente, em vez de indiretamente
pela funo self.buttonHandler_a.
Para ilustrar esta tcnica, iremos programar o event binding do button2 diferente do
button1.
Eis o que fizemos com o segundo boto:
# event binding sem transmitir o evento como um argumento
self.button2.bind("<Return>",
lambda
event, arg1=button_name, arg2=2, arg3="Bad stuff!" :
self.buttonHandler(arg1, arg2, arg3)
)

Comportamento do programa
Rodando o programa, ele se comportar exatamente como queremos.
Veja que voc pode mudar o foco de teclado entre os botes OK e CANCEL pressionando
a tecla TAB.
Particularmente, tente ativar OK pressionando ENTER. Fazendo isso voc estar
colocando-o para trabalhar pela funo buttonHandler_a, e tambm receber uma
mensagem impressa, informando sobre qual evento foi recebido por ele.
Em qualquer caso, tanto clicando em um dos botes com o mouse, ou solicitando um
widget via ENTER, o programa imprimir direitinho os argumentos que foram transmitidos
pela funo buttonHandler.
from Tkinter import *
class MyApp:
def __init__(self, parent):

GRUPO PYTHON UNESP ILHA SOLTEIRA

33

STEVEN FERG

PENSANDO EM TKINTER

self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
#------------------ BOTO N. 1 ----------------------button_name = "OK"
# command binding
self.button1 = Button(self.myContainer1,
command = lambda
arg1=button_name, arg2=1, arg3="Good stuff!" :
self.buttonHandler(arg1, arg2, arg3)
)
# event binding transmitindo o evento como um argumento
self.button1.bind("<Return>",
lambda
event, arg1=button_name, arg2=1, arg3="Good stuff!" :
self.buttonHandler_a(event, arg1, arg2, arg3)
)
self.button1.configure(text=button_name, background="green")
self.button1.pack(side=LEFT)
self.button1.focus_force() # pe o foco de teclado em button1
#------------------ BOTO N. 2 ----------------------button_name = "Cancel"
# command binding
self.button2 = Button(self.myContainer1,
command = lambda
arg1=button_name, arg2=2, arg3="Bad stuff!":
self.buttonHandler(arg1, arg2, arg3)
)
# event binding sem passer o evento como um argumento
self.button2.bind("<Return>",
lambda
event, arg1=button_name, arg2=2, arg3="Bad stuff!" :
self.buttonHandler(arg1, arg2, arg3)
)
self.button2.configure(text=button_name, background="red")
self.button2.pack(side=LEFT)
def buttonHandler(self, argument1, argument2, argument3):
print "buttonHandler routine received arguments:" \
, argument1.ljust(8), argument2, argument3
def buttonHandler_a(self, event, argument1, argument2, argument3):
print "buttonHandler_a received event", event
self.buttonHandler(argument1, argument2, argument3)
print "\n"*100 # limpa a tela
print "Starting program tt078."
root = Tk()
myapp = MyApp(root)

GRUPO PYTHON UNESP ILHA SOLTEIRA

34

STEVEN FERG

PENSANDO EM TKINTER

print "Ready to start executing the event loop."


root.mainloop()
print "Finished executing the event loop."

TT079 Transmitindo argumentos para alimentadores de eventos III


Usando Currying
No programa anterior, vimos uma tcnica envolvendo lambda para transmitir argumentos a
uma funo alimentadora de eventos. Neste programa, daremos uma olhada em como fazer
a mesma coisa usando uma tcnica chamada currying.
Sobre Curry
Em seu sentido mais simples, currying a tcnica de usar funo para construir outras
funes.
Currying uma tcnica herdada da programao funcional. Se voc quiser saber mais sobre
ela, h diversas informaes no Python Cookbook. A classe curry usada neste programa
a receita de Scott David Daniel, Curry associando parmetros com funes, disponvel
em
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549

Como foi discutido sobre lambda, no meu objetivo explicar como funciona currying:
trataremos isso como uma caixa-preta.
Curry como us-lo
A maneira de usar curry (a tcnica, no o tempero!) incluir a classe curry em seu
programa, ou import-la de seu prprio arquivo Python. Neste programa, iremos incluir o
cdigo curry diretamente no programa.
Inicialmente, pensamos que a seguinte linha liga self.buttonHandler opo command de
self.button1, mas descobrimos que isso no funciona da maneira que pensvamos.
self.button1 = Button(self.myContainer1,
command=self.buttonHandler(button_name,1,"Good stuff!")
)

Usando curry, o jeito de fazer o que queremos reescrever esta linha assim:
self.button1 = Button(self.myContainer1,
command=curry(self.buttonHandler,button_name,1,"Good stuff!")
)

Como voc pode ver, o cdigo bem direto. Em vez de solicitar a funo
self.buttonHandler, criamos um objeto curry (isto , uma instncia da classe curry),
transmitindo a funo self.buttonHandler em seu primeiro argumento. Basicamente, o que

GRUPO PYTHON UNESP ILHA SOLTEIRA

35

STEVEN FERG

PENSANDO EM TKINTER

acontece que o objeto curry recorda o nome da funo dada. Por isso, quando
requisitado, o objeto curry chama a funo que lhe foi dada quando foi criado.
Event binding
Chad Netzer desenvolveu uma tcnica similar ao currying, que pode ser usada para
parametrizar event binding4 que envolve uma funo event_lambda.
Para usar event_lambda, como com curry, voc deve incluir o cdigo para a funo
event_lambda em seu programa, ou import-la de seu prprio arquivo Python. Neste
programa, inclumos o cdigo da funo event_lambda diretamente no programa.

# ---------- cdigo
def event_lambda(f,
"""Escrevendo
return lambda

para a funo event_lambda ---------*args, **kwds ):


lambda numa interface mais amigvel"""
event,f=f,args=args,kwds=kwds: f(*args,**kwds)

Uma vez tendo a funo event_lambda disponvel, podemos us-la para ligar
self.buttonHandler ao evento de teclado ENTER, fornecendo a ela alguns argumentos. Eis
como fazemos isso:
self.button1.bind("<Return>",
event_lambda(self.buttonHandler,button_name,
1,"Good stuff!")
)

Se voc tiver uma curiosidade insacivel sobre como event_lambda funciona, d uma
olhadinha no cdigo do boto button2.
Para button2, usamos um processo em dois passos. Primeiro solicitamos a funo
event_lambda:
event_handler=event_lambda(self.buttonHandler,
button_name,2,"Bad stuff!")

Quando ela solicitada, usa lambda para criar um objeto funo novo e sem nome
(annimo).
lambda event, f=f, args=args, kwds=kwds : f( *args, **kwds )

O objeto funo sem nome um envoltrio para a funo que realmente queremos solicitar
(f, que neste programa self.buttonHandler) e os argumentos ns especificamos
quando chamamos a funo event_lambda. Assim, a funo event_lambda retorna esta
nova e annima funo.
Quando event_lambda retorna a funo annima, damos a ela o nome de event_handler.
4

A utilizao desta tcnica exige verso de Python igual ou superior a Python 2.0

GRUPO PYTHON UNESP ILHA SOLTEIRA

36

STEVEN FERG

PENSANDO EM TKINTER

event_handler=event_lambda(self.buttonHandler,
button_name,2,"Bad stuff!")

Ento, no segundo passo, ligamos o evento ENTER (<Return>) funo event_handler:


self.button2.bind("<Return>",event_handler)

Perceba que para a funo annima, event s um argumento suporte que descartado e
no chega a ser usado. Somente os argumentos posicionais (args) e os argumentos de
teclado (kwds) so transmitidos rotina alimentadora do boto.
Ufa! J torrei um monte de neurnios!
Este um assunto complicado. Mas voc no precisa torrar seus neurnios tentando
entender como tudo isso funciona. Voc no precisa saber como curry e event_lambda
trabalham se seu objetivo for us-los. Trate-os como caixas-pretas: use-os sem se preocupar
com o que h dentro.
Lambda versus Curry e event_lambda Qual devo usar?
Bem

O cdigo para usar curry e event_lambda relativamente intuitivo, curto e simples.


O lado negativo que para us-los voc deve inclu-los no cdigo do seu programa,
ou import-los.
Lambda, em contrapartida, built-in em Python voc no precisa fazer nada
especial para import-la; simples assim. H o lado negativo: o cdigo para us-la
grande e um pouco confuso.

Escolha o que lhe convier. Como diz o ditado, o cliente sempre tem razo. Use o que for
cmodo para suas tarefas.
A REAL moral dessa histria que...
Python uma linguagem poderosa, e oferece vrias ferramentas que podem ser usadas para
criar funes de rechamada para manipulao de eventos. Pensando em Tkinter uma
introduo aos conceitos bsicos, no uma enciclopdia de tcnicas, por isso exploramos s
algumas delas aqui. Mas c entre ns, voc se torna muito mais hbil com Python, e como
sua necessidade por mais flexibilidade cada vez maior, h caractersticas mais avanadas
da linguagem que estaro disponveis para voc desenvolver exatamente o tipo de funes
de rechamada que voc precisar.
Comportamento do programa
Se voc rodar este programa, ele se comportar como o anterior. No mudamos nada no
comportamento do programa, somente a maneira como foi escrito.

GRUPO PYTHON UNESP ILHA SOLTEIRA

37

STEVEN FERG

PENSANDO EM TKINTER

from Tkinter import *


# ---------- cdigo da classe curry (incio) ---------class curry:
"""da receita de Scott David Daniel
"curry associando parmetros com funes"
no "Python Cookbook"
"""
def __init__(self, fun, *args, **kwargs):
self.fun = fun
self.pending = args[:]
self.kwargs = kwargs.copy()
def __call__(self, *args, **kwargs):
if kwargs and self.kwargs:
kw = self.kwargs.copy()
kw.update(kwargs)
else:
kw = kwargs or self.kwargs
return self.fun(*(self.pending + args), **kw)
# ---------- cdigo da classe curry (final) ---------# ---------- cdigo da funo event_lambda (comeo) -------def event_lambda(f, *args, **kwds ):
"""Uma funo auxiliary que envolve
lambda numa interface mais amigvel
Obrigado a Chad Netzer pelo cdigo."""
return lambda event, f=f, args=args, kwds=kwds : f( *args, **kwds )
# ---------- cdigo da funo event_lambda (final) ----------class MyApp:
def __init__(self, parent):
self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
button_name = "OK"
# command binding usando curry
self.button1 = Button(self.myContainer1,
command = curry(self.buttonHandler,
button_name, 1, "Good stuff!")
)
# event binding usando a funo auxiliary event_lambda
self.button1.bind("<Return>",
event_lambda(self.buttonHandler,
button_name, 1, "Good stuff!")
)
self.button1.configure(text=button_name, background="green")
self.button1.pack(side=LEFT)
self.button1.focus_force() # Pe o foco de teclado em button1
button_name = "Cancel"

GRUPO PYTHON UNESP ILHA SOLTEIRA

38

STEVEN FERG

PENSANDO EM TKINTER

# command binding -- usando curry


self.button2 = Button(self.myContainer1,
command = curry(self.buttonHandler,
button_name, 2,
"Bad stuff!")
)
dois passos

# event binding usando a funo auxiliary event_lambda em


event_handler=event_lambda(self.buttonHandler,
button_name,2, "Bad stuff!")
self.button2.bind("<Return>", event_handler )
self.button2.configure(text=button_name, background="red")
self.button2.pack(side=LEFT)

def buttonHandler(self, argument1, argument2, argument3):


print "buttonHandler routine received arguments:", \
argument1.ljust(8), argument2, argument3
def buttonHandler_a(self, event, argument1, argument2, argument3):
print "buttonHandler_a received event", event
self.buttonHandler(argument1, argument2, argument3)
print "\n"*100 # limpa a tela
print "Starting program tt079."
root = Tk()
myapp = MyApp(root)
print "Ready to start executing the event loop."
root.mainloop()
print "Finished executing the event loop."

TT080 Opes de widget e configuraes de pack


Nos ltimos programas, perdemos um tempo discutindo tcnicas para ligar alimentadores
de eventos a widgets.
Com este programa, retornamos ao tpico de configurar a aparncia da GUI mudando os
widgets e controlando sua aparncia e posio.
Trs tcnicas de controlar o layout de uma GUI
H trs tcnicas de controlar o layout geral de uma GUI:

Atributos dos widgets;


Opes de pack() e
Posicionando os containeres (como frames).

Neste programa, veremos como controlar a aparncia mudando os atributos dos widgets e
as opes de pack().
Trabalhemos um pouco com alguns botes e com o frame que os contm. Nas ltimas
verses deste programa, chamamos o frame de myContainer1. Aqui, iremos renome-lo
como algo mais descritivo: buttons_frame.
GRUPO PYTHON UNESP ILHA SOLTEIRA

39

STEVEN FERG

PENSANDO EM TKINTER

Os nmeros das sees seguintes se referem aos comentrios numerados no cdigo fonte.
(1)
Primeiro, para nos certificarmos de que todos os botes tm a mesma largura,
especificamos um atributo largura (width) que o mesmo para todos eles. Perceba que o
atributo width especfico para o widget Button de Tkinter nem todos os widgets
possuem atributos de largura. Perceba tambm que o atributo width especificado em
unidades de caracteres (e no, por exemplo, em unidades de pixels, polegadas, milmetros).
Sabendo que nosso maior rtulo (Cancel) tem seis caracteres, vamos definir a largura dos
botes como 6.
(2)
Agora adicionamos padding aos nossos botes. Padding so espaos extra em torno do
texto, entre o texto e a borda do boto. Fazemos isso mudando os atributos padx e pady
dos botes. padx cria os espaos extras ao longo do eixo X, horizontalmente, direita e
esquerda do texto, enquanto pady faz isso ao longo do eixo Y, verticalmente, acima e
abaixo do texto.
Vamos especificar nosso espao horizontal como 3 milmetros (padx=3m) e nosso espao
vertical como 1 milmetro (pady=1m). Veja que, diferente do atributo width, que
numrico, estes atributos so escritos entre aspas. Isso porque estamos especificando as
unidades do padding por meio do sufixo m, ento temos que especificar o tamanho dos
espaos como uma string em vez de nmeros.
(3)
Finalmente, adicionamos alguns espaos ao container (buttons_frame) que organiza os
botes. Para o container, especificamos quatro atributos de padding. padx e pady
especificam o espao que deve existir em torno (do lado de fora) do frame. ipadx e
ipady (padx interno e pady interno) especificam os espaos internos. Este o
espaamento em tono de cada um dos widgets dentro do container.
Obs: no especificamos o espaamento do frame como um atributo do frame, e sim como
uma opo que fornecemos ao gerenciador de geometria (neste caso, pack).
(4)
Como voc pode ver, o espaamento um pouco confuso. Frames tm espaamentos
internos, mas widgets como button no. Em alguns casos, o espaamento um atributo do
widget, enquanto em outros casos temos que especific-lo com opes de pack().
Comportamento do programa
Quando voc rodar este programa, ver dois botes, mas agora eles devem ter o mesmo
tamanho. O tamanho dos botes tal que o texto no fica to espremido com antes: agora
os botes tm uma borda considervel.

GRUPO PYTHON UNESP ILHA SOLTEIRA

40

STEVEN FERG

PENSANDO EM TKINTER

from Tkinter import *


class MyApp:
def __init__(self, parent):
# ------ constantes para controle do layout -----button_width = 6
### (1)
button_padx = "2m"
button_pady = "1m"

### (2)
### (2)

buttons_frame_padx = "3m"
### (3)
buttons_frame_pady = "2m"
### (3)
buttons_frame_ipadx = "3m"
### (3)
buttons_frame_ipady = "1m"
### (3)
# -------------- fim das constantes ---------------self.myParent = parent
self.buttons_frame = Frame(parent)
self.buttons_frame.pack(
ipadx=buttons_frame_ipadx,
ipady=buttons_frame_ipady,
padx=buttons_frame_padx,
pady=buttons_frame_pady,
)

### (4)
### (3)
### (3)
### (3)
### (3)

self.button1=Button(self.buttons_frame,
command=self.button1Click)
self.button1.configure(text="OK", background= "green")
self.button1.focus_force()
self.button1.configure(width=button_width, ### (1)
padx=button_padx,
### (2)
pady=button_pady
### (2)
)
self.button1.pack(side=LEFT)
self.button1.bind("<Return>", self.button1Click_a)
self.button2=Button(self.buttons_frame,
command=self.button2Click)
self.button2.configure(text="Cancel", background="red")
self.button2.configure(width=button_width, ### (1)
padx=button_padx,
### (2)
pady=button_pady
### (2)
)
self.button2.pack(side=RIGHT)
self.button2.bind("<Return>", self.button2Click_a)
def button1Click(self):
if self.button1["background"] == "green":
self.button1["background"] = "yellow"
else:
self.button1["background"] = "green"
def button2Click(self):
self.myParent.destroy()
def button1Click_a(self, event):
self.button1Click()

GRUPO PYTHON UNESP ILHA SOLTEIRA

41

STEVEN FERG

PENSANDO EM TKINTER

def button2Click_a(self, event):


self.button2Click()
root = Tk()
myapp = MyApp(root)
root.mainloop()

TT090 Posicionando frames.


Neste programa, daremos uma olhada em posicionamento de containeres (frames). O que
iremos fazer criar uma srie de frames, colocados um dentro do outro: botton_frame,
left_frame e big_frame.
Estes frames no contero nada nenhum widget. Normalmente, por causa da elasticidade
dos frames, eles se encolheriam at no serem visveis, mas especificando atributos de
altura e largura podemos fornecer um tamanho inicial a eles.
Veja que no especificamos altura e largura para todos os frames. Para myContainer1, por
exemplo, no especificamos nenhum dos dois atributos, mas especificando estes atributos
para os widgets que este frame continha, ele se esticou at acomodar as alturas e larguras
acumuladas pelas alturas e larguras desses widgets.
Posteriormente exploraremos como colocar widgets nestes frames; por enquanto
simplesmente criaremos os frames, e daremos a eles diferentes tamanhos, posies e cores
de fundo.
Colocaremos tambm bordas em torno dos trs frames que nos interessaro mais no futuro:
bottom_frame, left_frame e right_frame. Os outros frames (top_frame e buttons_frame) no
recebero bordas.
Comportamento do programa
Quando voc roda este programa, v frames diferentes, com diferentes cores de fundo.
from Tkinter import *
class MyApp:
def __init__(self, parent):
#------ constantes para controlar o layout dos botes -----button_width = 6
button_padx = "2m"
button_pady = "1m"
buttons_frame_padx = "3m"
buttons_frame_pady = "2m"
buttons_frame_ipadx = "3m"
buttons_frame_ipady = "1m"
# -------------- fim das constantes ----------------

GRUPO PYTHON UNESP ILHA SOLTEIRA

42

STEVEN FERG

PENSANDO EM TKINTER

self.myParent = parent
### Nosso frame mais importante chama-se myContainer1
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
###
###
###
###
###

Usamos orientao VERTICAL (top/bottom) dentro


de myContainer1. Dentro de myContainer1, primeiro
criamos o frame buttons_frame
Ento criamos top_frame e bottom_frame
Estes sero nossos frames-exemplo.

# buttons_frame
self.buttons_frame = Frame(self.myContainer1)
self.buttons_frame.pack(
side=TOP,
ipadx=buttons_frame_ipadx,
ipady=buttons_frame_ipady,
padx=buttons_frame_padx,
pady=buttons_frame_pady,
)
# top_frame
self.top_frame = Frame(self.myContainer1)
self.top_frame.pack(side=TOP,
fill=BOTH,
expand=YES,
)
# bottom_frame
self.bottom_frame = Frame(self.myContainer1,
borderwidth=5,
relief=RIDGE,
height=50,
background="white",
)
self.bottom_frame.pack(side=TOP,
fill=BOTH,
expand=YES,
)
###
###
###
###

Coloquemos agora mais dois frames, left_frame


e right_frame,
dentro de top_frame, usando orientao
HORIZONTAL (left/right)

# left_frame
self.left_frame = Frame(self.top_frame, background="red",
borderwidth=5,
relief=RIDGE,
height=250,
width=50,
)
self.left_frame.pack(side=LEFT,
fill=BOTH,
expand=YES,
)

GRUPO PYTHON UNESP ILHA SOLTEIRA

43

STEVEN FERG

PENSANDO EM TKINTER

### right_frame
self.right_frame = Frame(self.top_frame, background="tan",
borderwidth=5,relief=RIDGE,
width=250)
self.right_frame.pack(side=RIGHT,fill=BOTH,expand=YES)
# agora adicionamos os botes a buttons_frame
#------ constantes para controle do layout -----button_width = 6
### (1)
button_padx = "2m"
button_pady = "1m"

### (2)
### (2)

buttons_frame_padx = "3m"
### (3)
buttons_frame_pady = "2m"
### (3)
buttons_frame_ipadx = "3m"
### (3)
buttons_frame_ipady = "1m"
### (3)
# -------------- fim das constantes ---------------self.button1=Button(self.buttons_frame,
command=self.button1Click)
self.button1.configure(text="OK", background= "green")
self.button1.focus_force()
self.button1.configure(
width=button_width,
padx=button_padx,
pady=button_pady
)
self.button1.pack(side=LEFT)
self.button1.bind("<Return>", self.button1Click_a)
self.button2=Button(self.buttons_frame,
command=self.button2Click)
self.button2.configure(text="Cancel", background="red")
self.button2.configure(width=button_width,
padx=button_padx,
pady=button_pady
)
self.button2.pack(side=RIGHT)
self.button2.bind("<Return>", self.button2Click_a)
def button1Click(self):
if self.button1["background"] == "green":
self.button1["background"] = "yellow"
else:
self.button1["background"] = "green"
def button2Click(self):
self.myParent.destroy()
def button1Click_a(self, event):
self.button1Click()
def button2Click_a(self, event):
self.button2Click()
root = Tk()
myapp = MyApp(root)
root.mainloop()

GRUPO PYTHON UNESP ILHA SOLTEIRA

44

STEVEN FERG

PENSANDO EM TKINTER

TT095 Mtodos gerenciadores de janelas & controlando o tamanho de


janelas com a opo geometry.
Dimensionar janelas pode ser uma experincia frustrante quando se trabalha com Tkinter.
Imagine esta situao: voc acredita em desenvolvimento interativo, ento primeiro voc
cuidadosamente cria um frame com a especificao de altura e largura que deseja e depois
de uns testes, percebe que funcionou. Ento voc vai para o prximo passo, que de
adicionar alguns botes ao frame. Testa de novo, mas agora, para sua surpresa, o Tkinter
est agindo como se no houvesse especificao alguma quando altura e largura do frame:
o frame se encolheu ao mnimo suficiente para acomodar os botes.
O que est acontecendo???
Bem, o comportamento do gerenciador de geometria (pack, em nossos programas)
consistente. Ou, permita-me dizer: o comportamento de pack depende da situao. O ponto
claro que o pack honrar com a solicitao de dimenses se o container estiver vazio, mas
se este contiver qualquer outro widget, ento a elasticidade natural do container vir tona
as configuraes de altura e largura do container sero ignoradas em favor das
configuraes dos widgets e o container se ajustar a estes to apertado quando possvel.
Isso fato: impossvel controlar o tamanho de um container no-vazio.
O que voc pode controlar o tamanho inicial da janela raiz inteira (aquela que um
container do container possui barra de ttulo, etc.), e voc faz isso pela opo
gerenciadora de janelas geometry.
(1)
Neste programa, usamos a opo geometry para fazer uma janelona em torno de um
framezinho.
(2)
Veja que a opo title, que ns tambm usamos neste programa, tambm um mtodo
gerenciador de janelas. Title controla o texto do ttulo na barra de ttulos da janela.
Veja tambm que a opo gerenciadora de janelas pode opcionalmente ser especificada com
um prefixo wm_. Por exemplo, wm_geometry e wm_title. Neste programa, s para
mostrar que voc pode usar qualquer uma destas sintaxes, usamos geometry e
wm_title.
Comportamento do programa
Este programa mostra quatro janelas em sucesso
Observe que voc ter que fechar cada uma delas clicando no widget CLOSE o
famigerado X em uma caixa, do lado direito da barra de ttulos.

GRUPO PYTHON UNESP ILHA SOLTEIRA

45

STEVEN FERG

PENSANDO EM TKINTER

Na primeira, vemos que o frame detm as propriedades de altura e largura que


especificamos, mas veja: ele no contm widgets!
Na segunda, vemos que exatamente o mesmo frame se comporta diferentemente quando
alguns widgets (aqui, trs botes) so adicionados a ele. Veja que o frame se encolheu em
torno dos trs botes.
Na terceira janela, novamente mostramos como o frame vazio se parece, s que dessa vez
usamos a opo geometry para controlar o tamanho da janela inteira. Podemos ver o fundo
azul do frame dentro de um grande campo cinza da janela.
Na ltima, novamente mostramos o frame com os trs botes dentro dele, mas desta vez
especificando o tamanho da janela com a opo geometry. O tamanho da janela o mesmo
que na janela anterior, porm (como na segunda janela) o frame se encolheu em torno dos
botes, por isso no podemos ver seu fundo azul.
from Tkinter import *
class App:
def __init__(self, root, use_geometry, show_buttons):
fm = Frame(root, width=300, height=200, bg="blue")
fm.pack(side=TOP, expand=NO, fill=NONE)
if use_geometry:
root.geometry("600x400")
### (1) observe o mtodo gerenciador de janelas
if show_buttons:
Button(fm, text="Button 1", width=10).pack(side=LEFT)
Button(fm, text="Button 2", width=10).pack(side=LEFT)
Button(fm, text="Button 3", width=10).pack(side=LEFT)
case = 0
for use_geometry in (0, 1):
for show_buttons in (0,1):
case = case + 1
root = Tk()
root.wm_title("Case " + str(case))
### (2) observe o mtodo gerenciador de janelas wm_title
display = App(root, use_geometry, show_buttons)
root.mainloop()

TT100 Opes de pack: side, expand, fill e anchor.


Neste programa, veremos mais opes de pack() para controlar layouts dentro de frames:

Side;
Fill;
Expand e
Anchor.

GRUPO PYTHON UNESP ILHA SOLTEIRA

46

STEVEN FERG

PENSANDO EM TKINTER

Este programa diferente dos outros da srie. Isto , voc no ter que ler seu cdigo para
entender suas caractersticas. S precisa rodar o programa.
O propsito do programa mostrar a voc os resultados das opes de pack. Rodar o
programa vai permitir a voc escolher diferentes opes e observar os efeitos das diferentes
combinaes de opes.
Os conceitos subjacentes da opo pack
Para entender como podemos controlar a aparncia dos widgets dentro de um container
(como um frame), precisamos lembrar que o gerenciador de geometria pack usa o modelo
de arranjo baseado no conceito de cavidade. Isto , cada container uma cavidade, e pack
acomoda o contedo dentro dela.
Em termos de posicionamento e exibio dos componentes num container, til entender
trs conceitos:

Espao no-solicitado (cavidade);


Espao solicitado mas no utilizado e
Espao solicitado e utilizado.

Quando voc acomoda um widget, como um boto, ele sempre acomodado ao longo de
um dos quatro lados da cavidade. A opo side de pack especifica quais lados sero
usados. Por exemplo, se especificamos side=LEFT, o widget ser acomodado (isto ,
posicionado) do lado esquerdo da cavidade.
Quando um widget acomodado ao longo de um lado do container, solicitado o lado
inteiro, mesmo que ele no ocupe todo o espao solicitado. Suponha que acomodemos um
pequeno boto chamado X ao longo do lado esquerdo de uma grande cavidade, como na
figura da prxima pgina.
A cavidade (rea no solicitada) agora ao lado direito do widget. O widget X solicitou o
lado esquerdo inteiro, numa tira que larga somente o suficiente para acomod-lo. Por seu
tamanho reduzido, o widget X usa somente uma pequena parte da rea total que foi
solicitada, somente o necessrio para que ele aparea.

GRUPO PYTHON UNESP ILHA SOLTEIRA

47

STEVEN FERG

PENSANDO EM TKINTER

Como voc pode ver, o widget X solicitou somente o espao que necessitava para aparecer.
Se especificarmos a opo de pack chamada expand=YES, ele ir solicitar toda a rea
disponvel. Nenhum pedao do lado direito da cavidade permanecer no-solicitado. Isso
no significa que o widget usa a rea inteira; ele continua usando somente a pequena parte
de que necessita.
Em tal situao, o widget X tem um espao muito grande sem utilidade em torno dele. Em
que lugar deste espao ele dever se posicionar? isso que a opo anchor diz a ele.
Quando um widget solicitou mais espao do que realmente usa, a opo anchor refina suas
coordenadas de posicionamento. N significa Norte (isto , centrado no topo da rea
solicitada). NE significa Nordeste (isto , acima e direita da rea solicitada). E assim
por diante.
J a opo fill serve para dizer ao widget se ele deve ou no ocupar todo o espao livre, e
em que direo ele deve se expandir:

fill=NOME significa que o widget no deve se expandir;


fill=X significa que ele deve se expandir na direo do eixo X (horizontalmente);
fill=Y significa que ele deve se expandir na direo do eixo Y (verticalmente) e
fill=BOTH significa que ele deve se expandir para todos os lados.

Rodando o programa
Ok, vamos rodar o programa. Voc no precisa ler o cdigo, s rodar o programa e
experimentar com as vrias opes de pack dos trs botes-demonstrao.
O frame do boto A d uma cavidade horizontal para o boto se locomover o frame no
muito mais alto que o boto; o frame do boto B d a ele uma cavidade vertical para se
locomover o frame no to mais largo que o boto, e o frame do boto C tem uma
enorme cavidade muito mais alta e larga que o boto para ele se divertir l dentro.
Se a aparncia de qualquer dos botes sob certas configuraes surpreende voc, tente
descobrir por que ele se parece assim.
E finalmente
Um jeito prtico de encontrar erros
Veja que empacotar widgets em containeres um negcio complicado porque o
posicionamento de um widget em relao a outros widgets prximos depende em parte de
como os widgets prximos foram acomodados. Isto , se os outros widgets foram
acomodados esquerda, ento a cavidade dentro da qual o prximo widget ser
empacotado ser direita. Mas se eles foram acomodados no topo da cavidade, ento a
cavidade dentro da qual o prximo widget ser empacotado ser abaixo deles. Tudo isso
uma bela salada!

GRUPO PYTHON UNESP ILHA SOLTEIRA

48

STEVEN FERG

PENSANDO EM TKINTER

Eis uma forma prtica de encontrar erros: se voc est desenvolvendo seu layout e se
deparar com um problema coisas que no funcionam da maneira que voc pensava que
deveriam funcionar ento d a cada um dos seus containeres (isto , cada um dos seus
frames) uma cor de fundo diferente, por exemplo
bg = red, ou
bg = cyan, ou
bg = tan.
... ou amarelo, ou azul, ou vermelho, o que voc quiser!
Isso permitir a voc ver como os frames esto se arranjando. Freqentemente, o que voc
vir lhe dar um indcio de qual o problema.

Meus agradecimentos a Pedro Werneck e


Osvaldo Santana pela reviso e a Douglas
Soares pelas observaes.

TRADUZIDO PELO GRUPO PYTHON


UNESP ILHA SOLTEIRA

Visite o site do Grupo Python! http://geocities.yahoo.com.br/grupopython


Dvidas sobre a traduo? Fale com o tradutor: labaki@feis.unesp.br

GRUPO PYTHON UNESP ILHA SOLTEIRA

49

Você também pode gostar