Capítulo 7

anterior sumario proximo

Como podemos fazer a nossa interface mais interativa, permitindo-nos mover ou redimensionar os objetos? A idéia de como fazer isso é simples. Definimos bindings para o objeto que deve ser movido com os eventos <1> e <B1-Motion>, o primeiro memorizando a posição atual do objeto e do cursor (mouse), o segundo recalculando sua posição em função do movimento do cursor. Para obter a posição atual do widget (suponhamos que seu nome seja .b), temos os comandos winfo x .b e winfo y .b. Para obter a posição atual do cursor, temos um mecanismo de substitução do comando bind, onde sequências começadas por % são substituidas pelos seus valores antes de executar o comando.

Outra característica do comando bind é que todas as suas variáveis são avaliadas em um contexto global. Expliquemos melhor: variáveis criadas no interior de um procedure são visíveis (em geral) somente dentro dessa procedure. Variáveis definidas fora de procedures têm contexto global, ou seja, podem ser acessadas de qualquer local, mesmo dentro de uma procedure através do comando global. Isso nos leva a exercitar maiores cuidados com os nomes usados para essas variáveis, uma vez que estes nomes não podem ser reusados. Uma possibilidade é definir todas as variáveis de uma aplicação (ou biblioteca) como um array. Por enquanto iremos relaxar um pouco nestas convenções, mas fica aqui o alerta.

Um problema relativamente menor é que o geometry manager place (que estaremos usando neste exemplo), define a posição de um widget em coordenadas relativas ao seu container, enquanto que iremos obter as coordenadas do cursor em relação à tela toda. Para (re)-posicionar um widget devemos fazer place .b -x $x -y $y, onde x e y são variáveis com a nova posição.

O comando bind admite várias substituições % para eventos: %k para keycodes, %K para keysyms, %A para o ascii do caracter, %x e %y para a posição x,y na janela à qual se refere o binding (que não nos serve, neste caso, pois gostaríamos de ter em relação ao container), %W para a janela (widget) à qual se refere o evento, %X e %Y para as coordenadas absolutas (em relação à tela) da posição do cursor. Outras substituições serão vistas posteriormente.

A figura ao lado mostra a posição inicial (%X %Y) onde o botão <1> do mouse foi pressionado, e uma outra posição (%X' %Y') após movimentar o mouse com este botão pressionado. O efeito desejado é o movimento correspondente do widget (mostrado sombreado na figura).

Inicialmente, salvamos a posição do cursor (mouse) e do widget que queremos mover (ao pressionar o botão <1>):

bind .b <1> {
     set xs %X
     set ys %Y
     set xx [winfo x .b]
     set yy [winfo y .b] }

O comando winfo tem várias utilidades, fornecendo informações sobre widgets. Por enquanto mostramos apenas os subcomandos "x" e "y", para obter as coordenadas do seu canto esquerdo superior no widget-pai.

movendo um widget

Depois devemos modificar a posição do widget a cada evento <B1-Motion>, movimento do cursor com o botão 1 pressionado (que neste caso entra como um modificador do evento, B1). O movimento será calculado pela variação da posição do cursor dx,dy. Salvamos também a nova posição do cursor para futuros movimentos, enquanto o botão estiver pressionado.

bind .b <B1-Motion> {
     set dx [expr %X - $xs]
     set dy [expr %Y - $ys]
     set xx [expr $xx + $dx]
     set yy [expr $yy + $dy]
     place .b -x $xx -y $yy
     set xs %X
     set ys %Y }

Tente como exercício generalizar esses comandos, dados os paràmetros %X, %Y e %W (nome do widget), definindo procedures que movimentem o widget sobre o qual se encontra o cursor.

redimensionando interativamente um widget

Da mesma forma como fizemos acima, podemos também modificar as dimensões (largura, altura) de um widget. A função winfo width .b fornece sua largura atual, e winfo height .b sua altura. Contudo, o geometry manager não é o responsável pelas dimensões do objeto (o próprio widget o é). Para mudar suas dimensões, o comando empregado é a própria instància do widget, com o subcomando configure (ou abreviadamente config). Fazendo .b config -width $largura -height $altura fazemos essa mudança.

O tclet ao lado é semelhante ao mostrado anteriormente, com a diferença somente nos bindings. Clique em qualquer ponto no interior do objeto e arraste o cursor para redimensionar o widget. Tome cuidado para não diminuir demais suas dimensões para que ele não "desapareça", e caso isto aconteça, faça o reload da página no seu browser.

Você já sabe como implementar esse tclet ao lado?

Aqui está a implementação deste tclet:

 frame .b -bd 2 -relief raised -width 50 -height 30 -bg goldenrod
 place .b -x 20 -y 20 -anchor nw
 bind .b <1> {
     set xs %X
     set ys %Y
     set xx [winfo width .b]
     set yy [winfo height .b]
 }
 bind .b <B1-Motion> {
     set dx [expr %X - $xs]
     set dy [expr %Y - $ys]
     set xx [expr $xx + $dx]
     set yy [expr $yy + $dy]
     .b config -width $xx -height $yy
     set xs %X
     set ys %Y }

Mais um exercício: implemente um programa que sirva para mover e redimensionar um frame como estes, selecionando a operação através de dois radiobuttons.


Alguns widgets têm suas dimensões em unidades de caracteres e não pixels. Operações de movimentação e redimensionamento com estes objetos devem levar em conta essa diferença. Podemos usar o comando font measure $fnt m, para obter a largura em pixels da letra "m" (o comando mostra o tamanho de qualquer string em pixels, dada como argumento) com o fonte de caracteres $fnt. Evidentemente, essa dimensão é uma média, mas serve para nosso propósito. A altura do fonte de caracteres pode ser calculada a partir do comando font metrics $fnt.

Para obter o fonte atualmente em uso por um widget, o comando .b config -font, devolve uma lista de 5 posições onde a última é o valor corrente. Assim, o valor desejado será lindex [.b config -font] 4. Experimente visualizar estes valores usando um dos tclets dos primeiros capítulos, ou melhor com o TkCon.

usando arrays para não "gastar" muitos nomes

Nos exemplos acima, as variáveis xs, ys, xx, yy, dx, dy são todas globais. Isto significa que, se em vários bindings ou fora de procedures, usarmos os mesmos nomes, estaremos "sujando" os seus valores. Uma forma de evitar essa inconveniência é definir um array com índices para cada valor que desejamos utilizar. Por exemplo, se o array chamar-se mov, podemos trocar estas variáveis por mov(xs), mov(ys, mov(xx), etc. Somente um nome será empregado, o do próprio array.

Não tente fazer a mesma coisa com listas, uma vez que elas são estruturas inerentemente mais lentas, devido à sua implementação.


rpragana Mon Apr 5 09:46:16 EST 1999