[Python - Tkinter] Liste déroulante avec Combobox, le chemin de croix.

Python 3, c’est le bien. Les interfaces graphiques, c’est ergonomique. Tant qu’on reste dans les standards, on se dit que tout sera supporté, qu’il y aura de la belle documentation, etc… Et bien je dois dire que pour arriver à avoir une liste déroulante, j’ai du secouer mon ami Google.

La recherche

Au départ, j’ai cru trouver la solution dans la documentation de Tkinter (enfin, une des documentations, il n’y a apparemment rien de bien concis à ce niveau). Peine perdue. Compliqué à trouver, lire et à s’y retrouver.

Me disant que c’était juste une bête chose, un bête nom de widget, je tape sur Google (mais pas trop fort) : “liste déroulante + tkinter”. Mauvaise idée, en voyant les résultats de la première page je me suis rappelée que les recherches anglaises sont bien plus fructueuses (un stackoverflow en français, ça existe ?).

Me voici donc partie avec “dropdown menu + tkinter + python 3”. Sans entrer dans les détails, voici les différents résultats que j’ai pu avoir :


Hm... Ce n’est pas une liste déroulante mais un menu ça.



Toujours pas une liste déroulante, même si c'est techniquement pas mal...



Une simple liste déguisée en "liste déroulante". Toujours pas ça.


Moui, pas fameux quoi. J’ai même failli me résoudre à utiliser ces méthodes visiblement généralisée, mais après une pause d’une semaine sans le net, on retrouve de la force et de la ténacité.

Révélation

En cherchant d’autres modules de GUI pour le python, pour d’autres projets, je suis tombée sur le blog d’une personne aimant s’amuser avec ce langage et qui partage ses découvertes et ses savoirs. Comme ici en fait ! 😊 … Mais avec bien plus de contenu. Et, bonne surprise, cette personne est francophone ! Découvrez le blog de Fevrier Dorian en cliquant sur ce lien !

J’étais surprise de voir que je n’étais pas la seule à avoir galéré. Surpris eaussi de voir qu’il a collecté un bon paquet de documentation dont il a référencé les liens dans son article. C’est via ces derniers que j’ai découvert la façon d’utiliser LE widget qu’il me fallait.

Car oui, en cherchant les widgets pour faire des listes déroulantes, je suis tombée sur un mystérieux “Combobox”. Seulement, aucun des codes fourni ne fonctionnait et pour cause, le module était introuvable. Vous allez comprendre pourquoi…

Le widget Combobox

Le widget Combobox était exactement celui qu’il me fallait. J’ai découvert au travers de mes recherches que c’est un nom assez commun donné aux widgets de la plupart des GUI, dont la définition, selon wikipédia, est :

Une combo box est un widget d’interface graphique utilisateur couramment utilisé. Traditionnellement, c’est une combinaison d’une liste déroulante (drop down menu) ou une liste d’élément avec une ligne modifiable (textbox), permettant à l’utilisateur d’écrire une valeur directement ou de la choisir parmi une liste d’options existante. Aujourd’hui, la distinction de base entre une combo box et une liste déroulante a le plus souvent disparue.

Source : http://en.wikipedia.org/wiki/Combo_box

On s’approche de plus en plus du but initial : avoir une liste déroulante simple, où on ne fait que choisir un élément dans la liste. Or, visiblement ici, on peut aussi éditer les éléments. C’est alors que je vois sur la page Wikipédia :

Le terme “combo box” est parfois utilisé pour signifier “liste déroulante”. En Java et .NET, “combo box” n’est pas un synonyme de “liste déroulante”. La définition de “liste déroulante” est parfois clarifiée par les termes “combo box non éditable” (ou quelque chose de similaire) afin de la distinguer de la “combo box” originale.

Source : http://en.wikipedia.org/wiki/Combo_box

Peut-être (sûrement même…) que la combobox du Python possède un attribut permettant d’empêcher la modification de la liste des éléments. Il me faut donc de la documentation ! Et j’ai découvert un site listant l’utilisation d’une Combobox avec Tkinter (merci encore à Dorian) : http://www.tcl.tk/. Et avec la page qui m’intéresse : http://www.tcl.tk/man/tcl8.5/TkCmd/ttk_combobox.htm

Alors oui, ça n’est pas du python. Ce n’est même pas tkinter en réalité, mais Tcl/Tk. Sachant que tkinter est une surcouche permettant d’utiliser Tcl/Tk via le Python, ce n’est pas un souci 😄. Les informations données sur ces documentations seront suffisantes.

Implémentation du widget Combobox

Passons à la pratique en se servant des nombreux liens de documentation à notre portée !

Commençons déjà par le nom de la combobox du site : “ttk::combobox”. Le “ttk::” signifie que le widget combobox se situe dans la classe “ttk”. Ttk est défini dans la documentation de Python par “Tk themed widgets”.

En gros, on va pouvoir appliquer une certaine apparence aux widgets de base. Dans la documentation, on nous conseille d’inclure ce module de la façon suivante :

1
2
from tkinter import *
from tkinter.ttk import *

