Une remarque pertinente ?
Une critique impertinente ?
Un lynchage en règle ?
Une invitation sous les tropiques ?

Ecrivez-moi !
Conçu et enseigné tel qu'en lui même, avec pertes, fracas et humour de qualité supérieure            
 par Christophe Darmangeat dans le M2 PISE du Master SSAMECI (Université Paris 7)            

 
 
A lire :

Le communisme primitif
n'est plus ce qu'il était


Aux origines de
l'oppression des femmes

Mon premier livre : 464 pages à propos des sociétés primitives, des sociétés préhistoriques, de la famille, des rapports entre les sexes et de bien d'autres choses encore. On est certes fort loin de l'informatique, mais avouez que ça n'est pas toujours plus mal.

Prix public : 20 €.

Disponible (entre autres) à la FNAC.

Critiques, réactions et discussions liées à l'ouvrage sur mon blog.
Ne manquez surtout pas
mon groupe de rock, les fabuleux
Midnight Jokers !
Jouer du rock c'est bien,
jouer de la country,
c'est bien aussi !
Écoutez aussi les improbables
Moonlight Swampers !
Vous aimez les petites bêtes qui vivent sous l'eau ?
Faites donc un tour vers mes photos sous-marines
 
  
 
 
 
Partie 8
Événements insolites
 
Il est temps à présent d'examiner plus en détail les différents événements que nous permet de gérer VB. Car il faut bien l'avouer, jusqu'ici, à part des clics, des clics et encore des clics, on n'a pas vu grand chose. Et ce serait bien dommage de s'en tenir là.

1. La notion de Focus
En parlant du Focus, ne pas oublier de prononcer le "s" final, sans quoi cela risque de prêter à confusion.
A part ça, le focus, dans une application Windows, désigne le curseur, au sens le plus général du terme. C'est lorsqu'un contrôle possède le "focus" qu'il devient concerné par la frappe d'une touche au clavier (la touche Entrée produisant l'enfoncement d'un bouton, par exemple). Selon les contrôles, le focus se matérialise à l'écran par un curseur clignotant (dans une Textbox), ou par un liseré sombre (sur un Button).
Du point de vue de l'utilisateur, il y a deux moyens de déplacer le focus :
  • en cliquant directement avec la souris sur le contrôle désiré (certains contrôles, tels le bouton, ne peuvent toutefois pas recevoir le focus de cette manière, car le clic de la souris y produit directement... un Click)
  • en appuyant sur la touche de tabulation, qui fait circuler le focus d'un contrôle à l'autre.
L'ordre de passage du focus est régi par la propriété TabIndex de chaque contrôle : le contrôle qui reçoit le focus par défaut, au lancement de la Form, est celui dont le TabIndex vaut zéro. Il va de soi que VB veille à ce que deux contrôles de la même Form ne puissent jamais posséder la même valeur de TabIndex (il empêche automatiquement qu'il y ait des doublons, ou des trous dans la numérotation).
Voyons maintenant le point de vue du programmeur. On peut tout aussi bien placer d'autorité le focus sur un contrôle par du code, en lui appliquant la méthode... Focus. Celle-ci peut s'employer pour presque tous les contrôles... excepté pour ceux qui ne peuvent recevoir le focus. Étonnant, non ?
Mais le code permet également de détecter l'arrivée du focus sur un contrôle, ou son départ. Il suffit pour cela de gérer respectivement les événements Gotfocus et Lostfocus, eux aussi disponibles pour la quasi-totalité des contrôles.
2. Les événements clavier
Dans un certain nombre d'applications, on peut souhaiter attribuer certaines conséquences à la frappe de certaines touches du clavier. Par exemple, la touche F1 doit ouvrir le fichier d'aide. Autre exemple, vous pilotez en temps réel les mouvement de Zorglub, le grandiose vaisseau de l'hyperespace, grâce aux touches de direction.
Tout cela suppose que la frappe de telle ou telle touche du clavier soit interprétée par le logiciel comme un événement. Aucun problème, Billou s'occupe de nous, et pour ce faire nous offre trois événements, pas un de moins.
  • Keypress : cet événement détecte le fait qu'un caractère a été frappé au clavier.
  • Keydown et Keyup : ces deux événements, qui fonctionnent de pair, se déclenchent lorsqu'une touche du clavier est enfoncée (Keydown) ou relâchée (Keyup). La caractéristique de ces deux événements est qu'ils détectent l'état physique du clavier.
