Programmation évènementielle
Maintenant que l'on sait construire l'interface graphique, c'est-à-dire placer les composants dans la fenêtre, on va voir comment définir l'interaction avec l'utilisateur. Lors de cette interaction, de nombreux évènements seront générés tels que des clics, des déplacements de souris, des pressions sur des touches du clavier, des activations de timer, des changements de valeurs de l'accéléromètre, etc. À ces différents évènements, il est possible d'associer des gestionnaires, c'est-à-dire une portion de code qui définit quoi faire en réaction à leur occurrence.
Boucle de gestion d'évènements
Un programme doté d'une interface graphique, une fois démarré, ne sera normalement quitté que suite à une action de l'utilisateur (il clique par exemple sur un bouton « quitter »). La figure 9 illustre les étapes de la vie d'un tel programme :
- Le programme commence par une phase d'initialisation qui va notamment récupérer des informations sur les composants hardwares tels que l'écran, ou les divers senseurs. Cette phase initialise également des structures de données qui seront nécessaires au bon fonctionnement et à la gestion de l'interface graphique.
- Le programme démarre ensuite une boucle de gestion d'évènements. Dans cette boucle, potentiellement infinie, le programme reçoit des notifications de tous les évènements qui le concernent et il les traite. Pour les évènements auxquels sont associés des gestionnaires, il exécute ces derniers. La gestion des évènements se fait de manière séquentielle, ils sont traités les uns à la suite des autres.
- Lorsqu'un des évènements reçus demande au programme de se quitter, il rentre dans une phase de terminaison durant laquelle il va libérer toutes les ressources allouées avant de se terminer.
Cette boucle de gestion d'évènements est précisément démarrée par la méthode run
, appelée sur l'instance de la classe de type App
représentant l'application avec interface graphique nouvellement définie. Elle s'exécute jusqu'à ce que le programme soit quitté, avec l'appel sys.exit(0)
qui serait exécuté dans un gestionnaire, par exemple.
Gestionnaire d'évènements
Lorsqu'un évènement se produit, et si aucun gestionnaire d'évènements ne lui a été associé dans le programme, il sera juste ignoré. Pour associer une action à exécuter lors de l'occurrence d'un évènement, il va donc falloir en définir. Trois éléments vont intervenir pour cela :
- la source de l'évènement est le composant sur lequel l'interaction a eu lieu ou celui qui l'a causé ;
- l'évènement à proprement parler est d'un certain type selon ce qui s'est produit ;
- et le gestionnaire d'évènements est la méthode à exécuter pour chaque occurrence de l'évènement.
En fonction du type d'évènement, certaines informations complémentaires peuvent être disponibles. Voici quelques exemples d'évènements :
- un bouton peut avoir être enfoncé suite à un clic dessus ;
- un clic gauche ou droit peut avoir été fait sur une zone de dessin, aux coordonnées $(x, y)$ ;
- un élément d'une liste déroulante peut avoir été sélectionné ;
- un élément à l'intérieur d'un composant peut avoir été glissé-déposé (drag and drop) d'une position de départ $(x_0, y_0)$ à une position d'arrivée $(x_1, y_1)$ ;
- une touche du clavier peut avoir été enfoncée ou relâchée ;
- la souris peut être rentrée dans ou avoir quitté une zone donnée.
Un gestionnaire d'évènements est donc une méthode que l'on va attacher à un évènement sur une source donnée. L'exemple suivant permet de quitter le programme lorsqu'on clique sur un bouton :
La première chose que l'on peut voir, c'est l'utilisation d'un conteneur de type AnchorLayout
. Ce dernier permet de positionner le composant qu'il contient à une position prédéterminée comme au centre, au nord... Dans cet exemple, on l'a centré selon les deux axes $x$ et $y$. On crée ensuite le bouton, comme d'habitude, et on l'ajoute dans le conteneur qui est renvoyé par la méthode build
.
La nouveauté, par rapport aux précédents exemples, c'est l'ajout d'une méthode dont on a préfixé le nom avec _
pour signaler qu'elle est à usage privé dans la classe. Il s'agit en fait d'un gestionnaire d'évènements que l'on va associer au clic gauche sur le bouton en appelant dessus la méthode bind
et en la passant au paramètre nommé on_press
:
Pendant l'exécution du programme, lorsque l'utilisateur clique sur le bouton, un évènement de type press
avec ce dernier en source est enregistré. Une fois que c'est à son tour d'être traité par la boucle d'évènements, puisqu'un gestionnaire lui a été associé, le programme l'exécute et, dans ce cas-ci, le programme est donc quitté.
Gestionnaire d'évènement dans le fichier KV
On peut évidemment associer des gestionnaires d'évènements à des composants directement depuis le fichier KV. Pour ce faire, on va devoir importer l'application dans le fichier KV, afin d'avoir accès aux méthodes représentant les gestionnaires d'évènements.
Néanmoins, une différence notable avec l'utilisation de la méthode bind
est qu'on ne doit pas spécifier la méthode à appeler, mais bien faire l'appel que l'on désire. Une première conséquence est qu'il faut soi-même fournir le paramètre qui identifie la source de l'évènement.
On commence donc par complètement supprimer la méthode build
de la classe QuitApp
ainsi que tous les imports qui y sont liés. Pour le reste, on ne doit toucher à rien. La nouvelle version de la classe est donc :
Vient ensuite le fichier quit.kv
, dont le composant principal est donc un conteneur de type AnchorLayout
:
On commence par importer la classe App
, ce qui se fait comme en Python mais préfixé des caractères \#:
. On récupère ensuite l'instance de la classe QuitApp
qui représente le programme qui est exécuté avec l'appel App.get_running_app()
. Sur cette instance, on appelle la méthode quit
en lui passant en paramètre self
qui représente le bouton qui a produit l'évènement. On a donc bien dû appeler la méthode manuellement.
Composant personnalisé
Il est parfois utile de vouloir définir un nouveau composant personnalisé, qui est construit à partir d'autres existants. Il suffit, pour cela, de définir une nouvelle classe dont le type est, par exemple, BoxLayout
. Pour illustrer cela, construisons un programme qui calcule automatiquement l'âge de l'utilisateur à partir de son année de naissance, qu'il doit fournir.
La figure 11 montre le look de l'interface graphique du calculateur d'âge. Une zone de texte permet à l'utilisateur d'entrer son année de naissance et, une fois qu'il l'aura validée avec la touche ENTER, l'âge calculé sera affiché dans le label inférieur.
Pour réaliser cette interface graphique, on va définir un nouveau composant appelé AgeCalculatorForm
et, comme d'habitude, la classe de type App
qui décrit le programme :
Il s'agit donc simplement d'une classe de type BoxLayout
, dont le corps est vide. Tel quel, un composant de type AgeCalculatorForm
est donc complètement équivalent à un composant BoxLayout
. Associé à cela, un fichier agecalculator.kv
décrit les composants de l'interface :
Comme on peut le voir, le composant principal, c'est-à-dire celui qui est ajouté à la fenêtre, est le composant AgeCalculatorForm
. La zone de texte est limitée à une ligne grâce à l'option multiline
fixée à False
. Comme on va le voir à la section suivante, l'avantage de créer son propre composant est qu'on va pouvoir récupérer facilement les informations entrés par l'utilisateur dans la zone de texte.
État des widgets
Lorsqu'on a une interface graphique avec des widgets qui demande de l'information, il est évidemment utile de savoir la récupérer. De même, lorsqu'un widget présente de l'information à l'utilisateur, il est utile de pouvoir la mettre à jour. Pour ce faire, on peut procéder de différentes manières, selon qu'on utilise ou non un fichier KV. Comme la bonne pratique consiste à en utiliser un, on se limitera à la façon de faire utilisant ce fichier.
La première chose à faire consiste à définir des propriétés dans le nouveau composant qu'on a créé, afin de pouvoir faire le lien avec les composants qui sont définis dans le fichier KV. Comme le lien à faire est vers des composants, on doit utiliser une propriété de type objet, à savoir une instance de la classe ObjectProperty
. On ajoute donc deux variables d'instance à la classe AgeCalculatorForm
:
La première variable va servir à faire le lien avec la zone de texte et la seconde avec le label où sera affiché l'âge calculé. Les liens sont établis dans le fichier agecalculator.kv
qui devient comme suit :
La première chose à faire est d'associer un identifiant à l'aide de l'attribut id
aux composants que l'on souhaite lier. On ajoute ensuite les deux propriétés qui sont définies dans la classe AgeCalculatorForm
dans le fichier KV en leur donnant comme valeurs les identifiants choisis.
La dernière étape consiste à récupérer l'année de naissance, calculer l'âge et l'afficher dans le label. On ajoute pour cela une méthode _compute
dans la classe AgeCalculatorForm
:
Il ne reste plus qu'à appeler cette méthode lorsque l'utilisateur enfonce la touche ENTER dans la zone de texte. On modifie pour cela le fichier KV en ajoutant la ligne suivante dans le bloc du TextInput
:
L'évènement auquel on associe un gestionnaire est donc on_text_validate
. De plus, on peut directement utiliser la variable root
qui représente le composant de type AgeCalculatorForm
qui est défini, sans devoir passer par App.get_running_app()
comme on a dû le faire précédemment.