Maintenant que vous savez créer des fenêtres, les organiser, y ajouter des boutons ou des étiquettes, il est temps que tout cela agisse un petit peu. À quoi bon cliquer sur des boutons si rien ne se passe ? Nous allons devoir tout d'abord nous intéresser à la gestion bas niveau des évènements pour comprendre le fonctionnement de GTK. Puis nous retournerons au haut niveau et à la programmation évènementielle à proprement parler.
Ce chapitre est fondamental pour la suite, mais il est ardu. N'hésitez pas à le relire ou à revenir piocher dedans à l'avenir car il faut être précis sur les types ou sous-packages employés (et ils sont nombreux). GNAT sera impitoyable si vous ne respectez pas scrupuleusement mes conseils.
Le principe
Les problèmes de la programmation évènementielle
Le titre de cette partie commençait par «la programmation évènementielle», je crois. Eh bien on ne peut pas dire qu'il y ait beaucoup d'évènements dans nos programmes dernièrement. Comment je fais pour lancer mes procédures et mes fonctions ?
On serait tenté d'écrire ce genre de chose :
1 2 3 | IF Bouton1.Is_clicked THEN Ma_Procedure ; END IF ; |
Le souci, c'est que tout notre code consiste à créer et paramétrer des widgets, les afficher et enfin lancer une boucle infinie (Main
). Où écrire ces quelques lignes ? Le programme ne met que quelques millisecondes à créer les widgets et à les afficher, cela ne laisse pas vraiment le temps à l'utilisateur d'aller cliquer sur un bouton. Et puis un clic ne dure pas plus d'une seconde, comment faire pour que le programme exécute cette ligne de test durant cette fraction de seconde ? C'est impossible.
Le problème est même plus profond que cela. Nous codions jusqu'ici de manière linéaire. Certes, nous avions les conditions pour exécuter telle ou telle instruction, les boucles pour répéter ou pas certaines portions de code, les tâches pour exécuter plusieurs programmes en même temps. Mais tout cela restait très prévisible : l'utilisateur ne pouvait sortir du cadre très fermé que vous lui aviez concocté à grands coups de IF
, CASE
, LOOP
, TASK
… Avec nos programmes fenêtrés, nous ne pouvons savoir quand l'utilisateur cliquera sur notre bouton, ni si il le fera, ni même s'il aura cliqué sur d'autres widgets auparavant. Vous êtes en réalité dans l'inconnu.
Et si on créait une grande boucle infinie qui testerait en continu les différentes actions possibles, comme nous l'avions fait au TP sur la bataille navale ?
Une boucle infinie ? C'est une idée. Mais nous disposons déjà de ce genre de boucle : Main
! Main
contrôle en permanence les différents widgets créés et vérifie s'ils ne sont pas cliqués, déplacés, survolés… Bref, Main
attend le moindre évènement pour réagir.
Le principe Signal-Procédure de rappel
Pour comprendre comment faire agir nos widgets, vous devez comprendre le principe de fonctionnement bas-niveau de GTK. Lorsqu'un évènement se produit, par exemple lorsque l'utilisateur clique sur un bouton, un signal est émis.
Aussitôt, ce signal est intercepté par le Main
qui va associer le widget et le type de signal émis à une procédure particulière, appelée procédure de rappel ou callback.
Mais comment Main
peut-il savoir quelle procédure il doit appeler lorsque l'on clique sur un bouton ?
Pour que votre programme puisse faire le lien, il faut que vous ayez préalablement connecté le widget en question et son signal à cette fameuse procédure de rappel. C'est ce à quoi nous allons nous attacher maintenant.
Connecter un signal à un callback
Fermer proprement le programme
Code de base et package
Notre premier projet est simple : créer une fenêtre avec un unique bouton. Lorsque l'utilisateur cliquera sur le bouton ou sur le bouton de fermeture de la fenêtre, cela mettra fin au programme. Pour cela nous n'aurons pas à créer de procédure de rappel, nous ferons appel à des procédures préexistantes. Voici donc notre code de base (que vous devriez pouvoir déchiffrer seul désormais) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | WITH Gtk.Main ; USE Gtk.Main ; WITH Gtk.Window ; USE Gtk.Window ; WITH Gtk.Enums ; USE Gtk.Enums ; WITH Gtk.Button ; USE Gtk.Button ; PROCEDURE MaFenetre IS Win : Gtk_Window ; Btn1 : Gtk_Button ; BEGIN Init ; --Initialisation de GTK Gtk_New(Win,Window_Toplevel) ; --Création et paramétrage de la fenêtre Win.Set_Title("Fenetre") ; win.set_default_size(200,150) ; Gtk_New_With_Mnemonic(Btn1, "_Exit") ; --Création d'un bouton et ajout à la fenêtre win.add(Btn1) ; Win.Show_all ; --Affichage et lancement de la boucle d'évènements Main ; END MaFenetre ; |
Pour manipuler nos divers widget, nous aurons besoin du package Gtk.Handlers
(pour rappel, le verbe anglais «to handle» signifie «manipuler»). Attention ce package en lui-même ne contient presque rien d'utilisable. Si vous ouvrez ses spécifications, vous vous rendrez compte qu'il contient en fait tout un ensemble d'autres sous-packages génériques. Je vous expliquerai les détails plus tard. Sachez pour l'instant que nous avons besoin du sous-package générique Gtk.Handlers.Callback
. Il n'a qu'un seul paramètre de généricité : le type Widget_Type
dont le nom me semble assez clair.
Nous devons donc l'instancier en créant un nouveau package P_Callback
. Pour l'instant, nous n'allons fournir en paramètre que le type GTK_Window_Record
et nous verrons pour le bouton un peu plus tard.
1 2 | PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Window_Record) ; USE P_Callback ; |
Attention, le type fourni est bien GTK_Window_Record
et pas GTK_Window
qui est, rappelons-le, un type pointeur !
La procédure de connexion
Vous devriez trouver à la ligne 847 de Gtk.Handlers
les spécifications d'une procédure essentielle :
1 2 3 4 5 | procedure Connect ( Widget : access Widget_Type'Class; Name : Glib.Signal_Name; Cb : Simple_Handler; After : Boolean := False); |
C'est cette procédure qui va connecter le widget et le signal qu'il émet avec une procédure de votre choix. Il est important de bien regarder sa spécification si vous ne voulez pas que GNAT soit désagréable avec vous. Tout d'abord le widget : on attend un pointeur sur une classe de widget. Autrement dit, dans notre cas présent, un objet de type GTK_Window
(et pas GTK_Window_Record
). Ensuite, le paramètre Name
correspond au nom du signal émis. Ce nom est en fait un simple string
que je vous donnerai. Le dernier paramètre (After
) ne nous servira que si vous voulez imposer que la connexion ne se fasse qu'une fois toutes les connexions par défaut effectuées. Bref, aucun intérêt à notre niveau. Enfin, nous arrivons au paramètre Cb
de type Simple_Handler
. Ce type correspond à un pointeur sur une procédure prenant en paramètre un pointeur sur une classe de widget (le widget grâce auquel vous avez instancié Gtk.Handlers.Callback
). C'est donc ce paramètre qui enregistrera la procédure à connecter.
Vous devrez être le plus rigoureux possible lorsque vous utiliserez la procédure de connexion. Le moindre écart posera soucis à la compilation ! Alors n'hésitez par à revenir lire cette partie plusieurs fois.
La procédure de rappel
Nous allons créer une procédure pour sortir de la boucle d'évènements : Stop_Program
. Cette procédure utilisera l'instruction Main_Quit
qui met fin au Main
. La logique voudrait que cette procédure n'ait aucun paramètre. Pourtant, si j'ai préalablement attiré votre attention sur la procédure connect()
, c'est pour que vous compreniez comment définir votre callback. connect()
va non seulement connecter un signal à une procédure, mais il transmettra également le widget appelant à ladite procédure si ce signal est détecté. Nous voilà donc obligés d'ajouter un paramètre à notre callback. Quant à son type, il suffit de regarder les spécifications de connect()
pour le connaître : « ACCESS Widget_Type'Class
» ! Ce qui nous donnera :
1 2 3 4 | PROCEDURE Stop_Program(Emetteur : access Gtk_Window_Record'class) IS BEGIN Main_Quit; END Stop_Program ; |
Si vous compiler, GNAT ne cessera de vous dire : "warning : formal parameter "Emetteur" is not referenced". Ce n'est pas une erreur rédhibitoire mais c'est toujours gênant de laisser traîner ces avertissements. Alors pour indiquer à GNAT que le paramètre Emetteur
est sciemment inutilisé, vous pourrez ajouter le PRAGMA unreferenced
:
1 2 3 4 5 | PROCEDURE Stop_Program(Emetteur : access Gtk_Window_Record'class) IS PRAGMA Unreferenced (Emetteur); BEGIN Main_Quit; END Stop_Program ; |
La connexion, enfin !
Le package est instancié et le callback rédigé. Il ne reste plus qu'à effectuer la connexion à l'aide de la procédure vue plus haut. Pour cela, vous devez connaître le nom du signal de fermeture de la fenêtre : "destroy"
. Ce signal est envoyé par n'importe quel widget lorsqu'il est détruit. Notre code donne donc :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | WITH Gtk.Main ; USE Gtk.Main ; WITH Gtk.Window ; USE Gtk.Window ; WITH Gtk.Enums ; USE Gtk.Enums ; WITH Gtk.Button ; USE Gtk.Button ; WITH Gtk.Handlers ; PROCEDURE MaFenetre IS --Instanciation du package pour la connexion PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Window_Record) ; USE P_Callback ; --Notre callback PROCEDURE Stop_Program(Emetteur : access Gtk_Window_Record'class) IS PRAGMA Unreferenced (Emetteur); BEGIN Main_Quit; END Stop_Program ; Win : Gtk_Window ; Btn1 : Gtk_Button ; BEGIN Init ; Gtk_New(Win,Window_Toplevel) ; Win.Set_Title("Fenetre") ; win.set_default_size(200,150) ; Gtk_New_With_Mnemonic(Btn1, "_Exit") ; win.add(Btn1) ; --Connexion du signal "destroy" avec le callback Stop_program --Pensez que le signal est un string, non un type énuméré --N'oubliez pas que le 3° paramètre doit être un pointeur connect(win, "destroy", Stop_Program'access) ; --en cas de souci, essayez ceci : --connect(win, "destroy", to_Marshaller(Stop_Program'access)) ; Win.Show_all ; Main ; END MaFenetre ; |
Utiliser le bouton
Nous voudrions maintenant pouvoir faire de même avec notre bouton. Le souci, c'est que la procédure connect()
a pour nom complet P_Callback.connect()
et que le package P_Callback
a été instancié pour les objets de la classe GTK_Window_Record'class
. Deux solutions s'offrent à nous : soit créer un second package P_Callback_Button
, soit améliorer le premier.
J'opte pour la deuxième méthode. Il suffit d'utiliser une classe commune aux GTK_Window_Record et aux GTK_Button_Record. Vous savez désormais que fenêtres et boutons sont deux widgets. Nous pourrions donc utiliser la classe GTK_Widget_Record'class
et citer le package Gtk.Widget
. Mais nous savons également que ce sont deux conteneurs (GTK_Container_Record'class
) et même des conteneurs pour un seul élément (GTK_Bin_Record'class
). Nous pouvons donc généraliser notre package de trois façons différentes. Les voici, de la plus restrictive à la plus large :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | WITH Gtk.Bin ; USE Gtk.Bin ; ... PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Bin_Record) ; USE P_Callback ; PROCEDURE Stop_Program(Emetteur : access Gtk_Bin_Record'class) IS ... -- OU WITH Gtk.Container ; USE Gtk.Container ; ... PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Container_Record) ; USE P_Callback ; PROCEDURE Stop_Program(Emetteur : access Gtk_Container_Record'class) IS ... -- OU WITH Gtk.Widget ; USE Gtk.Widget ; ... PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Widget_Record) ; USE P_Callback ; PROCEDURE Stop_Program(Emetteur : access Gtk_Widget_Record'class) IS ... |
Ne reste plus qu'à effectuer la connexion. Si peu de signaux sont intéressants pour l'instant avec les fenêtres, avec les boutons, nous avons davantage de choix. Voici quelques signaux :
"clicked"
: le bouton a été cliqué, c'est-à-dire enfoncé puis relâché."pressed"
: le bouton a été enfoncé"released"
: le bouton a été relâché"enter"
: la souris survole le bouton"leave"
: la souris ne survole plus le bouton
Voici ce que donnerait notre programme :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | WITH Gtk.Main ; USE Gtk.Main ; WITH Gtk.Window ; USE Gtk.Window ; WITH Gtk.Enums ; USE Gtk.Enums ; WITH Gtk.Button ; USE Gtk.Button ; WITH Gtk.Bin ; USE Gtk.Bin ; WITH Gtk.Handlers ; PROCEDURE MaFenetre IS PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Bin_Record) ; USE P_Callback ; PROCEDURE Stop_Program(Emetteur : access Gtk_Bin_Record'class) IS PRAGMA Unreferenced (Emetteur); BEGIN Main_Quit; END Stop_Program ; Win : Gtk_Window ; Btn1 : Gtk_Button ; BEGIN Init ; Gtk_New(Win,Window_Toplevel) ; Win.Set_Title("Fenetre") ; win.set_default_size(200,150) ; Gtk_New_With_Mnemonic(Btn1, "_Exit") ; Win.Add(Btn1) ; Connect(Win, "destroy", To_Marshaller(Stop_Program'ACCESS)) ; Connect(Btn1, "clicked", To_Marshaller(Stop_Program'ACCESS)) ; Win.Show_all ; Main ; END MaFenetre ; |
Interagir avec les widgets
Un callback à deux paramètres
Fermer le programme, c'est facile : il suffit d'une seule instruction. Mais comment je fais si je veux créer des callbacks modifiant d'autres widgets ?
Pour y voir clair, nous allons créer un programme «Hello world». L'idée est simple : notre fenêtre affichera une étiquette nous saluant uniquement si nous appuyons sur un bouton. Nous avons donc besoin de créer une fenêtre contenant une étiquette, un bouton et bien-sûr un conteneur :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | WITH Gtk.Main ; USE Gtk.Main ; WITH Gtk.Window ; USE Gtk.Window ; WITH Gtk.Enums ; USE Gtk.Enums ; WITH Gtk.Button ; USE Gtk.Button ; WITH Gtk.Label ; USE Gtk.Label ; WITH Gtk.Box ; USE Gtk.Box ; WITH Gtk.widget ; USE Gtk.widget ; WITH Gtk.Handlers ; PROCEDURE MaFenetre IS --SOUS PACKAGES POUR MANIPULER LES CALLBACKS PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Widget_Record) ; USE P_Callback ; --CALLBACKS PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) IS PRAGMA Unreferenced (Emetteur ); BEGIN Main_Quit; END Stop_Program ; --WIDGETS Win : Gtk_Window ; Btn : Gtk_Button ; Lbl : Gtk_Label ; Box : Gtk_Vbox ; BEGIN Init ; --CREATION DE LA FENETRE Gtk_New(Win,Window_Toplevel) ; Win.Set_Title("Fenetre") ; win.set_default_size(200,150) ; --CREATION DU BOUTON ET DE L'ETIQUETTE Gtk_New_With_Mnemonic(Btn, "Dis _bonjour !") ; Gtk_New(Lbl,"") ; --CREATION DE LA BOITE Gtk_New_Vbox(Box) ; Box.Pack_Start(Lbl) ; Box.Pack_Start(Btn) ; Win.Add(Box) ; --CONNEXION AVEC LE CALLBACK DE FERMETURE Connect(Win, "destroy", Stop_Program'ACCESS) ; Win.Show_all ; Main ; END MaFenetre ; |
Ce code n'est pas très compliqué, vous devriez pouvoir le comprendre seul. Maintenant, nous allons créer un callback supplémentaire appelé Dis_Bonjour()
qui modifiera une étiquette (ne nous préoccupons pas de la connexion pour l'instant) :
1 2 3 4 5 6 7 8 9 | PROCEDURE Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; Label : Gtk_Label) IS PRAGMA Unreferenced(Emetteur) ; BEGIN IF Label.Get_Text'Length = 0 THEN Label.Set_Text("Hello world !") ; ELSE Label.Set_Text("Ouais, ouais ... salut !") ; END IF ; END Dis_Bonjour; |
J'utilise deux méthodes de la classe GTK_Label_Record
: Set_text()
qui modifie le texte de l'étiquette et Get_Text()
qui renvoie le contenu. Rien de bien compliqué mais un souci toutefois : nous avons besoin de deux paramètres !
Un nouveau package de callbacks
Or, pour l'instant notre package P_Callback
n'est instancié qu'avec un seul type : la classe GTK_Widget_Record
du widget émetteur. Cela signifie que nos callbacks ne peuvent avoir qu'un unique paramètre de ce même type. Comment faire pour accéder à un second widget ? Plusieurs solutions s'offrent à vous. L'une d'elles consiste à placer nos widgets dans un package spécifique pour en faire des variables globales et ainsi ne plus avoir à les déclarer comme paramètre formel de nos méthodes. Vous pourrez ainsi facilement accéder à votre GTK_Label
. Toutefois, vous savez que je n'aime pas abuser des variables globales. D'autant plus que les GTK_Widget
, GTK_Button
ou GTK_Label
sont, vous le savez des pointeurs généralisés : nous multiplions les prises de risque.
Pour résoudre ce dilemne, GtkAda fournit toute une série de package pour nos callbacks. L'un d'entre eux, Gtk.Handlers.User_Callback
permet de passer un paramètre supplémentaire à nos procédures de connexion (il faut donc l'instancier à l'aide de deux types). Ces deux paramètres sont simplement ceux fournis à notre callback : GTK_Widget_Record pour l'émetteur (le premier paramètre de notre callback est un pointeur vers ce type) ; GTK_Label pour le second paramètre. D'où la définition :
1 2 | PACKAGE P_UCallback IS NEW Gtk.Handlers.User_Callback(Gtk_Widget_Record,Gtk_Label) ; USE P_Ucallback ; |
La connexion
La connexion se fait de la même manière que précédemment : en utilisant la procédure connect()
. Toutefois celle-ci admettra un paramètre supplémentaire :
1 2 3 4 5 6 | procedure Connect( Widget : access Widget_Type'Class; Name : Glib.Signal_Name; Marsh : Marshallers.Marshaller; User_Data : User_Type; After : Boolean := False); |
On retrouve les paramètres habituels, sauf User_Data
qui ici correspond au type GTK_Label
employé par votre callback. Voici ce que donne notre code :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | WITH Gtk.Main ; USE Gtk.Main ; WITH Gtk.Window ; USE Gtk.Window ; WITH Gtk.Enums ; USE Gtk.Enums ; WITH Gtk.Button ; USE Gtk.Button ; WITH Gtk.Label ; USE Gtk.Label ; WITH Gtk.Box ; USE Gtk.Box ; WITH Gtk.widget ; USE Gtk.widget ; WITH Gtk.Handlers ; PROCEDURE MaFenetre IS --SOUS PACKAGES POUR MANIPULER LES CALLBACKS PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Widget_Record) ; USE P_Callback ; PACKAGE P_UCallback IS NEW Gtk.Handlers.User_Callback(Gtk_Widget_Record, Gtk_Label) ; USE P_Ucallback ; --CALLBACKS PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) IS PRAGMA Unreferenced (Emetteur ); BEGIN Main_Quit; END Stop_Program ; PROCEDURE Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; Label : GTK_Label) IS PRAGMA Unreferenced(Emetteur ) ; BEGIN IF String(Label.Get_Text)'Length = 0 THEN Label.Set_Text("Hello world !") ; else Label.Set_Text("Ouais, ouais ... salut !") ; END IF ; END Dis_Bonjour; --WIDGETS Win : Gtk_Window ; Btn : Gtk_Button ; Lbl : Gtk_Label ; Box : Gtk_Vbox ; BEGIN Init ; --CREATION DE LA FENETRE Gtk_New(Win,Window_Toplevel) ; Win.Set_Title("Fenetre") ; win.set_default_size(200,150) ; --CREATION DU BOUTON ET DE L'ETIQUETTE Gtk_New_With_Mnemonic(Btn, "Dis _bonjour !") ; Gtk_New(Lbl,"") ; --CREATION DE LA BOITE Gtk_New_Vbox(Box) ; Box.Pack_Start(Lbl) ; Box.Pack_Start(Btn) ; Win.Add(Box) ; --CONNEXION AVEC LES CALLBACKS Connect(Win, "destroy", Stop_Program'ACCESS) ; Connect(Btn, "clicked", Dis_Bonjour'ACCESS, Lbl); Win.Show_all ; Main ; END MaFenetre ; |
Perfectionner encore notre code
Mais ça veut dire que si je voulais ajouter un deuxième bouton qui modifierait un autre widget (une image par exemple), il faudrait créer un nouveau sous package du genre : PACKAGE P_UCallback2 IS NEW Gtk.Handlers.User_Callback(Gtk_Widget_Record, Un_Autre_Type_De_Widget) ;
! Ça va devenir encombrant à la longue tous ces packages, non?
Vous avez raison, nous devons optimiser notre code pour ne pas créer plusieurs instances d'un même package. Et pour cela, nous allons réutiliser les types contrôlés ! Si, comme je vous l'avais conseillé, vous placez tous vos widgets dans un type contrôlé, accompagné d'une méthode Initialize()
, vous n'aurez dès lors plus qu'une seule instanciation à effectuer :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | WITH Gtk.Window ; USE Gtk.Window ; WITH Gtk.Enums ; USE Gtk.Enums ; WITH Gtk.Button ; USE Gtk.Button ; WITH Gtk.Label ; USE Gtk.Label ; WITH Gtk.Box ; USE Gtk.Box ; WITH Gtk.Widget ; USE Gtk.Widget ; WITH Ada.Finalization ; USE Ada.Finalization ; WITH Gtk.Handlers ; PACKAGE P_Fenetre IS TYPE T_Fenetre IS NEW Controlled WITH RECORD Win : Gtk_Window ; Btn : Gtk_Button ; Lbl : Gtk_Label ; Box : Gtk_Vbox ; END RECORD ; PROCEDURE Initialize(F : IN OUT T_Fenetre) ; PACKAGE P_Handlers IS NEW Gtk.Handlers.Callback(Gtk_Widget_Record) ; USE P_Handlers ; PACKAGE P_UHandlers IS NEW Gtk.Handlers.User_Callback(Gtk_Widget_Record,T_Fenetre) ; use P_UHandlers ; END P_Fenetre ; |
P_Fenetre.ads
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | WITH Gtk.Main ; USE Gtk.Main ; WITH P_Callbacks ; USE P_Callbacks ; PACKAGE BODY P_Fenetre IS PROCEDURE Initialize(F : IN OUT T_Fenetre) IS BEGIN Init ; Gtk_New(GTK_Window(F.Win),Window_Toplevel) ; F.Win.Set_Title("Fenetre") ; F.win.set_default_size(200,150) ; Gtk_New_With_Mnemonic(F.Btn, "Dis _bonjour !") ; Gtk_New(F.Lbl,"") ; Gtk_New_Vbox(F.Box) ; F.Box.Pack_Start(F.Lbl) ; F.Box.Pack_Start(F.Btn) ; F.Win.Add(F.Box) ; Connect(F.Win, "destroy", Stop_Program'ACCESS) ; Connect(F.Btn, "clicked", Dis_Bonjour'ACCESS, F) ; F.Win.Show_all ; END Initialize ; END P_Fenetre ; |
P_Fenetre.adb
1 2 3 4 5 6 7 8 9 10 | WITH Gtk.Widget ; USE Gtk.Widget ; with P_Fenetre ; use P_Fenetre ; PACKAGE P_Callbacks IS PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) ; PROCEDURE Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; F : T_Fenetre) ; END P_Callbacks ; |
P_Callbacks.ads
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | WITH Gtk.Main ; USE Gtk.Main ; WITH Gtk.Label ; USE Gtk.Label ; PACKAGE BODY P_Callbacks IS PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) IS PRAGMA Unreferenced (Emetteur ); BEGIN Main_Quit; END Stop_Program ; PROCEDURE Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; F : T_Fenetre) IS PRAGMA Unreferenced(Emetteur ) ; BEGIN IF F.Lbl.Get_Text'Length = 0 THEN F.Lbl.Set_Text("Hello world !") ; else F.Lbl.Set_Text("Ouais, ouais ... salut !") ; END IF ; END Dis_Bonjour; END P_Callbacks ; |
P_Callbacks.adb
1 2 3 4 5 6 7 8 9 10 | WITH Gtk.Main ; USE Gtk.Main ; WITH P_Fenetre ; USE P_Fenetre ; PROCEDURE MaFenetre IS F : T_Fenetre ; pragma Unreferenced(F) ; BEGIN Main ; END MaFenetre ; |
MaFenetre.adb
Autres packages de callback
À quoi servent les autres sous-packages de Gtk.Handlers
?
Le package Gtk.Handlers
comprend 6 sous-packages de callback :
Return_Callback
: ce package permet d'employer des fonctions comme callbacks et pas des procédures. Elle prend en paramètre le type de widget émetteur, bien entendu, ainsi que le type des résultats retournés par les fonctions.User_Return_Callback
: similaire au packageUser_Callback
utilisé dans cette sous-partie, mais pour des fonctions de rappel, il prend en paramètre la classe du widget émetteur, le type des résultats retournés par les fonctions de rappel, ainsi que le typeUser_Type
du paramètre supplémentaire (notre GTK_Label dans l'exemple ci-dessus).User_Return_Callback_With_Setup
: similaire au package précédent, il permet toutefois de fournir une procédure d'initialisation des objets de typeUser_Type
.Callback
: vu dans la sous-partie n°2.User_Callback
: vu dans la sous-partie n°3.User_Callback_With_Setup
: similaire au packageUser_Callback
mais fournissant une procédure d'initialisation des objets de typeUser_Type
.
GDK et les événements
Le clic droit
Les signaux de GTK.Widget
Tu ne nous as pas dit comment gérer les clic droits sur un bouton?
Les signaux dont je vous ai parlé jusque là sont ceux liés aux boutons, on les trouve dans le package Gtk.Button
. Mais aucun signal lié aux GTK_Button
ne prend en compte le clic-droit. Il faut donc chercher un peu plus dans les tréfonds de GTK pour y parvenir. Tout d'abord, nous savons que les boutons ne sont rien d'autre que des widgets, ou pour parler le langage de GTK, ce sont des GTK_Widget
. Grâce aux principes de la POO, nous savons que les GTK_Button
héritent par dérivation des méthodes des GTK_Widget
. Nous n'avons donc qu'à fouiner dans le package GTK.Widget
pour y trouver ce que nous cherchons. Malheureusement, les seuls signaux que nous y trouvons et qui soient liés à la souris sont les suivants :
1 2 | Signal_Button_Press_Event : constant Glib.Signal_Name := "button_press_event"; Signal_Button_Release_Event : constant Glib.Signal_Name := "button_release_event"; |
Le premier est émis lorsqu'un bouton de la souris est enfoncé, le second lorsqu'il est relâché. Mais ça ne nous avance pas sur la question : est-ce un clic gauche ou droit ? Sans compter que certaines souris ont plus de 2 boutons ! C'est pourtant un début. Nous allons donc modifier la connexion entre notre bouton et la procédure Dis_Bonjour
en utilisant ce signal :
1 2 3 | Connect(Btn, "button_press_event", Dis_Bonjour'ACCESS, F); -- OU Connect(Btn, Signal_Button_Press_Event, Dis_Bonjour'ACCESS, F); |
P_Fenetre.adb
User_Return_Callback, nous voilà !
GNAT compile sans sourciller mais lorsque nous lançons notre programme, une jolie exception est levée :
raised SYSTEM.ASSERTIONS.ASSERT_FAILURE : Handlers for "button_press_event" on a GtkButton should be functions
Autrement dit, pour les non anglophones, avec ce signal et ce type de widget nous ne pouvons utiliser comme callback que des fonctions ! Et pour être plus précis, des fonctions renvoyant un booléen. En effet, GTK souhaite savoir s'il doit jouer ou non la petite animation du bouton qui s'enfonce lors du clic sur le GTK_Button
. Cela remet en cause une bonne partie de notre code et notamment le package utilisé. Nous allons devoir abandonné le package Gtk.Handlers.User_Callback
pour le package Gtk.Handlers.User_Return_Callback
:
1 2 | PACKAGE P_UHandlers IS NEW Gtk.Handlers.User_Return_Callback(Gtk_Widget_Record, Boolean, T_Fenetre) ; USE P_UHandlers ; |
P_Fenetre.ads
Le premier paramètre pour instancier notre package est toujours le type du widget émetteur, le second correspond au type du résultat retournée par les callbacks, enfin le dernier type correspond au type du paramètre de la fonction de callback. Il nous faut désormais en venir à notre procédure et la transformer en fonction adéquate :
1 2 | FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; F : T_Fenetre) RETURN Boolean ; |
P_Callback.ads
1 2 3 4 5 6 7 8 9 10 | FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; F : T_Fenetre) RETURN Boolean IS PRAGMA Unreferenced(Emetteur ) ; BEGIN IF F.Lbl.Get_Text'Length = 0 THEN F.Lbl.Set_Text("Hello world !") ; ELSE F.Lbl.Set_Text("Ouais, ouais... salut !") ; END IF ; RETURN True ; END Dis_Bonjour; |
P_Callback.adb
Compilez et lancez votre programme : tout fonctionne de nouveau, à un détail près. Regardez attentivement le bouton «Dis bonjour !» et cliquez dessus. Certes le texte s'affiche convenablement au-dessus, mais le bouton reste immobile ! Comme si ce n'était qu'une simple image. La raison vient de la ligne 20 du fichier P_Callback.adb : nous avons renvoyé TRUE
. Gtk considère que tout s'est bien passé durant notre callback et que celui-ci s'occupe de tout, notamment de l'aspect du bouton. Remplacez cette ligne par «RETURN FALSE ;
» et vous verrez que ce détail s'arrangera.
Distinguer gauche et droite
Si je comprends bien, j'ai tout modifié pour en arriver au même point qu'avant ?
Pas tout à fait. Si vous testez votre programme, vous vous apercevrez que désormais, un clic droit activera le callback Dis_Bonjour() tout aussi bien qu'un clic gauche ou qu'un clic avec un autre bouton de la souris (la molette par exemple). Un clic droit active donc notre callback, il ne nous reste plus qu'à distinguer la gauche et la droite.
Pour ce faire, nous allons continuer notre exploration de GTK et même parcourir les bibliothèques de GDK ! Plus exactement, nous allons avoir besoin du package GDK-Event.ads
que je vous conseille d'ouvrir pour suivre la fin de ce chapitre. À la ligne 229 de ce même package, vous trouverez le type suivant :
1 | type Gdk_Event is new Gdk.C_Proxy; |
Les GDK_Event
sont des événements. Pour simplifier, disons qu'il s'agit de la version bas niveau des signaux. Lorsqu'un GDK_Event
est émis, la boucle Main
l'interprète et émet le signal correspondant. L'idée serait donc d'analyser l'événement qui a émis le Signal_Button_Press_Event
à défaut de l'intercepter nous-même. Nous allons donc ajouter un nouveau paramètre à notre callback : Evenement
de type GDK_Event
.
1 2 3 4 5 6 7 8 9 10 11 12 | WITH Gtk.Widget ; USE Gtk.Widget ; WITH P_Fenetre ; USE P_Fenetre ; WITH GDK.Event ; USE GDK.Event ; PACKAGE P_Callbacks IS PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) ; FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; Evenement : GDK_Event ; F : T_Fenetre) RETURN Boolean ; END P_Callbacks ; |
P_Callbacks.ads
Vous vous demandez comment nous allons pouvoir récupérer cet événement ? Soyez patients, faites comme si ce problème n'en était pas un et fouillons un peu dans nos packages. Nous souhaiterions connaître quel bouton a été cliqué ? Une fonction Get_Button()
existe à la ligne 418 de Gdk-event.ads
. Celle-ci analyse un événement et renvoie le numéro du bouton qui a été cliqué. Venons-en au corps de notre callback :
1 2 3 4 5 6 7 8 9 10 11 12 | FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; Evenement : GDK_Event ; F : T_Fenetre) RETURN Boolean IS PRAGMA Unreferenced(Emetteur ) ; BEGIN CASE Get_Button(Evenement) IS WHEN 1 => F.Lbl.Set_Text("Hello world !") ; WHEN 3 => F.Lbl.Set_Text("Ouais, ouais ... salut !") ; WHEN OTHERS => F.Lbl.Set_Text("") ; END CASE ; RETURN False ; END Dis_Bonjour; |
p_callbacks.adb
Désormais, notre programme affichera "Hello world !"
lors d'un clic gauche et "Ouais, ouais... salut !"
lors d'un clic droit. Un clic avec tout autre bouton, comme la molette, effacera le contenu de l'étiquette.
Connaître l'événement grâce aux Marshallers
Mais ?!? GNAT n'accepte plus la connexion entre mon GTK_Button
et mon callback ? Comment lui dire qu'il y a un paramètre en plus pour Dis_Bonjour
? Et quand est-ce que l'on peut obtenir ce GDK_Event
pour de vrai ?
Pas de panique. On ne va pas chercher à obtenir cet événement puisqu'il a déjà été capturé afin d'émettre le signal Signal_Button_Press_Event
. Lorsque notre callback sera déclenché, il sera trop tard pour obtenir l'événement et si nous cherchons à le capturer, cela empêchera l'exécution du callback. La seule solution est de faire appel aux fonctions To_Marshaller()
. Elles vont nous permettre de transformer un simple pointeur sur fonction en un Marshaller
, type qui permet d'embarquer davantage d'informations. Vous n'y comprenez rien, alors voyez l'exemple suivant :
1 | Connect(F.Btn, "button_press_event", to_Marshaller(Dis_Bonjour'ACCESS), F) ; |
P_Fenetre.adb
Vous remarquerez que je n'ai ajouté aucun paramètre pour le GDK_Event. J'ai simplement converti mon pointeur sur Dis_Bonjour en Marshaller. Testez ce code et dites m'en des nouvelles.
Le double clic
Le GDK_Event_Type
Et pour un double clic, on fait comment ?
Bonne question. Jetez un œil aux toutes premières lignes du package GDK-Event.ads
. Vous allez y trouver un type très intéressant, Gdk_Event_Type
, dont voici la liste des valeurs :
1 2 3 4 5 6 7 8 | type Gdk_Event_Type is (Nothing, Delete, Destroy, Expose, Motion_ Notify, Button_Press, Gdk_2button_Press, Gdk_3button_Press, Button_Release, Key_Press, Key_Release, Enter_Notify, Leave_Notify, Focus_Change, Configure, Map, Unmap, Property_Notify, Selection_Clear, Selection_Request, Selection_Notify, Proximity_In, Proximity_Out, Drag_Enter, Drag_Leave, Drag_Motion, Drag_Status, Drop_Start, Drop_Finished, Client_Event, Visibility_Notify, No_Expose, Scroll, Window_State, Setting, Owner_Change, Grab_Broken ); |
Il s'agit des différents types d'événements de bases qu'un GDK_Event
peut prendre. Nous n'allons pas tous les voir mais si vous êtes attentif vous y trouverez button_Press
, l'événement pour un simple clic de souris, Gdk_2button_Press
, l'événement pour un double clic de souris, Gdk_3button_Press
, pour un triple clic, Button_Release, pour un bouton de souris relâché, Key_Press, pour une touche de clavier enfoncée ou Key_Release, pour une touche de clavier relâchée. Nous verrons les deux dernières juste après ; concentrons-nous sur Gdk_2button_Press
et Gdk_3button_Press
.
Nous souhaiterions que notre callback affiche une phrase en espagnol lors d'un double clic. Nous devons donc distinguer les clics simples des clics doubles (le principe sera le même pour les triples clics) et pour cela connaître le Gdk_Event_Type
de notre paramètre Evenement
. Nous utiliserons pour cela une méthode de Gdk.Event :
1 | function Get_Event_Type (Event : Gdk_Event) return Gdk_Event_Type; |
Voici ce que pourrait donner notre callback, revu et corrigé «para hablar Español» :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; Evenement : GDK_Event ; F : T_Fenetre) RETURN Boolean IS PRAGMA Unreferenced(Emetteur ) ; Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ; BEGIN IF Type_Evenement = Button_Press THEN CASE Get_Button(Evenement) IS WHEN 1 => F.Lbl.Set_Text("Hello world !") ; WHEN 3 => F.Lbl.Set_Text("Ouais, ouais... salut !") ; WHEN OTHERS => F.Lbl.Set_Text("") ; END CASE ; ELSIF Type_Evenement = Gdk_2button_Press THEN F.Lbl.Set_Text("Ola ! Como esta ?") ; --Désolé pour les accents manquants. END IF ; RETURN False ; END Dis_Bonjour; |
P_Callbacks.adb
Creusons un peu
Vous savez maintenant gérer les double-clics. Alors, heureux ? Je tiens toutefois à vous montrer quelque-chose. Complétons le code précédent :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; Evenement : GDK_Event ; F : T_Fenetre) RETURN Boolean IS PRAGMA Unreferenced(Emetteur ) ; Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ; BEGIN IF Type_Evenement = Button_Press THEN put_line("clic") ; CASE Get_Button(Evenement) IS WHEN 1 => F.Lbl.Set_Text("Hello world !") ; WHEN 3 => F.Lbl.Set_Text("Ouais, ouais... salut !") ; WHEN OTHERS => F.Lbl.Set_Text("") ; END CASE ; ELSIF Type_Evenement = Gdk_2button_Press THEN Put_Line("double clic") ; F.Lbl.Set_Text("Ola ! Como esta ?") ; END IF ; RETURN False ; END Dis_Bonjour; |
P_Callbacks.adb
Compilez, exécutez votre programme et faites un double-clic sur le GTK_Button
. Regardez maintenant la console. Elle affiche :
1 2 3 | clic clic double clic |
Autrement dit, le callback a été appelé 3 fois ! Soit une fois pour chaque clic puis une troisième fois pour le double clic. Ce n'est pas bien grave me direz-vous, mais cela doit vous amenez à prendre quelques précautions : si vous souhaitez qu'un double-clic réalise une action, assurez vous qu'un simple clic ne l'en empêche pas.
Et si on fouinait ?
Tant que nous avons le nez dans le package GDK.Event
, profitons-en pour regarder quelques autres fonctions entourant Get_Event_Type()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function Get_Time (Event : Gdk_Event) return Guint32; -- Renvoie l'heure à laquelle a eu lieu l'événement function Get_X (Event : Gdk_Event) return Gdouble; function Get_Y (Event : Gdk_Event) return Gdouble; -- Renvoient les coordonnées de la souris au moment de l'événement. -- Les coordonnées sont calculées par rapport à la fenêtre mère function Get_X_Root (Event : Gdk_Event) return Gdouble; function Get_Y_Root (Event : Gdk_Event) return Gdouble; -- Comme précédemment, sauf que les coordonnées sont calculées par rapport -- à la fenêtre dont est issu l'événement function Get_Count (Event : Gdk_Event) return Gint; -- Renvoie le nombre d'événements en attente. |
Le clavier
Reconnaître le clavier
Et maintenant : le clavier, le clavier, le clavier !!!
Vous êtes bien impatients ! Mais il est grand temps de nous en occuper. Nous allons faire appel aux GDK_Event_Type
vus précédemment : Key_press et Key_release. Par exemple, si l'utilisateur clique sur une touche du clavier, le programme affichera un message en allemand :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; Evenement : GDK_Event ; F : T_Fenetre) RETURN Boolean IS PRAGMA Unreferenced(Emetteur ) ; Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ; BEGIN IF Type_Evenement = Button_Press THEN CASE Get_Button(Evenement) IS WHEN 1 => F.Lbl.Set_Text("Hello world !") ; WHEN 3 => F.Lbl.Set_Text("Ouais, ouais ... salut !") ; WHEN OTHERS => F.Lbl.Set_Text("") ; END CASE ; ELSIF Type_Evenement = Gdk_2button_Press THEN F.Lbl.Set_Text("Ola ! Como esta ?") ; ELSIF Type_Evenement = Key_Press THEN F.Lbl.Set_Text("Guten tag lieber Freund.") ; END IF ; RETURN False ; END Dis_Bonjour; |
P_Callbacks.adb
Bien sûr, il est important que le GTK_Button soit sélectionné pour que cela arrive, qu'il ait le focus. Cela se traduit par un cadre en pointillé autour du texte du bouton. Notre programme ne contient qu'un seul bouton, donc le problème ne se pose pas mais pensez-y à l'avenir.
Reconnaître la lettre
Cherchons maintenant à connaître la touche utilisée. Le message sera en allemand seulement si l'utilisateur appuie sur une lettre minuscule. Nous allons utiliser la fonction get_string() qui renvoie le caractère obtenu par la touche du clavier mais sous forme de chaîne de caractère :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; Evenement : GDK_Event ; F : T_Fenetre) RETURN Boolean IS PRAGMA Unreferenced(Emetteur ) ; Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ; BEGIN IF Type_Evenement = Button_Press THEN CASE Get_Button(Evenement) IS WHEN 1 => F.Lbl.Set_Text("Hello world !") ; WHEN 3 => F.Lbl.Set_Text("Ouais, ouais ... salut !") ; WHEN OTHERS => F.Lbl.Set_Text("") ; END CASE ; ELSIF Type_Evenement = Gdk_2button_Press THEN F.Lbl.Set_Text("Ola ! Como esta ?") ; ELSIF Type_Evenement = Key_Press THEN IF Get_String(Evenement)(1) in 'a'..'z' THEN F.Lbl.Set_Text("Guten tag lieber Freund.") ; ELSE F.Lbl.Set_Text("") ; END IF ; END IF ; RETURN False ; END Dis_Bonjour; |
P_Callbacks.adb
Reconnaître la touche
Cela fonctionne mais un souci se pose : tous les caractères ne sont pas imprimables ! Si l'utilisateur appuie sur Tabulation ou la touche ←, le programme plante. Mieux vaudrait connaître le code de la touche activée que le caractère correspondant. Cela peut se faire avec la fonction Get_Key_Val()
qui renvoie une valeur de type Gdk_Key_Type
. Ce type est défini dans le package Gdk.Types
, mais la plupart des constantes nécessaires se trouvent dans le package Gdk.Types.keysyms
que je vous conseille d'ouvrir à son tour. Vous y trouverez de nombreuses constantes. Par exemple, GDK_Tab
(Tabulation), GDK_Return
(Entrée), GDK_Escape
(Echap), GDK_Delete
(Supprimer), GDK_Left
, GDK_Right
, GDK_Down
, GDK_Up
(←, →, ↓ et ↑), GDK_Shift_L
ou GDK_Shift_R
(Majuscule gauche et Majuscule droite).
Les touches 'A'
à 'Z'
se nomment GDK_A
à GDK_Z
. Leurs valeurs, puisque ce ne sont que des nombres, sont comprises entre 16#0041#
et 16#005a#
(valeurs hexadécimales, vous l'aurez reconnu). Les minuscules, quant à elles se nomment GDK_LC_a
, GDK_LC_b
… (LC signifiant «lower case»). Leurs valeurs hexadécimales sont comprises entre 16#0061#
et 16#007a#
.
À nous de jouer maintenant ! Si l'utilisateur appuie sur une lettre minuscule, le programme lui parlera allemand, s'il appuie sur la tabulation, il lui parlera hollandais et sinon il lui parlera danois :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ; Evenement : GDK_Event ; F : T_Fenetre) RETURN Boolean IS PRAGMA Unreferenced(Emetteur ) ; Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ; BEGIN IF Type_Evenement = Button_Press THEN CASE Get_Button(Evenement) IS WHEN 1 => F.Lbl.Set_Text("Hello world !") ; WHEN 3 => F.Lbl.Set_Text("Ouais, ouais ... salut !") ; WHEN OTHERS => F.Lbl.Set_Text("") ; END CASE ; ELSIF Type_Evenement = Gdk_2button_Press THEN F.Lbl.Set_Text("Ola ! Como esta ?") ; ELSIF Type_Evenement = Key_Press THEN IF Get_Key_Val(Evenement) in 16#0061#..16#007a# THEN F.Lbl.Set_Text("Guten tag lieber Freund.") ; ELSIF Get_Key_Val(Evenement) = GDK_Tab THEN F.Lbl.Set_Text("Goede dag lieve vriend.") ; ELSE F.Lbl.Set_Text("God dag kaere ven.") ; END IF ; END IF ; RETURN False ; END Dis_Bonjour; |
P_Callbacks.adb
Bien sûr, pour que ce code fonctionne vous devrez penser à ajouter les lignes suivantes :
1 2 | WITH Gdk.Types ; USE Gdk.Types; WITH Gdk.Types.Keysyms ; USE Gdk.Types.Keysyms ; |
En résumé :
- Tout évènement sur un widget, aussi anodin soit-il, déclenche l'émission d'un signal. C'est la capture de ce signal qui va permettre le déclenchement d'une procédure de rappel ou callback.
- Tout callback doit être connecté à un widget et un signal. Cela se fait grâce aux procédures
connect()
disponibles dans les sous-packages deGtk.Handlers
. - Pour obtenir des interactions entre les différents widgets, vous pouvez utiliser le package
Gtk.Handlers.User_Callback
. - Si vous ne trouvez pas votre bonheur dans les signaux proposés, cherchez parmi les signaux des widgets parents ou utilisez les
GDK_Event
. - Faites appel aux types contrôlés et à vos connaissances en POO pour simplifier votre développement.