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 MECI (Université Paris 7)            

 
 
 
 
 
 
Partie 8
Événements insolites
 
Il est temps à présent d'examiner plus en détail différents autres é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. Or, il serait bien dommage de s'en tenir là, vu les possibilités réjouissantes qu'offre VB en la matière.
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. Méfiance tout de même, il se peut qu'après suppressions et créations, deux contrôles de la même Form puissent posséder la même valeur de TabIndex, ou 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
2.1 Une première approche...
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 mouvements 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, Bilou 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
2.2 Où l'on parle (enfin) de l'autre paramètre
C'est le moment ou jamais de revenir sur un point où, jusqu'à maintenant, nous ne nous sommes pas arrêtés autant qu'il le mérite - pour être très précis, nous avons fait les choses à moitié : je veux parler des paramètres en entrée des procédures événementielles.
En effet, nous avons pu constater que dans toute procédure évènementielle, quelle qu'elle soit, VB imposait le passage de deux paramètres : le fameux Sender, et le mystérieux e.
Fameux, le Sender, puisqu'on l'a vu (et tripoté) à de nombreuses reprises. Est-il encore besoin de rappeler qu'il s'agit d'une variable faisant référence à l'objet qui a déclenché la procédure ? Ce paramètre s'est avéré particulièrement utile (et même, franchement indispensable) lorsque des évènements impliquant plusieurs objets différents étaient branchés 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.
Il est à présent grand temps de révéler la seconde moitié du mystère, à savoir la nature et la fonction de l'étrange paramètre e. Lui aussi est 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, VB ne va pas mettre deux fois la même information sous deux noms différents, eh, patate). D'ailleurs, l'objet pointé par e n'est pas un contrôle du tout. Il représente, si l'on veut, les conditions, les circonstances, ou les résultats, comme on préfère, de l'événement lui-même. La nature de ses propriétés varie donc d'un événement à l'autre.
Remarque « tout se tient »
C'est bien pourquoi chaque type d'action va de pair avec un type différent de e, et que les procédures gérant une action donnée ne peuvent être utilisée pour en gérer une 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. C'est cette dernière propriété qui nous permettra d'identifier, par son code, quelle était la touche qui vient d'être enfoncée ou relâchée.
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é.
2.3 Un dernier (tout petit) détail
Il nous reste toutefois une petite chose à 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
3.1 Évènements peu remarquables
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 (nuance délicieusement subtile avec le précédent)
  • 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 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.
3.2 Évènements beaucoup plus intéressants
  • 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 (similaire donc à MouseHover... mais en mieux !)
Ces trois événements génèrent un objet e comportant plusieurs propriétés tout à fait captivantes :
  • 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 à modifier 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.
Manipuler l'apparence du curseur de la souris à bon escient 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 seuls 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 sablier, si célèbre chez les plus de 40 ans, 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, en sablier, on doit modifier sa propriété Cursor...
  • soit en changeant sa valeur par défaut
  • soit en la modifiant via une instruction, par exemple :
