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
Inicialmente, salvamos a posição do cursor (mouse) e do widget que queremos
mover (ao pressionar o botão bind .b <1> { set xs %X set ys %Y set xx [winfo x .b] set yy [winfo y .b] }
O comando |
![]() |
Depois devemos modificar a posição do widget a cada evento
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.
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.
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.