Activités de ce TD :
- comprendre les principaux éléments d'une interface graphique.
- apprendre à utiliser
tkinter
pour créer des interfaces graphiques en Python. - enrichir l'interface graphique de
PistusResa
avec une fenêtre de connexion.
Introduction
Une interface graphique (GUI) permet à tout utilisateur de PistusResa
d'accéder à toutes les fonctionnalités de l'application de manière simple et intuitive.
Plusieurs bibliothèques, connues sous le nom de boîte à outils de composants d'interface graphique (en anglais, widget toolkits), existent pour programmer une interface graphique en Python. Pour ce tutoriel, nous avons choisi Tkinter, qui est construit sur la bibliothèque Tcl/Tk, pour deux raisons :
- Il s'agit de la bibliothèque standard d'interface graphique intégrée à Python.
- Contrairement à d'autres bibliothèques, elle est facile à apprendre.
Par contre, les interfaces programmées avec Tkinter ont l'air un peu rudimentaires. Cependant, si nous avons besoin de développer rapidement des interfaces simples, Tkinter est un excellent choix.
La figure suivante montre la fenêtre de connexion de PistusResa
. Les principaux éléments d'une interface graphique sont les suivants :
- Fenêtre, c'est-à-dire un conteneur qui inclut tous les autres éléments de l'interface graphique, généralement appelés widgets (en français, composants d'interface graphique).
- Barre de titre : où apparaît le titre de la fenêtre.
- Champs de texte : où les utilisateurs peuvent saisir du texte.
- Étiquettes : utilisées pour expliquer la fonction des autres widgets.
- Boutons : utilisés par les utilisateurs pour déclencher une réaction.
Ces widgets ne sont généralement pas ajoutés directement à la fenêtre ; ils sont plutôt regroupés dans des "frames". Dans la figure, il y a trois frames (invisibles dans la figure) :
- le premier contient les étiquettes et les champs de texte Username et Password ;
- le deuxième contient l'étiquette du message (Enter the username (at least 5 characters)) ;
- le troisième contient les boutons.
L'avantage d'utiliser des frames est que l'on peut disposer les widgets selon différents critères, comme nous le verrons plus loin.
Une grosse partie de l'interface graphique de PistusResa
est déjà implémentée. Votre objectif aujourd'hui est d'ajouter une fenêtre de connexion, comme celle montrée dans la figure.
Se familiariser avec l'interface graphique
Dans la première partie de ce TD, vous apprendrez à utiliser tkinter
.
⏰ Temps à consacrer à cette partie : au maximum 1 heure.
👉 Continuez à lire sur cette page.
La fenêtre de connexion
Vous êtes maintenant prêt à créer la fenêtre de connexion.
Il est important de comprendre comment l'application PistusResa
est exécutée en premier lieu.
Le point d'entrée de l'application est le fichier pistus.py
.
Essayez d'exécuter le fichier pistus.py
dans Visual Studio Code.
Si vous obtenez des erreurs lorsque vous exécutez le fichier à l'aide du bouton d'exécution de Visual Studio Code, vous devez ouvrir un terminal et taper : py -m pistus
(Windows) ou python3 -m pistus
(macOS). Cela garantira que toutes les importations dans tous les fichiers fonctionneront.
👉👉ATTENTION : Si vous obtenez une erreur AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS
, ouvrez le fichier gui_config.py
et changez Image.ANTIALIAS avec Image.LANCZOS à la line 18.👈👈
Lorsque vous exécutez pistus.py
, les instructions suivantes sont exécutées (lisez le code pour identifier ces actions) :
- Charger la configuration de l'application du fichier
./config/config
. - Charger le paquet de messages dans la langue spécifiée dans la configuration. Le paquet de messages contient le texte associé aux widgets.
- Se connecter à la base de données, dont le chemin d'accès est spécifié dans le fichier de configuration.
- Si l'autorisation est activée dans le fichier de configuration,
PistusResa
appelle la fonctionopen_login_window()
définie dans le fichier./gui/login.py
. - Si l'autorisation n'est pas activée, la fenêtre principale de l'application s'affiche.
- Chaque fois que l'utilisateur ferme la fenêtre principale de l'application,
PistusResa
ferme la connexion à la base de données et arrête son exécution.
👉 Si vous regardez le fichier de configuration, l'autorisation est activée. Cependant, si vous exécutez le fichier pistus.py
maintenant, aucune fenêtre n'apparaîtra car la fenêtre de connexion n'est pas encore implémentée.
L'interface graphique de la fenêtre de connexion
Vous allez maintenant créer les widgets de la fenêtre de connexion.
Ouvrez le fichier ./gui/login.py
et allez à la fonction open_login_window()
.
Cette fonction est appelée lorsque PistusResa
est exécuté et que l'authentification est activée. La fonction crée l'interface graphique de la fenêtre. En particulier, elle appelle les trois fonctions suivantes :
_credentials_frm_widgets()
. Crée le frame contenant les champs de texte pour saisir le nom d'utilisateur et le mot de passe._message_frm_widgets()
. Crée le frame contenant une étiquette utilisée pour afficher des messages à l'utilisateur (par exemple, "mot de passe incorrect")._buttons_frm_widgets()
. Crée le frame contenant les boutons.
👉 Votre tâche consiste à mettre en œuvre ces trois fonctions. Pour chaque fonction :
- Lisez les commentaires dans le code pour comprendre ce qu'il faut faire.
- Les variables pour les champs de texte, les étiquettes et les boutons sont déjà définies au début de la fonction. Utilisez ces variables et ne changez pas leur nom.
- Ecrivez le code à l'endroit indiqué (après la phrase "TODO").
Complétez l'implémentation de la fonction _credentials_frm_widgets()
.
👉 À la fin de la fonction, les champs de texte que vous créez sont ajoutés à une variable globale text_fields
, un dictionnaire. Ne l'oubliez pas, car vous devrez utiliser cette variable globale plus tard lorsque vous ajouterez des fonctions de rappel (en anglais, callbacks) à vos widgets.
ANSWER ELEMENTS
ttk.Label(credentials_frm, text = username_lbl_text)\ .grid(row=0, column=0, padx=10, pady=10, sticky = "w") # Adds the text field "username" username_tf_content = tk.StringVar(value="") username_tf = ttk.Entry(credentials_frm, textvariable=username_tf_content) username_tf.grid(row = 0, column = 1, sticky='ew') # Adds the label "password". ttk.Label(credentials_frm, text = password_lbl_text)\ .grid(row = 1, padx=10,pady=10, column = 0, sticky = "W") # Adds the text field "password". password_tf_content = tk.StringVar(value="") password_tf = ttk.Entry(credentials_frm, show='*', \ textvariable=password_tf_content) password_tf.grid(row = 1, column = 1, sticky='ew') # The text fields will span the entire column. credentials_frm.columnconfigure(1, weight=1)
Complétez l'implémentation de la fonction _message_frm_widgets()
.
👉 À la fin de la fonction, les étiquettes que vous avez créées sont ajoutées à une variable globale control_labels
, un dictionnaire. Ne l'oubliez pas, car vous devrez utiliser cette variable globale plus tard lorsque vous ajouterez des fonctions de rappel à vos widgets.
ANSWER ELEMENTS
message_lbl = ttk.Label(message_frm, style="Check.TLabel") message_lbl.grid(row=0, column=0, sticky='ew')
Complétez l'implémentation de la fonction _buttons_frm_widgets()
.
👉 À la fin de la fonction, les boutons que vous créez sont ajoutés à une variable globale buttons
, un dictionnaire. Ne l'oubliez pas, car vous devrez utiliser cette variable globale plus tard lorsque vous ajouterez des fonctions de rappel à vos widgets.
Gestion des événements
L'automate suivant montre comment la fenêtre de connexion change en fonction des actions de l'utilisateur sur ses widgets.
Avez-vous besoin d'une description textuelle de l'automate ? Cliquez ici
- Initialement, la fenêtre est dans l'état INIT. Le champ de texte
password
et le boutonlogin
sont désactivés ; l'étiquette de contrôle affiche le messagemessages_bundle["enter_username"]
; - Pendant que l'utilisateur tape son nom d'utilisateur, celui-ci est vérifié pour s'assurer qu'il répond à certains critères de validité (
username_ok
) ; si c'est le cas (le mot de passe n'est pas OK parce que l'utilisateur n'a encore rien tapé), la fenêtre passe à l'état USERNAME_ENTERED ; - Dans l'état USERNAME_ENTERED, seul le bouton
login
est désactivé ; l'étiquette de contrôle affiche le messagemessages_bundle["enter_password"]
; - Si l'utilisateur modifie le nom d'utilisateur et que celui-ci ne satisfait pas à la contrainte de validité, la fenêtre repasse à l'état INIT ;
- Si l'utilisateur tape un mot de passe qui répond à certains critères de validité, la fenêtre passe à l'état CREDENTIALS_ENTERED ; le message
messages_bundle["login_authorized"]
est affiché dans l'étiquette de contrôle ; - Dans l'état CREDENTIALS_ENTERED, tous les champs sont activés, y compris le bouton
login
; - Si l'utilisateur modifie le mot de passe et que le mot de passe ne répond pas aux critères de validité, la fenêtre revient à l'état USERNAME_ENTERED ;
- Si l'utilisateur modifie le nom d'utilisateur et que celui-ci ne satisfait pas aux critères de validité, la fenêtre revient à l'état INIT ; le champ de texte du mot de passe est désactivé, mais sa valeur actuelle est conservée ;
- Si l'utilisateur modifie le nom d'utilisateur et que le nom d'utilisateur et le mot de passe répondent tous deux aux critères de validité, la fenêtre passe directement à l'état CREDENTIALS_ENTERED ;
- Si l'utilisateur clique sur le bouton
login
, et que le nom d'utilisateur et le mot de passe ne sont pas corrects, la fenêtre reste dans l'état CREDENTIALS_ENTERED et le messagemessages_bundle["username_not_found"]
oumessages_bundle["incorrect_password"]
est affiché dans l'étiquette de contrôle ; - Si l'utilisateur clique sur le bouton
login
, et que le nom d'utilisateur et le mot de passe sont corrects, la fenêtre de connexion est détruite et la fenêtre principale dePistusResa
est ouverte ;
Quel que soit l'état, si l'utilisateur clique sur le bouton clear
, tous les champs sont effacés et la fenêtre revient à l'état INIT ;
si l'utilisateur clique sur le bouton cancel
, la fenêtre de connexion est détruite.
L'animation suivante peut vous aider à comprendre le comportement de la fenêtre de connexion en fonction des actions de l'utilisateur.
Implémentation de l'automate
Vous allez maintenant implémenter les fonctions nécessaires pour réaliser le comportement décrit dans l'automate discuté ci-dessus.
👉 Vous trouverez les fonctions à implémenter dans le fichier login.py
.
Suivez les instructions dans les commentaires pour implémenter les fonctions suivantes :
init_state()
.username_entered_state()
.credentials_entered_state()
.username_updated()
.password_updated()
.
👉 Rappelez-vous que les champs de texte, les étiquettes de contrôle et les boutons sont stockés dans les variables globales text_fields
, control_labels
et buttons
respectivement.
👉 N'oubliez pas d'ajouter les instructions nécessaires à la fonction _credentials_frm_widgets()
pour associer les fonctions username_updated
et password_updated
en tant que fonctions de rappel des champs de texte username
et password
.
👉 Exécutez pistus.py
et vérifiez que la fenêtre change correctement d'état lorsque vous tapez le nom d'utilisateur et le mot de passe.
Warning. Même si le nom d'utilisateur et le mot de passe correspondent à tous les critères de validité, vous ne pouvez pas vous connecter à l'application. Vous devrez attendre la section suivante.
ANSWER ELEMENTS
Fonction init_state
:
text_fields["username"][0].configure(state=["!disabled"]) text_fields["password"][0].configure(state=["disabled"]) buttons["login"].configure(state=["disabled"]) control_labels["message_ctrl"].\ configure(text=messages_bundle["enter_username"])
Fonction username_entered_state
:
text_fields["username"][0].configure(state=["!disabled"]) text_fields["password"][0].configure(state=["!disabled"]) buttons["login"].configure(state=["disabled"]) control_labels["message_ctrl"].\ configure(text=messages_bundle["enter_password"])
Fonction credentials_entered_state
:
text_fields["username"][0].configure(state=["!disabled"]) text_fields["password"][0].configure(state=["!disabled"]) buttons["login"].configure(state=["!disabled"]) control_labels["message_ctrl"].configure(text=message)
Fonction username_updated
:
if utils.username_ok(get_username()): if not utils.password_ok(get_password()): username_entered_state() else: credentials_entered_state(messages_bundle["login_authorized"]) else: init_state()
Fonction password_updated
:
if utils.password_ok(get_password()): if not utils.username_ok(get_username()): init_state() else: credentials_entered_state(messages_bundle["login_authorized"]) else: if not utils.username_ok(get_username()): init_state() else: username_entered_state()
Dans la fonction _credentials_frm_widgets
, nous devons ajouter les deux instructions suivantes afin d'associer les deux fonctions de rappel username_updated
et password_updated
aux champs de texte username
et password
.
username_tf_content.trace("w", username_updated) password_tf_content.trace("w", password_updated)
Connexion
Vous allez maintenant mettre en œuvre trois fonctions qui vous permettront de vous connecter à PistusResa
.
👉 Le nom d'utilisateur est admin
et le mot de passe est Adm1n!
- Lisez les commentaires dans le fichier
login.py
pour implémenter les fonctionslogin()
,clear()
etcancel()
. - Ajoutez les instructions nécessaires à la fonction
_buttons_frm_widgets
pour associerlogin()
,clear()
etcancel()
comme fonctions de rappel des boutonslogin
,clear
etcancel
respectivement. - Exécutez
pistus.py
pour vérifier que vous pouvez vous connecter et ouvrir la fenêtre principale dePistusResa
.
ANSWER ELEMENTS
Fonction login
:
if res[0]: window.destroy() open_main_window(cursor, conn, messages_bundle, lang) elif res[1] == auth.INCORRECT_PASSWORD: credentials_entered_state(messages_bundle["incorrect_password"]) elif res[1] == auth.USERNAME_NOT_FOUND: credentials_entered_state(messages_bundle["username_not_found"])
Function clear
:
text_fields["username"][1].set("") text_fields["password"][1].set("")
Fonction cancel
:
clear() window.destroy()
Dans la fonction _buttons_frm_widgets
, nous devons ajouter les arguments command=login
, command=clear
et command=cancel
lorsque nous créons les boutons login, clear et cancel respectivement.
La fenêtre principale
Si vous vous connectez à PistusResa
, vous devriez voir la fenêtre principale de l'application.
Cette fenêtre comporte un menu sur la gauche avec trois options qui permettent aux utilisateurs d'ouvrir des fenêtres pour
ajouter et modifier les données et les inscriptions des élèves.
Jouez avec l'interface et vérifiez que les fonctions que vous avez codées dans le module Student
fonctionnent correctement.
Que faire ensuite ? (Facultatif)
Si vous avez encore du temps, vous pouvez compléter l'application et implémenter le module d'authentification et le module deadline.