Button1.Cursor = Cursors.WaitCursor
Dans le cas où l'on veut utiliser un curseur personnalisé existant sous la forme d'un fichier, le code sera forcément un peu plus compliqué...
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. Une alternative consiste à 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 certains « effets spéciaux » (et donner notamment l'illusion qu'on déplace de petites images, telles des pions d'un jeu, 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. 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
3.1.1 Les préparatifs
Un Drag & Drop est un traitement un chouia complexe, composé d'un certain nombre d'instructions et de procédures évènementielles obligatoires. Et naturellement, on peut enrichir tout cela par des choses plus facultatives. Comme à chaque jour suffit sa peine, commençons par le strict nécessaire. Un Drag & Drop implique toujours au moins deux contrôles :
  1. celui qui est « draggé » (que l'Académie me pardonne ce néologisme). C''est le contrôle sur lequel on va enfoncer le bouton gauche de la souris (Drag...) afin de déclencher les opérations - je me propose de l'appeler, dans la suite de ce texte, ContrôleD (avec un D comme Départ).
  2. celui au-dessus duquel on va relâcher le bouton de la souris pour provoquer le Drop. J'appellerai ce contrôle ContrôleC (avec un C comme Cible).
Remarque de bonne méthode :
Ces définitions sont totalement indépendantes des effets précis recherchés par le Drag & Drop. Elles s'appliquent donc à toutes les situations sans exception. La première chose à faire avant d'écrire quelque ligne que ce soit consiste donc à identifier, dans le problème que l'on veut traiter, quel contrôle jouera le rôle de ContrôleD, et lequel celui de ContrôleC.
Poursuivons. Pour qu'il puisse se passer quoi que ce soit, deux conditions indispensables doivent être présentes.
  1. il faut que le Drag puisse être effectué sur ContrôleD. En réalité, cela ne nécessite aucune instruction particulière, hormis de gérer convenablement l'événènement qui inaugure un Drag, à savoir ContrôleD.MouseDown.
  2. il faut également que le Drop puisse être effectué sur ContrôleC. Mais là, attention : en plus de devoir gérer l'événement correspondant, à savoir ContrôleC.DragDrop, nous aurons dû préalablement modifier une propriété de ContrôleC, soit en changeant carément sa valeur par défaut, soit en écrivant :
ControleC.Allowdrop = True
Naturellement, cette instruction peut être placée en bien des endroits... pourvu qu'elle soit exécutée avant le début du Drag & Drop ! (ça paraît logique...). Donc, on fera modifier cette propriété de la manière jugée la plus appropriée, simplement sans perdre de vue cette petite contrainte.
3.1.2 Le décollage
À ce stade de la compétition, tout est prêt. Les rampes de lancement sont dressées, les moteurs allumés. Il n'y a plus qu'à effectuer la mise à feu. Celle-ci, le déclenchement du Drag & Drop proprement dit, s'effectue via le passage de la méthode DoDragDrop à ContrôleD. Le truc, c'est que cette réclame deux arguments pas évidents à comprendre par la simple intuition :
  • Data : il s'agit d'un objet, chargé en fait de transmettre des informations lors du Drag & Drop. J'y reviendrai dans la partie suivante, mais sachez qu'on peut mettre absolument ce qu'on veut dans cet argument, comme par exemple la chaîne de caractère "AstalavistaBaby".
  • AllowedEffects : il s'agit d'un paramètre censé gérer les effets du Drag & Drop. Le truc, c'est que je n'ai jamais compris, ou trouvé d'informations, sur la manière dont il influerait sur lesdits effets. On pourra donc considérer que sa seule implication réelle est de modifier l'aspect du curseur durant le Drag & Drop. Les valeurs de cette méthode sont les membres d'une énumération parmi All, Copy, link, Move, None, Scroll, chacune étant associée à une forme de curseur différente.
Tout cela donnea donc une ligne pouvant par exemple ressembler à :
ControleD.DoDragDrop("AstalavistaBaby", DragDropEffects.All)
Remarque de mise à feu :
Le passage de la méthode DoDragDrop déclenche de manière immédiate l'interruption de l'exécution de la procédure en cours (MouseDown) et le déclenchement des autres procédures liées au Drag & Drop. Ce n'est qu'une fois celles-ci terminées que VB reviendra à la procédure MouseDown interrompue, et terminera son exécution.
Des instructions placées avant le DoDragDrop seront donc exécutées avant même le décollage, des instructions placées après ne sront exécutées qu'une fois l'aterrissage effectué...
3.1.3 Le survol
À partir du moment où l'instruction DoDragDrop est exécutée, le Drag & Drop proprement dit est enclenché. On pourrait (naïvement) penser que celui-ci se limite ensuite à gérer le Drop, c'est-à-dire l'atterrissage. Mais nenni. Visual Basic nous impose de gérer un évènement supplémentaire, sans lequel rien ne fonctionnera. C'est d'autant plus déroutant que cet évènement semble ne pas servir à grand chose, mais bon, pas le choix, il faut faire, on fait.
L'évènement en question est celui cui conrrespond à l'arrivée du curseur au-dessus du contrôle cible, autrement dit ControleC.DragEnter. Et cette procédure devra comporter une instruction, et une seule, consistant à affectuer la propriété Effects de l'objet e, par un membre de l'émnumération déjà rencontrée concernant les images de curseur. On aura donc, par exemple :
e.Effect = DragDropEffects.All
Dussé-je le rappeler, cette instruction ne possède aucun autre effet tangible que de modifier l'aspect du curseur (ou de le conserver si, comme c'est vraisemblable, on lui donne ici la même valeur que lors du DoDragDrop).
3.1.4 L'atterrissage
On en arrive à l'ultime étape : le Drop proprement dit. Celle-ci ne pose aucune difficulté : il suffit de gérer l'évènement adéquat, soit ControleC.DragDrop. C'est dans cette procédure qu'on pourra donner toutes les instructions corespondant aux effets désirés.
Remarque résumeuse :
Un Drag & Drop réclame donc au strict minimum la création de trois procédures évènementielles (ControleD.MouseDown, ControleC.DragDrop et ControleC.DragDrop). Naturellement, ce nombre peut être amené à grimper si l'on souhaite gérer plusieurs cibles pour le même contrôle de départ, plusieurs contrôles de départ pour la même cible ou plusieurs contrôles de départ pour plusieurs cibles.
3.2 Programmer les effets du Drag & Drop
Jusqu'ici, nous avons confectionné un superbe écrin... vide. En effet, nous avons vu dans le détail comment programmer l'ensemble des évènements d'un Drag & Drop, mais finalement, cette belle série de cabrioles ne mène (pour l'instant) à rien du tout. On prend un cntrôle (ou un élément de contrôle), on le trimbale, on relâche le bouton, et là... rien. Il est donc grand temps de couronner notre édifice.
3.2.1 La méthode officielle (déconseillée)
Théoriquement, l'effet le plus courant d'un Drag & Drop, à savoir Le transport d'une information depuis le contrôle D vers le contrôle C, peut être pris en charge par un des objets qui sont générés par les évènements du Drag & Drop, à savoir le fameux paramètre e ; et plus précisément, par sa propriété Data. Toute l'affaire se déroule en deux temps :
  • au démarrage du Drag and Drop, c'est-à-dire lors du passage de la méthode DoDragDrop, l'information voulue est passée à e : c'est le premier des deux arguments de cette méthode, celui pour lequel on a rentré tout à l'heure la première chose qui nous passait par la tête, à savoir "AstalavistaBaby". Mais on aurait tout aussi bien pu écrire ici une valeur plus significative et utile pour notre problème, comme le nom d'un contrôle, la valeur de l'une de ses propriétés, un de ses items si c'est une liste, etc.
  • lors de l'atterrissage, c'est à dire dans la procédure DragDrop, on peut récupérer l'information, qui se trouve dans la propriété e.Data. Sauf que... sauf que, cette récupération pose des problèmes de syntaxe à ingurgiter des aspirines par seaux entiers. pour commencerune, quel que soit la nature de son contenu, pas question d'accéder directement à l'information recherchée. Il va impérativement falloir recourir à la méthode GetData. Ce qui donnera donc un très joli :
    e.Data.GetData
    Mais les ennuis ne font que commencer. Si les informations qu'on a rangées dans cette propriété ont le bon goût d'être d'une nature simple (du texte), voilà ce que cela donne : il préciser de surcroît le format dans lequel on veut récupérer son information, par le très joli DataFormats.Text... sans omettre de préciser que VB doit convertir le tout en type String ! e.Data.GetData(DataFormats.Text).ToString
