Capítulo 8

anterior sumario proximo

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

alterando a execução de bindings

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.

um simples sistema de ajuda

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?).


rpragana Mon Apr 19 20:44:37 EST 1999