Remarque finaude :
Cela signifie que les touches ne produisant pas de caractères, telles les touches de fonction, ou les touches de direction, ne génèrent pas l'événement Keypress, mais génèrent les événements KeyDown et KeyUp
Revenons-en à présent à un aspect sur lequel, jusqu'à maintenant, nous ne nous sommes pas arrêtés autant qu'il le mérite : je veux parler des paramètres en entrée des procédures événementielles.
Pour ce qui est des généralités sur les paramètres en entrée d'une procédure, je ne vous ferai pas l'affront de vous réexpliquer ce dont il s'agit. Vous êtes blindés en algo, et je sais que jamais vous ne vous seriez lancé dans ce cours sans être des cadors sur la question. Passons donc rapidement, comme il sied de le faire en présence d'experts.
Dans une procédure événementielle, nous avons toujours pu constater, quel que soit l'événement, que VB organisait le passage de deux paramètres : le fameux Sender, et le mystérieux e.
Fameux, le Sender, puisqu'on a déjà vu qu'il s'agissait d'une variable faisant référence à l'objet qui a déclenché la procédure. Ceci s'est avéré particulièrement utile lorsque plusieurs objets étaient branchés (par exemple en cas de clic) sur la même procédure : le paramètre Sender nous a alors permis, au sein de cette procédure, d'identifier lequel des contrôles avait déclenché la procédure, et d'avoir accès à ses propriétés.
Le paramètre e est également une variable qui désigne un objet. Mais cet objet n'est pas le contrôle qui a déclenché la procédure (évidemment, puisque c'est Sender, on ne va pas mettre deux fois la même information sous deux noms différents, eh, patate). D'ailleurs, le paramètre objet e n'est pas un contrôle du tout. Il représente, si l'on veut, les conditions, ou les résultats, comme on préfère, de l'événement lui-même. La nature de ses propriétés variera donc d'un événement à l'autre.
S'il s'agit d'un événement Click, disons-le tout net, il n'y a pour ainsi dire aucune propriété dans e, car rien n'est plus tristement banal et sans caractéristiques particulières qu'un clic. En revanche, s'il s'agit d'un événement clavier, c'est tout de suite beaucoup plus intéressant.
Par exemple, lors d'un KeyDown ou d'un KeyUp, e possèdera tout un tas de propriétés booléennes (Shift, Alt) ou numériques (Keycode) nous permettant de savoir dans les menus détails quel était l'état du clavier lors du déclenchement de l'événement.
Lors d'un KeyPress, nous trouvons pour le paramètre en entrée e la propriété caractère KeyChar, contenant le caractère généré par la touche pressée.
Ainsi, le code qui afficherait dans une MessageBox une à une les touches frappées au clavier serait :
Private Sub Button1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles Button1.KeyPress
  Dim tutu As Integer
  tutu = MsgBox(e.KeyChar, vbOKOnly, "Touche frappée :")
