Création d’une application graphique avancée
Après avoir maîtrisé les fondamentaux de la création d’interfaces graphiques avec PySimpleGUI, il est temps d’approfondir notre compréhension et d’explorer des concepts plus avancés. Cette section se concentrera sur l’élaboration de fenêtres plus sophistiquées et la mise en œuvre d’interactions plus complexes avec l’utilisateur. Vous découvrirez comment créer des interfaces utilisateur riches, capables de capturer et de répondre à une variété d’actions de l’utilisateur. Un autre aspect clé que nous aborderons est l’intégration de ces interfaces graphiques avancées avec les objets de votre application. Vous apprendrez comment faire en sorte que votre interface graphique et votre code interagissent de manière fluide et cohérente, créant une expérience utilisateur intégrée.
Organisation de l’interface graphique
L’un des principaux avantages de PySimpleGUI est sa flexibilité en termes d’organisation des éléments de l’interface, appelés widgets. Chaque widget, qu’il s’agisse d’un bouton, d’un champ de texte ou d’une image, est placé dans ce que l’on appelle un « layout». Un layout peut être imaginé comme une grille sur laquelle vous placez vos widgets, ligne par ligne. Chaque ligne est une liste de widgets, et l’ensemble de ces lignes forme le layout, lui-même encapsulé dans une liste. Par exemple, si vous voulez créer une fenêtre avec un champ de texte en haut et deux boutons en dessous, vous pouvez le faire avec le code suivant :
layout = [ [sg.Text('Veuillez entrer votre nom :')], [sg.Input()], [sg.Button('Ok'), sg.Button('Annuler')] ]
Dans PySimpleGUI, la création de colonnes se fait à l’aide du widget Column
. Cela peut être utile lorsque vous voulez afficher des widgets côte à côte, ou lorsque vous voulez créer des sections d’interface plus complexes. Le widget Column
prend en argument une liste de listes, représentant le layout de la colonne. Vous pouvez imaginer une colonne comme une fenêtre à part entière avec son propre layout, qui est ensuite insérée dans le layout principal de votre fenêtre.
Dans l’exemple ci-dessous, chaque Column
a son propre layout, défini par une liste de listes, exactement comme le layout principal. Les deux colonnes sont ensuite placées sur la même ligne du layout principal, ce qui les fait apparaître côte à côte dans la fenêtre de l’application.
layout = [ [sg.Column([ [sg.Text('Colonne 1, ligne 1')], [sg.Text('Colonne 1, ligne 2')] ]), sg.Column([ [sg.Text('Colonne 2, ligne 1')], [sg.Text('Colonne 2, ligne 2')] ])] ]
Vous pouvez également contrôler l’alignement des widgets à l’intérieur d’une colonne avec l’option element_justification
de Column
, qui peut prendre les valeurs ‘left’, ‘center’ ou ‘right’.
Les colonnes dans PySimpleGUI offrent une grande flexibilité en termes de conception d’interface. Par exemple, elles peuvent être imbriquées les unes dans les autres pour créer des dispositions plus complexes. Une utilisation courante des colonnes est de regrouper des widgets associés, comme des boutons qui contrôlent une certaine fonction de l’application.
layout = [ [sg.Column([ [sg.Text('Colonne 1, ligne 1')], [sg.Column([ [sg.Text('Colonne 2, ligne 1')], [sg.Text('Colonne 2, ligne 2')] ])] ])] ]
De plus, il est important de noter que chaque colonne peut avoir sa propre largeur et hauteur, définies avec les options size et scrollable de Column. Les colonnes, tout comme les autres éléments de PySimpleGUI, peuvent être personnalisées de différentes façons pour répondre à vos besoins spécifiques. Vous pouvez modifier leur apparence, leur comportement, leur alignement et bien plus encore.
Pour personnaliser l’apparence des colonnes, PySimpleGUI offre plusieurs options, telles que background_color
, qui définit la couleur de fond de la colonne, et border_width
, qui définit l’épaisseur de la bordure autour de la colonne. Par exemple, pour créer une colonne avec une couleur de fond bleue et une bordure de 2 pixels, vous pouvez faire :
layout = [ [sg.Column([ [sg.Text('Colonne 1, ligne 1')], [sg.Text('Colonne 1, ligne 2')] ], background_color='blue', border_width=2)] ]
Navigation entre différentes interfaces
Lors du développement d’applications plus complexes, il est souvent nécessaire de disposer de plusieurs interfaces graphiques, aussi appelées fenêtres. Par exemple, vous pouvez avoir un écran d’accueil, un écran pour l’entrée de données et peut-être un autre pour afficher des résultats ou des graphiques. Dans ce contexte, il est important de comprendre comment naviguer de manière fluide et intuitive entre ces différentes fenêtres.
Dans l’exemple suivant, nous allons utiliser PySimpleGUI pour créer un écran d’accueil simple. Cet écran aura un titre et un bouton pour passer à la prochaine fenêtre. Nous allons utiliser l’approche de la programmation orientée objet pour organiser notre code, en encapsulant l’écran d’accueil dans sa propre classe. Notez que la classe Accueil encapsule à la fois la création de l’écran (dans son constructeur) et sa logique de fonctionnement (dans la méthode run). C’est une façon propre et modulaire d’organiser votre code lorsque vous travaillez avec plusieurs fenêtres ou écrans.
class WindowHome: def __init__(self): self._title = "Ecran d'Accueil" self._layout = [ [sg.Text("Bienvenue à l'écran d'accueil!")], [sg.Button("Aller à la prochaine fenêtre", key="REGISTER")] ] self._window = None def run(self): self._window = sg.Window(self._title, self._layout) while True: event, values = self._window.read() if event == sg.WINDOW_CLOSED: break elif event == "REGISTER": self._window.hide() formulaire = WindowRegister() result = formulaire.run() print(result) # Affiche le résultat du formulaire self._window.un_hide() self._window.close()
Pour passer de l’écran d’accueil à un autre écran comme un formulaire d’inscription, vous pouvez définir une autre classe pour le formulaire d’inscription. Ensuite, dans la méthode run de la classe WindowHome, lorsque l’utilisateur clique sur le bouton, vous créez une instance de la classe WindowInscription et appelez sa méthode run. La fonction window.hide() peut être utilisée pour masquer temporairement une fenêtre sans la fermer. C’est utile si vous voulez cacher une fenêtre pendant que vous ouvrez une autre, puis la réafficher plus tard.
L’exemple ci-dessous montre un formulaire d’inscription très simple qui demande le nom et l’e-mail de l’utilisateur. Notez que la méthode run du WindowRegister renvoie les valeurs saisies par l’utilisateur lorsque celui-ci clique sur le bouton « Valider ». Notez l’utilisation du window.close()
avant le return. Par ailleurs, l’utilisateur a deux manières de quitter la fenêtre d’inscription : soit en cliquant sur le bouton « Valider », soit en fermant la fenêtre. Dans le premier cas, la méthode run renvoie les valeurs saisies par l’utilisateur ; dans le second cas, elle renvoie None. Cela assure que la méthode run renvoie toujours une valeur, quelle que soit la manière dont l’utilisateur quitte la fenêtre.
class WindowRegister: def __init__(self): self._title = "Ecran Inscription" self._layout = [ [sg.Text("Nom:"), sg.Input(key="nom")], [sg.Text("Email:"), sg.Input(key="email")], [sg.Button("Valider", key="SUBMIT")] ] self._window = None def run(self): self._window = sg.Window(self._title, self._layout) while True: event, values = self.window.read() if event == sg.WINDOW_CLOSED: self._window.close() return None elif event == "SUBMIT": self._window.close() # Note: vous devriez ajouter ici une validation des données return values["nom"], values["email"] self._window.close()
Interactions entre l’interface graphique et les objets
Les interactions entre l’interface graphique et le code de l’application sont le pont qui relie l’utilisateur et la logique de l’application, permettant aux deux de communiquer. Il est donc important de comprendre comment cette liaison se fait.
La bibliothèque PySimpleGUI offre plusieurs mécanismes pour interagir avec votre code applicatif. Tout d’abord, chaque widget de l’interface graphique possède une méthode Update qui permet de modifier son contenu dynamiquement. Par exemple, vous pouvez utiliser cette méthode pour actualiser le texte d’un label ou pour ajouter de nouveaux éléments à une liste. Cela permet à votre code d’afficher des informations à jour à l’utilisateur.
Par ailleurs, chaque fois que l’utilisateur interagit avec un widget, PySimpleGUI génère un événement. Vous pouvez réagir à ces événements en exécutant une certaine partie de votre code. Par exemple, lorsque l’utilisateur clique sur un bouton « Soumettre », vous pouvez exécuter une fonction qui valide les données entrées par l’utilisateur, exécute une certaine logique d’application, puis met à jour l’interface pour afficher le résultat.
L’exemple ci-dessus correspond à une fenêtre avec un compteur et deux boutons pour augmenter et diminuer le compteur. Lorsque l’utilisateur clique sur le bouton “Increment”, le compteur est augmenté, et lorsqu’il clique sur le bouton “Decrement”, le compteur est diminué. L’affichage du compteur est actualisé dès que le compteur change. Notez que self._window['-TEXT-'].update(f'Valeur du compteur: {self._counter}')
est utilisé pour mettre à jour le texte affiché à l’utilisateur. Le '-TEXT-'
est une clé qui permet d’identifier le widget Text à actualiser. Cette clé est définie lors de la création du widget dans le layout de la fenêtre.
import PySimpleGUI as sg class CounterWindow: def __init__(self): self._title = "Le compteur" self._layout = [ [sg.Text('Valeur du compteur: 0', key='-TEXT-')], [sg.Button('Increment'), sg.Button('Decrement')] ] self._window = None self._counter = 0 def run(self): self._window = sg.Window(self._title, self._layout) while True: event, values = self._window.read() if event == sg.WINDOW_CLOSED: break elif event == 'Increment': self._counter += 1 elif event == 'Decrement': self._counter -= 1 self._window['-TEXT-'].update(f'Valeur du compteur: {self._counter}') self._window.close()
Dans l’exemple fourni, nous avons ajouté une variable supplémentaire, self._counter
, de type entier. Cette variable sert à conserver l’état du compteur entre les différentes interactions de l’utilisateur avec l’interface. Il est important de noter que bien que nous ayons utilisé un entier dans cet exemple, nous aurions tout aussi bien pu utiliser un objet plus complexe, tel qu’une instance d’une classe Timer
ou CompteurDeJaime
. La nature précise de l’objet est sans importance du moment qu’il est utilisé de manière cohérente dans le reste du code de la fenêtre. Cet objet peut être initialisé directement dans le constructeur de la fenêtre, ou il peut être passé au constructeur en paramètre si vous souhaitez permettre à l’extérieur de la fenêtre de contrôler son initialisation.
Quoi qu’il en soit, il est à noter que ces interactions doivent être pensées de manière à respecter le principe d’encapsulation de la programmation orientée objet. En effet, l’interface graphique ne doit pas accéder directement aux attributs internes de vos objets, mais plutôt utiliser leurs méthodes publiques pour interagir avec eux. Cela permet de préserver l’intégrité de vos objets et de faciliter la maintenance de votre code.
Il est important de noter que les règles établies pour la programmation console s’appliquent également à la programmation avec interface graphique. En particulier, il est important de maintenir la séparation entre la logique métier de votre application (c’est-à-dire les classes et objets que vous avez définis pour gérer vos données et vos algorithmes) et l’interface utilisateur.
Ainsi, de la même manière que nous nous sommes interdits d’utiliser la fonction print dans nos classes métier pour la console, il est tout aussi interdit d’appeler une fonction graphique directement à partir de ces classes lors de l’utilisation d’une interface graphique. Autrement dit, vos classes métier ne doivent pas être conscientes de l’existence de l’interface utilisateur. Elles ne devraient contenir aucune référence à PySimpleGUI ou à toute autre bibliothèque d’interface utilisateur.
Bonnes pratiques dans la conception d’interfaces
Bien que la conception d’interfaces graphiques puisse sembler principalement une question d’esthétique, il existe en réalité un certain nombre de principes et de bonnes pratiques qui peuvent améliorer l’efficacité et l’utilisabilité de votre application. Voici quelques-unes des bonnes pratiques fondamentales à prendre en compte lors de la création d’interfaces graphiques.
- Clarté et simplicité : Les meilleurs designs sont souvent les plus simples. Il est important de s’assurer que votre interface est intuitive et facile à comprendre. Les utilisateurs devraient pouvoir comprendre immédiatement à quoi sert chaque contrôle et comment naviguer dans l’application.
- Hiérarchie visuelle et organisation : Les éléments de votre interface doivent être organisés de manière logique et cohérente. Les contrôles liés doivent être regroupés ensemble, et la hiérarchie visuelle doit être utilisée pour indiquer les relations entre les différents éléments. Cela peut être réalisé grâce à l’utilisation appropriée des espacements, des lignes de séparation, des groupes de contrôle, etc.
- Consistance : La consistance est la clé pour rendre votre application facile à utiliser. Essayez de maintenir une consistance dans l’utilisation des couleurs, des polices, des tailles et des styles de vos contrôles. De même, les actions similaires doivent être effectuées de manière similaire dans différentes parties de votre application.
- Feedback : Il est important de fournir des retours aux utilisateurs lorsqu’ils interagissent avec votre application. Que ce soit par le biais de messages d’erreur, de confirmations d’actions réussies ou simplement de changements visibles dans l’état des contrôles, le feedback permet à l’utilisateur de comprendre ce qui se passe et de corriger ses erreurs.
Au-delà des principes esthétiques et ergonomiques, il faut respecter certaines règles techniques et organisationnelles lors de la conception d’interfaces graphiques. L’aspect technique du développement d’interfaces graphiques ne se limite pas à la programmation des contrôles eux-mêmes, mais englobe également des considérations importantes concernant la structure et l’organisation du code.
- Séparation du code en plusieurs fichiers : Pour maintenir la lisibilité et la modularité de votre code, il est préférable de séparer le code en plusieurs fichiers. Vous pourriez, par exemple, avoir un fichier pour chaque classe ou chaque fenêtre de votre application. Cela rendra votre code plus facile à comprendre et à maintenir à long terme.
- Dispatch d’événements vers des fonctions : Une bonne pratique supplémentaire pour organiser votre code consiste à utiliser le “dispatch” ou la “délégation” d’événements. En d’autres termes, plutôt que de gérer tous les événements (par exemple, les clics de bouton) directement dans la boucle principale de votre programme, vous pouvez « déléguer » chaque événement à une fonction spécifique.
- Séparation stricte entre l’affichage et la logique métier : au risque de se répéter, il s’agit d’une règle fondamentale de la programmation orientée objet. La logique métier de votre application doit strictement être séparée de l’affichage et de l’interaction avec l’utilisateur. En d’autres termes, les classes qui gèrent l’interface graphique ne doivent pas contenir de logiques métiers, et inversement. Cela permet d’assurer que chaque partie du code a une responsabilité claire et définie, facilitant ainsi la maintenance et la réutilisation du code.