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

PENSANDO EM TKINTER

STEVEN FERG

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

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

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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: 4

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

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! 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 "========================================================" Trillium is a kind of flower!"

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

print print print print print print print print print print

"Press the letter of your answer, then the ENTER key." " " " " A. B. C. X. Animal" Vegetable" Mineral" 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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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() root.mainloop() ###(1) ###(2)

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

PENSANDO EM TKINTER

STEVEN FERG

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() root.mainloop() ### (1) ### (2) ### (3)

### (4)

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. 10

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

(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 11

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

(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

PENSANDO EM TKINTER

STEVEN FERG

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. 14

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

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. 15

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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() root = Tk() myapp = MyApp(root) root.mainloop() ### (5) ### (6)

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 18

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

(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

PENSANDO EM TKINTER

STEVEN FERG

self.button2.bind("<Return>", self.button2Click) 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()

### (2)

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

PENSANDO EM TKINTER

STEVEN FERG

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 22

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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: 32

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

# 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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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 35

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

# 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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

### 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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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. 46

GRUPO PYTHON UNESP ILHA SOLTEIRA

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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

PENSANDO EM TKINTER

STEVEN FERG

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