GUI, restez vivants !

 

Pourquoi

Il peut arriver qu'une interface contrôle un widget qui nécessite un calcul conséquent. Dans ce cas, il ne faut pas que l'interface reste bloquée jusqu'à la fin du calcul ; surtout si le calcul est devenu inutile car l'utilisateur a changé les paramètres du widget.


Comment

  1. Le widget doit détecter le changement des paramètres et doit arrêter aussitôt son calcul.
  2. Les widgets annexes ne doivent pas être bloqués pendant le calcul.

Pour la condition 1, il faut que le widget enregistre les paramètres au début de son calcul et vérifient régulièrement (à chaque boucle de calcul par exemple) qu'ils n'ont pas varié.

Pour la condition 2, le plus simple est de permettre à la boucle des évènements de traiter les messages en attente. Le danger étant que ces messages relancent (récursivement) la procédure courante jusqu'à dépasser la capacité de la pile des appels de procédure. Il faut donc trouver un moyen de limiter le niveau de récursivité induit.

Un problème annexe est d'éviter de refaire plusieurs fois un même calcul même s'il est redemandé (à tort) à travers l'interface.


Le code

  # ---------------
  # parameters
  # ---------------

  set radius 64
  set px 0.25
  set py -0.25
  set ::TRACE 0

  # ---------------
  # procs
  # ---------------

    # compute_px
    # ---------------
  proc compute_px {px} \
  {
    update
    if {$::TRACE} { puts "compute_px $px" }
    if {$px == $::currentpx} { if {$::TRACE} { puts "px current" }; return }
    after 0 compute $::px $::py
  }

    # compute_py
    # ---------------
  proc compute_py {py} \
  {
    update
    if {$::TRACE} { puts "compute_py $py" }
    if {$py == $::currentpy} { if {$::TRACE} { puts "py current" }; return }
    after 0 compute $::px $::py
  }

    # compute px py
    # ---------------
  proc compute {px py} \
  {
    if {$::TRACE} { puts "compute $px $py" }
    if {$px == $::currentpx && $py == $::currentpy} { if {$::TRACE} { puts "current" }; return }
    set ::currentpx $px
    set ::currentpy $py
    set radius $::radius
    set dx [expr {$px * $radius}]
    set dy [expr {$py * $radius}]
    for {set y -$radius} {$y < $radius} {incr y} \
    {
      for {set x -$radius} {$x < $radius} {incr x} \
      {
        if {$px != $::px} { if {$::TRACE} { puts "($x,$y) px $px $::px" }; return }
        if {$py != $::py} { if {$::TRACE} { puts "($x,$y) py $py $::py" }; return }
        set r [expr {($x * $x + $y * $y) / ${::radius^2}}]
        if {$r > 1} { continue }
        set r [expr {(($x - $dx) * ($x - $dx) + ($y - $dy) * ($y - $dy))/ ${::radius^2}}]
        set c [expr {127 + int(128 * (1 - $r))}]
        if {$c < 0} { set c 0 }
        _img_ put [format #%2.2x%2.2x%2.2x $c $c $c] \
          -to [expr {$x + $radius}] [expr {$y + $radius}]
        update
      }
    }
  }

  # ---------------
  # packages
  # ---------------

  package require Tk
  package require Img

  # ---------------
  # interface
  # ---------------
  wm title . "Keeping alive"
  set diameter [expr {$radius * 2}]
  set radius^2 [expr {double($radius * $radius)}]
  set pi/2 [expr {asin(1)}]
  set currentpx ""
  set currentpy ""
    # image
    # ---------------
  image create photo _img_ \
    -width $diameter -height $diameter
    # canvas
    # ---------------
  canvas .c -bd 0 -highlightt 0 \
    -width $diameter -height $diameter
  .c create image 0 0 -anchor nw -image _img_
  pack .c
    # label frame
    # ---------------
  labelframe .lf -text "glint relative position" -relief groove
    # scales
    # ---------------
  label .lf.lgx -text \nx
  scale .lf.sgx -orient horizontal -variable ::px \
    -from -1.0 -to 1.0 -resolution 0.05 -length 200
  label .lf.lgy -text \ny
  scale .lf.sgy -orient horizontal -variable ::py \
    -from -1.0 -to 1.0 -resolution 0.05 -length 200
    # place & display
    # ---------------
  pack .lf
  grid .lf.lgx .lf.sgx
  grid .lf.lgy .lf.sgy
  update
    # events
    # ---------------
  .lf.sgx config -command compute_px
  .lf.sgy config -command compute_py
  wm protocol . WM_DELETE_WINDOW exit

  # ---------------
  # go
  # ---------------

  compute $px $py

Voir aussi


Questions/réponses

Le calcul est fait dans la procédure compute. Celle-ci reçoit les paramètres px et py. Les variables globales ::px et ::py contiennent les valeurs courantes (mises à jour par l'interface) de ces paramètres.

À chaque boucle de calcul, la procédure compare px et ::px, py et ::py. S'il y a une différence c'est que l'utilisateur a demandé un nouveau calcul et il faut aussitôt arrêter celui qui est en cours (la procédure retourne aussitôt vers l'appelant).

--

À la fin de chaque boucle de calcul de la procédure compute, l'instruction update est appelée. Cela permet à la boucle des évènements de traiter les mouvements des widgets échelles.

La récursivité est contrôlée par le nombre maximum d'appels rapprochés des procédures compute_px et compute_py. Les échelles allant de -1 à +1 par pas de 0.05, il y a donc - au plus - 40 appels rapprochés de chacJL.


12-02-2005 - Comment garder son interface vivante, ulis

20-08-2012 - Corrections mineures


Catégorie Tutoriel