Tout ça pour une malheureuse donnée de type texte ! Comme disait (à peu près) Obélix : « Ils sont fous chez Redmond ! » Car, est-il besoin de le préciser, dès que la donnée stockée dans e sera d'un type plus complexe (structure, image...) la récupérer dans le e.Data va devenir une vraie torture, où l'on pourra passer des heures à chercher (en vain) la syntaxe adéquate. Voilà pourquoi, très franchement, je ne vois aucun avantage à utiliser les voies officielles, et je les vois tous à choisir les chemins interlopes.
3.2.2 La technique de contrebande (de loin la meilleure)
Cette technique va consister tout simplement à ne pas utiliser l'objet e, mais à véhiculer les informations voulues tout simplement... par des variables. Simplement, étant donné les circonstances, il faudra veiller à deux choses :
  • les variables, puisqu'on les affecte dans la procédure MouseDown et qu'on récupérera a priori leur valeur dans la procédure DragDrop, devront nécessairement être publiques au niveau Form.
  • la méthode DoDragDrop exigeant que le premier de ses deux arguments, celui qui remplira le e.Data, soit renseigné, on n'oubliera pas de lui fournir... absolument n'importe quoi, puisque cette valeur aura pour seul rôle d'éviter une erreur de VB. Donc, parmi les candidats possibles, on trouvera "AstalavistaBaby", "Zigopaf" ou, plus sobrement, "Toto".
Le premier exercice, Boîte à Meuh, est censé être une simple petite mise en jambes :aucune difficulté, aucun piège.

Le suivant, Lapins est déjà un peu plus coton...

Just a pawn in their game, en plus d'être franchement stimulant, nécessite de veiller à quelques détails techniques. En effet, il faudra 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
Boîte à Meuh
Lapins
Just a pawn in their game