Muitas vezes desejamos usar bindings diferentes para os mesmos eventos, dependendo de estados da nossa interface. Isso acontece, por exemplo, num editor visual de interfaces, mas também para editar desenhos, textos, ou em qualquer programa multi-modo. No capítulo anterior vimos como criar bindings para realizar operações de movimentação e redimensionamento de um objeto, mas como podemos fazer para ter esses vários bindings comutados rapidamente, sem ter que redefinir cada binding associado a um evento? Felizmente o tcl/tk nos fornece uma estrutura, chamada bindtags, que associa um conjunto de tais bindings para um objeto ou uma classe de objetos.
Quando um widget é criado, ele já nasce com um conjunto de tags pré-definidos. Po exemplo, um botão .b tem os seguintes bindtags: { .b Button . all }. O primeiro (.b) relativo à instância do objeto, o segundo da sua classe (Button, com a primeira letra maiúscula), o seguinte relativo ao toplevel que o contém, e finalmente o tag all que se aplica a todos os widgets da aplicação. Esses bindtags são acessíveis pelo comando do mesmo nome e podem ser modificados, adicionando ou retirando novos tags à lista (com as funções de tcl para manipulação de listas). O tag Button (que pode ser usado com um comando bind) indica eventos que se referem a qualquer objeto da classe (botões).
A idéia é criamos vários tags, ou seja, nomes simbólicos para os
bindings desejados. No nosso exemplo abaixo, tentaremos fazer com que o mesmo
programa sirva tanto para mover, como redimensionar o objeto, e ainda para
desativar essas operações, sem ser necessário ficar redifinindo via varios
comandos bind quando o estado do nosso editor for modificado. Para
mudar de estado o editor, usamos simplesmente 3 radiobuttons, com
valores idênticos aos tags associados: none, move, resize (nomes
inglêses são menores! resize=redimensiona).
Inicialmente adicionamos à lista de tags, na primeira posição, o tag
"none", que efetivamente não faz nada, porque não possui nenhum
binding associado. Isso é feito pelo comando bindtags .b [linsert
[bindtags .b] 0 $oper]. O comando [bindtags .b] no interior
simplesmente lê os tags atuais. O comando linsert insere na primeira
posição (0) da lista o novo tag.
Quando escolhermos um determinado valor para a variável oper
(abreviatura de operação), os radiobuttons executarão o comando
setbindings, que substitue o primeiro tag da lista pelo novo valor da
variável oper.
Devemos então criar os bindings como fizemos para os exemplos anteriores,
usando no lugar do objeto (.b) os tags recém-definidos e pronto. A
listagem abaixo não mostra essa última parte, que fica como exercício para o
leitor.
set oper none frame .top -height 150 -width 300 frame .bottom radiobutton .bottom.rb0 -text desabilita -variable oper -value none \ -command setbindings radiobutton .bottom.rb1 -text move -variable oper -value move \ -command setbindings radiobutton .bottom.rb2 -text redimensiona -variable oper -value resize \ -command setbindings pack .bottom.rb0 .bottom.rb1 .bottom.rb2 -side left pack .top .bottom frame .b -bd 2 -relief raised -width 50 -height 30 -bg goldenrod place .b -in .top -x 20 -y 20 -anchor nw bindtags .b [linsert [bindtags .b] 0 $oper] proc setbindings {} { global oper bindtags .b [lreplace [bindtags .b] 0 0 $oper] }
|
Edite a figura (um frame com -relief raised) ao lado para cada posição dos radiobuttons na parte inferior. Observe que o estado com o primeiro radiobutton ativado não permite nenhuma edição na realidade: a ele está associado o tag "none" definido acima. Todas as operações são realizadas pelo mesmo botão do mouse (botão da esquerda, <1>). |
Os bindings associados a cada tag da lista bindtags são executados em sequência, na mesma ordem da lista bindtags. Por exemplo, se os bindtags são move .b Button . all, primeiramente os bindings associados ao tag move são executados, depois os referentes a .b, e assim por diante. Às vezes queremos evitar a execucão de qualquer outro binding após um determinado. Podemos fazer isso executando o comando break (que é usado também para finalizar um loop dentro das estruturas de controle do tcl) e nenhum outro binding será executado.
Veja esse exemplo ligeiramente diferente. Queremos que o objeto mude de cor quando estiver sendo editado, mas não quando as edições estiverem inativas. Conseguimos isso adicionando os bindings bind .b <1> { .b config -bg yellow } bind .b <B1-ButtonRelease> { .b config -bg goldenrod }. Quando estamos com o radiobutton "desabilita" (tag none) selecionado nada acontece, nem mesmo a mudança de cor do bjeto. Para evitar a execução do primeiro bind acima, definimos o binding bind none <1> { break }, que é executado primeiro na ordem dos bindtags, evitando assim a execução dos outros bindings. |
|
Vejamos agora como podemos introduzir nas nossas aplicações um sistema de
ajuda com balloons, que aparecem quando o usuário deixa o cursor do
mouse por alguns instantes parado sobre os botões ou menubuttons da aplicação.
Para isso, precisaremos de mais um comando tcl, o after
. Este
comando adiciona numa lista de "coisas a fazer" um comando escolhido por nós,
que será executado após o tempo dado expirar. A idéia é simples: ao entrar (o
mouse) no botãp, programamos com after
um comando que irá tornar
visível a janela com a ajuda. Ao sair, destruimos esssa janela e
desprogramamos o after
, se for o caso. Numa aplicação "normal"
podemos criar simplesmente um toplevel, mas com um tclet, isto não funciona
(toplevel não podem ser criados em tclets, usualmente). Sendo assim, iremos
deixar previamente criado um frame que posicionaremos sobre a interface
existente. No interior deste frame, então, colocaremos o label com a mensagem
de ajuda desejada. Para armazenar as diversas mesnagens (uma por botão),
utilizaremos um array, indexado pelo nome (instância) do widget. Entretanto, em tclets também não podemos usar o comando
after, portanto o tclet é ligeiramente diferente e mostrará a ajuda
instantaneamente.
Nossa "aplicação" terá apenas um canvas e uma barra de menus, com alguns menubuttons:
frame .bar menubutton .bar.mb1 -text Arquivo menubutton .bar.mb2 -text Edit menubutton .bar.mb3 -text Config menubutton .bar.mb4 -text Ajuda pack .bar.mb1 .bar.mb2 .bar.mb3 -side left -fill x pack .bar.mb4 -side right canvas .c -width 420 -height 150 pack .bar -side top -fill x pack .c -side top -fill both
Os textos de ajuda, como já dissemos, ficarão em um array:
set ajuda(.bar.mb1) "Operações com arquivos" set ajuda(.bar.mb2) "Comandos de edição" set ajuda(.bar.mb3) "Configuração da aplicação" set ajuda(.bar.mb4) "???"
O frame que conterá o texto da ajuda em si:
frame .help -bd 1 -bg black label .help.lab -text "Texto da ajuda" -bg yellow pack .help.lab
Note que não materializamos o frame (nem pack
nem
place
, etc, foi usado com ele, só no seu interior), pois não
queremos que ele apareça agora.
Agora vem a parte mais importante. Quando o cursor do mouse entrar (binding
<Enter>
) em algum botão (na realidade, aqui só temos
menubuttons, mas podemos estender o raciocínio para botões normais), devemos
iniciar o processo de "mostrar no futuro" o frame da ajuda. O comando
after
espera como argumentos o tempo em milisegundos e em seguida
(o restante dos argumentos) o comando a ser executado. Ele retorna um
identificador deste comando programado para posterior manipulacão, o que
salvamos na variável ajuda(id)
. Salvamos a janela à qual se
refere a ajuda em ajuda(win)
e reconfiguramos o nosso label de
ajuda para o texto contido no array (indexado pelo nome do botão). A posição
da janela será calculada em função da posição do cursor, que é em relação ao
menubutton. O comando winfo x %W
devolve a coordenada x do canto
esquerdo superior da janela no seu "pai", e de forma similar com o eixo y. Os
20 pixels adicionais deixam a janela de ajuda um pouco abaixo do local do
cursor, para melhor visibilidade. Vejamos o código:
bind Menubutton <Enter> {+ set ajuda(win) %W .help.lab config -text $ajuda(%W) set ajuda(id) [after 500 place .help \ -x [expr %x + [winfo x %W]]\ -y [expr %y + [winfo y %W] + 20] ] }
Quando o mouse "sair" (binding <Leave>
) do botão, devemos
cancelar o comando after
pendente, usando a opção
cancel
desse comando. Para isso salvamos o identificador na
variável ajuda(id), e destruir essa variável (para não "cancelar" novamente).
Finalmente, devemos retirar a janela de ajuda, deixando-a invisível, caso ela
já tenha aparecido. Como estamos colocando-a com um place
, o
place forget
faz isso.
bind Menubutton <Leave> {+ if [info exists ajuda(id)] { after cancel $ajuda(id) unset ajuda(id) } place forget .help }
|
Posicione o cursor do mouse sobre um dos botões da barra na parte superior desse tclet. Experimente "entrar" no botão a partir de várias direções, para verificar a posição que o frame com a ajuda aparece. Depois aumente esse código para conter outros tipos de botões, mas deixe-o seguro: se não existir ajuda para o widget que for visitado, não faça a janela aparecer em branco, projete o seu default. |
Ponha o código descrito acima (com o comando after inclusive) em um arquivo e execute-o. Ajuste o tempo de 500 ms para algo que o agrade mais (ou deixe-o numa variável). Implemente outro sistema de ajuda com uma barra de status na parte inferior da aplicação, como a que voce pode ver agora no Netscape (voce o está usando, não?).