De cette façon, les widgets de base importés par la première ligne seront modifiés par la classe ttk que l’on importe à la deuxième ligne. On garde une uniformité de l’apparence, et ça c’est plutôt agréable ! 😄 On va mettre ça dans un squelette de programme graphique utilisant tkinter :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding:utf-8 -*-

from tkinter import * # GUI
from tkinter.ttk import * # Widgets avec thèmes

class MonProgramme(Tk):
""" Mon programme graphique utilisant Python3 et Tkinter """

def __init__(self):
Tk.__init__(self) # On dérive de Tk, on reprend sa méthode d'instanciation

# -------------------------

if(__name__ == '__main__'):
application = MonProgramme() # Instanciation de la classe
application.mainloop() # Boucle pour garder le programme en vie
application.quit() # Fermeture propre à la sortie de la boucle

Maintenant, on va ajouter le widget combobox. En regardant sur cette page, nous allons pouvoir déterminer quels arguments utiliser pour l’instanciation du widget. J’ai retenu ceux-ci :

  • textvariable : Doit être l’objet StringVar() spécifique à Tkinter. Cet objet contiendra l’élément sélectionné de la liste. Si nous modifions, via “variable.set(‘valeur’)”, la valeur de l’objet, l’élément sélectionné de la liste changera également. Ca fonctionne également dans l’autre sens : si on sélectionne un élément de la liste, notre objet changera sa valeur. Ce n’est pas requis, mais dans certains cas ça peut être utile

  • values : Est une variable de type list ou tuple. Ce que contiendra cette variable sera le contenu de la liste de notre combobox.

  • state : c’est l’attribut le plus intéressant à mes yeux. Il permet de dire si on peut modifier la liste (normal) ou non (readonly), ou bien la désactiver totalement (disabled). Et readonly est le choix qui s’est porté à moi.

Ce qui donnera ceci, par exemple :

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
# -*- coding:utf-8 -*-

from tkinter import * # GUI
from tkinter.ttk import * # Widgets avec thèmes

class MonProgramme(Tk):
""" Mon programme graphique utilisant Python3 et Tkinter """

def __init__(self):
Tk.__init__(self) # On dérive de Tk, on reprend sa méthode d'instanciation

# Widgets

# Quel fruit a été sélectionné ?
self.fruitSelect = StringVar()
self.stockFruits = ('Pomme', 'Poire', 'Banane')
self.listeFruits = Combobox(self, textvariable = self.fruitSelect, \
values = self.stockFruits, state = 'readonly')

# Placement des widgets
self.listeFruits.grid()

# -------------------------

if(__name__ == '__main__'):
application = MonProgramme() # Instanciation de la classe
application.mainloop() # Boucle pour garder le programme en vie
application.quit() # Fermeture propre à la sortie de la boucle

Et visuellement, ceci :

Aller un peu plus loin

Parfait. Nous avons notre liste déroulante, que peut-on faire avec ? Voici quelques lignes de codes que j’ai utilisées et qui pourront peut-être vous être utile 😊

Sélectionner le premier élément de la liste automatiquement

… Sinon, nous avons un vide qui correspond à une valeur nulle. C’est assez logique quand on y pense: la première ligne du widget est censée être utilisée pour écrire nous-même une valeur. Mais ici, nous sommes en readonly, donc pas de modification ni d’ajout autorisé.

Pour afficher la première valeur de la liste que nous avons créée, nous allons passer par self.fruitSelect (qui est une instance de StringVar()) et qui possède la valeur sélectionnée de la liste.

Cette dernière se mettra à jour selon la valeur de notre objet StringVar() également. Pour modifier la valeur de self.fruitSelect, nous utilisons la méthode set (str). Dans notre cas, nous allons fournir le premier élément de la liste des fruits dans self.stockFruits. Ce qui donne ceci :

1
self.fruitSelect.set(self.stockFruits[0])

Après exécution, nous constaterons que la liste déroulante aura directement pour valeur sélectionnée “Pomme” !

Exécuter une méthode après avoir cliqué sur un élément

Il y a un événement intégré aux combobox permettant d’exécuter une méthode en cliquant sur la combobox. C’est-à-dire avant de sélectionner un objet, donc en ouvrant simplement la liste par exemple. Cet événement porte l’intitulé postcommand.

Bien, s’iels ont prévu un événement se passant avant de modifier une liste, on peut logiquement s’attendre à en retrouver un qui se déclenchera quand on sélectionne un objet de la liste…

Mais évidemment, rien n’est simple dans la vie et ceci a été soit laissé de côté, soit oublié. Ce n’est pourtant pas impossible et une astuce simple permet d’arriver à nos fins grâce à la méthode .bind() (elle est présente dans TOUS les widgets !) et à l’événement envoyé par la combobox : .

Petit code de test :

1
2
3
self.combobox = Combobox(self, values = self.listeCombobox, state = 'readonly', background = 'white')

self.combobox.bind('<<ComboboxSelected>>', self.methodeTest) # Executer une méthode -après- sélection d'un élément

Petite astuce, les événements que .bind() va cibler sont entourés de < et >. Nous aurons donc une duplication de ces caractères, d’où la nécessité de les doubler nous-même ici 😉.

Fin !

En espérant que ceci soit utile à quelqu’un d’autre que moi 😄… Rappel des liens de ce billet :