End Sub
Et voilà le travail, enveloppez, c'est pesé.
Il nous reste toutefois un petit détail à régler avant d'en avoir définitivement terminé avec les événements clavier. Imaginons que nous voulions réaliser un "appel à l'aide" avec la touche F1. Pas le choix, nous devrons passer par un KeyDown (car la touche F1 n'engendrant aucun caractère, elle ne produit donc pas d'événement Keypress). Mais là où ça coince, c'est quand on réfléchit à quel contrôle nous devrons affecter l'événement. En effet, le focus étant supposé pouvoir se trouver sur bien des endroits de la Form lorsqu'on appuiera sur la touche F1, il faudrait en bonne logique créer une procédure Chmoll.KeyDown pour chacun des contrôles Chmoll susceptibles de posséder le focus. On n'est pas rendu.
Heureusement, il y a une autre possibilité : faire en sorte que la Form court-circuite tous les contrôles qui se trouvent sur elle en cas de frappe de touche au clavier. Il suffit pour cela de régler sa propriété KeyPreview (qu'on pourrait traduire approximativement par "interception du clavier") à True. On n'a plus alors qu'à écrire une seule procédure, celle qui gère l'événement KeyDown sur la Form. Et dans cette  procédure, à tester si la touche frappée était bien F1, auquel cas on déclenche l'ouverture de l'aide. Et hop, comme qui rigole.

3. Événements Souris
Bon, dans la vie d'un informaticien, il n'y a pas que le clavier. Il y a aussi la souris. Et la souris, petit animal vif et malicieux, ça peut faire plein de choses. Ça peut survoler un contrôle. Ça peut se faire appuyer un bouton (ou relâcher un bouton précédemment appuyé) pendant qu'elle est au-dessus d'un contrôle... Bref, une souris, c'est capable d'engendrer une foultitude d'événements aussi intéressants que variés.
Trois de ces événements ne requièrent pas plus de commentaires que cela :
  • MouseHover : qui détecte le passage de la souris sur un contrôle
  • MouseEnter : qui détecte l'arrivée de la souris sur un contrôle
  • MouseLeave : qui détecte la sortie de la souris d'un contrôle
De ces trois événements, qui effectuent le service minimum, et qui n'envoient quasiment aucun paramètre à la procédure qu'ils appellent, il n'y a donc pas grand chose à dire de plus. En revanche, d'autres événements vont nous permettre, via les propriétés du paramètre e, de récupérer des tas de renseignements utiles.
  • MouseDown : événement produit par le fait qu'un bouton de la souris vient d'être enfoncé au-dessus d'un contrôle
  • MouseUp : événement produit par le fait qu'un bouton de la souris vient d'être relâché au-dessus d'un contrôle
  • MouseMove : événement produit par le déplacement de la souris au-dessus d'un contrôle
Ces trois événements génèrent donc un objet e comportant plusieurs propriétés, dont les plus intéressantes sont :
  • Button : qui indique quel est le bouton de la souris à l'origine de l'événement, parmi la liste suivante : Left, Right, Middle, None.
  • X et Y : qui désignent les coordonnées de la souris par rapport au contrôle qui reçoit l'événement (et non par rapport à la Form, attention, piège à... étudiants, et aux autres aussi)
Remarque féline :
A noter qu'un contrôle peut partir à la chasse à la souris, et la "capturer" ! C'est-à-dire qu'il peut capter les événements souris, même s'ils ne se produisent pas au-dessus de lui... Il faut pour cela mettre la propriété Capture du contrôle à True.
En-dehors des événements qu'elle est capable de produire, la souris est également intéressante dans la mesure où son curseur est un moyen très simple d'informer l'utilisateur de ce qu'il va pouvoir faire avec les différentes zones d'une application. Windows, Word, Excel, entre autres éminents exemples, passent leur temps à triturer le curseur de la souris pour dire à l'utilisateur que là il peut rétrécir une fenêtre, que là il peut élargir une colonne, que là il peut sélectionner toute une ligne, etc.
Savoir manipuler l'apparence du curseur de la souris est donc une chose qui ne coûte pas cher en termes de savoir-faire technique, et qui est très payante pour l'ergonomie d'une application.
Pour commencer, il faut savoir que seul des fichiers de type cursor (*.cur) ou icone ("*.ico) peuvent devenir un curseur de souris. Si vous voulez créer des curseurs personnalisés à partir de l'image de votre choix, il faudra donc avant toute chose prendre soin de convertir cette image dans le format adéquat. Une manière simple de procéder est d'utiliser pour cela l'éditeur incorporé dans Visual Studio, auquel vous aurez accès via la commande Projet - Ajouter un nouvel élément - Fichier curseur.
Remarque incidente :
Au passage, vous noterez que Visual Studio contient en interne un véritable bric-à-brac  d'outils graphiques, permettant de créer et modifier non seulement des curseurs, mais aussi des icônes, et des tas d'autres choses encore.
Il existe toutefois une série de curseurs prédéfinis (les curseurs standard de Windows), qui apparaissent sous la forme de membres statiques de la classe Cursors. Par exemple, le (trop) célèbre sablier est désigné par Cursors.Waitcursor. Nous reviendrons plus loin sur ce qu'est un "membre statique". Mais pour l'instant, contentons-nous de noter que pour modifier l'apparence du curseur au-dessus d'un contrôle, et le transformer, par exemple, dans le (trop) célèbre sablier, on peut - en-dehors du fait de modifier la valeur par défaut de la propriété Cursor de ce contrôle - taper le code suivant :
Button1.Cursor = Cursors.WaitCursor
Dans le cas d'un curseur personnalisé existant sous la forme d'un fichier, on entrera :
Button1.Cursor = New Cursor("MonFichierCurseur.cur")
Première remarque au cas où :
Lorsqu'on veut modifier un curseur, on n'est pas obligé de modifier le curseur par défaut du contrôle. On peut aussi se contenter de modifier le curseur actuel, à savoir :
Current.Cursor
Deuxième remarque au cas où :
Lorsqu'on charge un fichier pour jouer le rôle de curseur, celui-ci peut être de type *.cur ou de type *.ico. Cette dernière possibilité est particulièrement intéressante pour produire des effets du meilleur goût (et donner notamment l'illusion qu'on déplace des images alors qu'on ne déplace que le curseur...)
Et voilà le travail, c'est aussi simple que cela. Allez, distrayons-nous un peu :
 
Exercice
Exécutable
Sources
Questionnaire

3. Gérer le Glisser - Déposer (Drag & Drop)
Un des trucs balaises dans l'interface graphique de Windows, c'est qu'on peut se servir de sa souris pour prendre des trucs à un endroit, les trimballer et aller les mettre ailleurs. La quasi-totalité des logiciels exploitent cette possibilité, et il serait quand bien même bien dommage que nous n'apprenions pas à programmer avec VB ce qu'on appelle en français le "Glisser - Déposer", et en anglais le "Drag and Drop". N'est-il pas ?
Cela dit, mieux vaut le savoir, mettre en oeuvre le Drag and Drop, cela suppose une fieffée dose de patience et de rigueur, car le moins qu'on puisse dire, c'est que ça ne glisse pas comme sur des roulettes et qu'à la fin, c'est souvent les armes qu'on dépose. Mais bon, en y allant le plus rationnellement et le plus méthodiquement possible, on peut espérer s'en sortir vivants.

3.1 Approche générale
La première chose à comprendre, c'est qu'un Drag and Drop est constitué de trois événements obligatoires, séparés par une quantité variable d'événements facultatifs. Ces trois événements incontournables se situent au point de départ et au point d'arrivée de la manip :
  • au départ, il faut autoriser (et gérer) le fait qu'un contrôle puisse être alpagué par la souris, et que lui même, ou une de ses propriétés, puisse être trimballé. Parce que par défaut, aucun contrôle normalement constitué ne peut subir un "Glisser", ou un "Drag". Cela se fait en passant au contrôle la méthode DoDragDrop.
  • il faut également autoriser le fait que tel ou tel contrôle puisse recevoir le largage. Car a priori, aucun contrôle n'accepte de son plein gré d'être la cible d'un "drop". Ceci ne peut se faire qu'en réglant la propriété AllowDrop du (des) contrôle(s) cible(s) à True.
    Remarque :
    Ces deux propriétés doivent être modifiées par du code qui, tant qu'à faire, et pour des raisons aisément compréhensibles, sera exécuté juste avant que le Drag & Drop ne se déclenche.
    Traditionnellement, on considère qu'un bon endroit pour écrire ce code est la procédure MouseDown du contrôle qui subit le Drag.
    Piège mortel :
    Si l'on s'occupe du DoDragDrop et du Allowdrop dans la procédure MouseDown, alors il faut absolument écrire les Allowdrop avant le DoDragDrop, faute de voir toute l'affaire marcher de manière boiteuse.
    C'est étrange et un peu déroutant, mais c'est comme ça.
  • à l'arrivée, il faut programmer ce qui se produit lorsque le bidule que représente le curseur de la souris va être largué au-dessus du (des) contrôle(s) cible(s). Et cela se décompose en deux événements : DragEnter (entrée du curseur au-dessus du contrôle susceptible de recevoir un Drop) et DragDrop (lâchage proprement dit).
Aux trois tâches à accomplir, correspondent donc très logiquement trois événements à gérer. Ce qu'il y a d'hilarant dans l'affaire, c'est que ces trois événements ne correspondent pas aux trois tâches en question ! Un événement (MouseDown) s'occupe de deux des tâches, alors que la troisième tâche (la gestion du Drop) mobilise à elle seule deux événements (DragEnter et DragDrop).

3.2 Les trois événements cruciaux
Résumons-nous, en prenant pour le moment un exemple simple : on va autoriser l'utilisateur à prendre le texte d'un Label (que nous appellerons Etiquette) pour le poser dans une TextBox (que nous appellerons Arthur, parce que c'est un joli nom, et que si on l'appelait Perceval, ça risquerait de nous porter la poisse.

Première étape :
Nous devons autoriser Etiquette à être l'objet d'un Drag, et Arthur à être la cible d'un Drop. Pour le Drag, la méthode DoDragDrop devra préciser deux paramètres :
  • la nature des informations qui seront transmises à l'objet e, objet qui sera créé lors du Drag, qui ne disparaîtra qu'avec le Drop, et dont les propriétés resteront à notre disposition en permanence entre ces deux moments. Je rappelle que e n'est autre que ce fameux objet qui figure comme paramètre de certaines procédures événementielles, et dont nous ne nous étions guère préoccupés jusque là. Je rappelle également - au cas où - que si Sender représente l'objet qui a déclenché la procédure, e incarne quant à lui l'événement lui même, et ses propriétés représentent donc en quelque sorte les circonstances de cet événement.
    VB nous demande donc de préciser par ce paramètre, lors du DoDragDrop, quelles sont les données qui seront affectées à la propriété Data de l'objet e.
    Ce paramètre a donc parfois une grande importance, et parfois il n'en a aucune : tout dépend, en gros, si plusieurs objets peuvent être à l'origine du Drag, ou s'il n'y en a qu'un seul. Dans le premier cas, nous aurons vraisemblablement besoin de récupérer lors du Drop l'information qui aura été passée en paramètre. Dans le second cas, la récupération de la propriété Data de e n'est pas utile, et on peut donc affecter à peu près n'importe quoi à e.Data !
    Considérons ici le cas le plus compliqué : plusieurs contrôles seront susceptibles d'être  l'objet d'un Drag. Étant donné que ce qui nous intéresse est le texte du Label, nous affecterons à e.Data la propriété Text d'Etiquette.
  • second paramètre, le type d'effets que le Drag and Drop pourra produire (parmi une liste prédéfinie). Le choix le plus simple est bien entendu All. Mais on pourrait d'ores et déjà restreindre les effets possibles en optant pour une des autres possibilités.
Nous entrerons donc, dans la procédure Etiquette.MouseDown :
Arthur.AllowDrop = True
Etiquette.DoDragDrop(Etiquette.Text, DragDropEffects.All)

Deuxième étape :
Nous devons gérer à présent l'entrée du curseur (après un Drag) dans la zone de Drop, c'est-à-dire au-dessus du contrôle Arthur. C'est là, dans cette procédure Arthur.DragEnter, que nous devrons préciser l'effet qui devra se produire lors du Drop. A noter que cette procédure, et cette instruction, sont indispensables, quand bien même on aura déjà précisé les possibilités lors du MouseDown. Si l'on veut, lors du MouseDown, on n'a fait que définir ce qui serait possible. Là, il faut dire ce qui va vraiment se passer.
Cela se fait en affectant la propriété Effects de l'objet e, via une énumération :
e.Effects = DragDropEffects.All

Troisième étape :
Il ne nous reste plus qu'à préciser, dans la procédure Arthur.DragDrop, ce qui doit se passer lors du largage. Ici, c'est très simple : Arthur doit prendre le texte qui se trouvait dans Etiquette. Si nous avions été sûrs que seul Etiquette avait pu être victime d'un Drag, l'affaire auraiot été un peu plus simple. Mais nous avons choisi de traiter le cas général, celui où l'information a été passée lors du MouseDown à la propriété Data de l'objet e.
Récupérer les données trimballées dans la propriété Data de e n'est pas une mince affaire. On ne peut consulter cette propriété qu'en lui appliquant la méthode GetData, méthode exigeant elle-même qu'on précise le format des données à récupérer... données qui doivent être ensuite converties dans le format approprié par la méthode adéquate ! Bref, la simplicité même, dans la plus pure tradition Petitmou.
Dans notre exemple, cela donne :
Arthur.Text = e.Data.GetData(DataFormats.Text).ToString
Eh oui, quand même, ça calme, hein. Et là, c'est un des exemples les plus simples qu'on puisse imaginer. C'est tout dire.

3.3 Raffinements divers
Ce que nous venons de voir, c'est la base minimale, sans laquelle aucun Drag & Drop n'est possible. Mais on peut tout à fait enrichir l'interface, en tripatouillant la tête du curseur de la souris, en affichant ça ou là des informations, etc. Je me contente ici d'indiquer quelques pistes, tant le sujet est vaste.
Pour commencer, jetons un oeil rapide sur les autres événements qu'il est possible de gérer lors d'un Drag & Drop.
  • DragLeave : c'est en quelque sorte l'inverse du DragEnter. Cet événement se produit lorsque le curseur, pendant un Drag & Drop, quitte le contrôle concerné. Il est particulièrement utile pour gérer le fait que le curseur de la souris sorte du cadre de la fenêtre de l'application (c'est-à-dire de la Form).
  • DragOver : cet événement est un peu l'équivalent du MouseMove en cas de Drag & Drop. Il se déclenche pour tout mouvement de la souris au-dessus du contrôle concerné.
  • GiveFeedback : cet événement est déclenché dès que le Drag commence (il suit donc, chronologiquement, le MouseDown lorsque celui-ci provoque un Drag). C'est cet événement qui doit être utilisé si l'on souhaite programmer un curseur personnalisé durant le Drag and Drop.
Ensuite, j'ai parlé du curseur de la souris : il est en effet bon de savoir qu'en cas de besoin, on peut par exemple procéder à une gestion "fine" des coordonnées de la souris durant un Drag & Drop. En effet, chaque événement où la souris est impliquée - et lors d'un Drag & Drop, ils le sont tous ! - envoie au paramètre e deux propriétés, X et Y, qui précisent les coordonnées de la souris au moment du déclenchement de l'événement.
Attention toutefois ! Selon le type d'événement concerné, ces coordonnées e.X et e.Y sont stipulées par rapport au contrôle qui reçoit l'événement, ou par rapport à l'écran !!! Autant vous dire qu'il ne va pas falloir s'étonner de certains résultats surréalistes... Dans ce cas, la démarche sera toujours la même : vérifier dans l'aide, et effectuer les conversions de coordonnées nécessaires, comme on aura bientôt l'occasion de le faire dans de croustillants exercices.
La souris communique d'autres informations à l'événement e, par exemple l'état de ses boutons - ce qui permet de différencier un clic gauche d'un clic droit.
Enfin, notre bonheur ne serait pas complet si j'omettais de signaler que certains contrôles posent des problèmes particuliers pour le Drag & Drop - et disposent donc de solutions particulières. Il s'agit en particulier des ListBox, Combobox et autres Treeview, qui permettent de Glisser - Déposer un de leurs éléments, en allant le positionner à un endroit précis. Mais là, mon courage pourtant légendaire m'abandonne, et je renvoie les programmeurs concernés à des exemples de code traînant dans les bouquins ou sur le Net.
Bon, assez discuté, je sens que vous trépignez d'impatience à l'idée de mettre tout cela en pratique.
L'exercice Lapins est une introduction au Drag & Drop, qui ne pose aucune difficulté particulière.
Just a pawn in their game, en revanche, est nettement plus... stimulant.
Tout d'abord, il faudra absolument décompresser les deux fichiers *.ico livrés avec l'exécutable dans le même répertoire que celui-ci. Cette solution est certes inélégante, mais nous n'apprendrons que plus tard à procéder de la bonne façon.
Ensuite, le programme pose quelques petites difficultés, en premier lieu parvenir à susciter l'illusion que l'on promène les pions. Cela vous rappelle-t-il une remarque faite un peu plus haut ?
Exercice
Exécutable
Sources
Lapins
Just a pawn in their game