• Aucun résultat trouvé

Python Mega Widgets

Dans le document Chapitre 1 : Penser comme un programmeur (Page 178-185)

objet.méthode(arguments)

Chapitre 10 : Approfondir les structures de données

14.3 Python Mega Widgets

Les modules Pmw constituent une extension intéressante de Tkinter. Entièrement écrits en Python, ils contiennent toute une librairie de widgets composites, construits à partir des classes de base de Tkinter. Dotés de fonctionnalités très étendues, ces widgets peuvent se révéler fort précieux pour le développement rapide d'applications complexes. Si vous souhaitez les utiliser, sachez cependant que les modules Pmw ne font pas partie de l'installation standard de Python : vous devrez donc toujours vérifier leur présence sur les machines cibles de vos programmes.

Il existe un grand nombre de ces méga-widgets. Nous n'en présenterons ici que quelques-uns parmi les plus utiles. Vous pouvez rapidement vous faire une idée plus complète de leurs multiples possibilités, en essayant les scripts de démonstration qui les accompagnent (lancez par exemple le script all.py , situé dans le répertoire \Pmw\demos).

14.3.1 "Combo Box"

Les méga-widgets s'utilisent aisément. La petite application ci-après vous montre comment mettre en oeuvre un widget de type ComboBox (boîte de liste combinée à un champ d'entrée). Nous l'avons configuré de la manière la plus habituelle (avec une boîte de liste déroulante).

Lorsque l'utilisateur de notre petit programme choisit une couleur dans la liste déroulante (il peut aussi entrer un nom de couleur directement dans le champ d'entrée), cette couleur devient automatiquement la couleur de fond pour la fenêtre maîtresse.

Dans cette fenêtre maîtresse, nous avons ajouté un libellé et un bouton, afin de vous montrer comment vous pouvez accéder à la sélection opérée précédemment dans le ComboBox lui-même (le bouton provoque l'affichage du nom de la dernière couleur choisie).

1. from Tkinter import * 2. import Pmw 3.

4. def changeCoul(col):

5. fen.configure(background = col) 6.

7. def changeLabel():

8. lab.configure(text = combo.get()) 9.

10. couleurs = ('navy', 'royal blue', 'steelblue1', 'cadet blue', 11. 'lawn green', 'forest green', 'dark red',

12. 'grey80','grey60', 'grey40', 'grey20') 13.

14. fen = Pmw.initialise()

15. bou = Button(fen, text ="Test", command =changeLabel) 16. bou.grid(row =1, column =0, padx =8, pady =6)

17. lab = Label(fen, text ='néant', bg ='ivory') 18. lab.grid(row =1, column =1, padx =8)

19.

20. combo = Pmw.ComboBox(fen, labelpos = NW,

21. label_text = 'Choisissez la couleur :', 22. scrolledlist_items = couleurs,

23. listheight = 150,

24. selectioncommand = changeCoul) 25. combo.grid(row =2, columnspan =2, padx =10, pady =10) 26.

27. fen.mainloop()

Commentaires :

Lignes 1 & 2 : On commence par importer les composants habituels de Tkinter, ainsi que le module Pmw.

Ligne 14 : Pour créer la fenêtre maîtresse, il faut utiliser de préférence la méthode Pmw.initialise(), plutôt que d'instancier directement un objet de la classe Tk(). Cette méthode veille en effet à mettre en place tout ce qui est nécessaire afin que les widgets esclaves de cette fenêtre puissent être détruits correctement lorsque la fenêtre elle-même sera détruite. Cette méthode installe également un meilleur gestionnaire des messages d'erreurs.

Ligne 12 : L'option labelpos détermine l'emplacement du libellé qui accompagne le champ d'entrée. Dans notre exemple, nous l'avons placé au-dessus, mais vous pourriez préférer le placer ailleurs, à gauche par exemple (labelpos = W). Notez que cette option est indispensable si vous souhaitez un libellé (pas de valeur par défaut).

Ligne 14 : L'option selectioncommand transmet un argument à la fonction invoquée : l'item sélectionné dans la boîte de liste. Vous pourrez également retrouver cette sélection à l'aide de la méthode get(), comme nous le faisons à la ligne 8 pour actualiser le libellé.

14.3.2 "Scrolled Text"

Ce méga-widget étend les possibilités du widget Text sandard, en lui associant un cadre, un libellé (titre) et des barres de défilement.

Comme le démontrera le petit script ci-dessous, il sert fondamen-talement à afficher des textes, mais ceux-ci peuvent être mis en forme et intégrer des images.

Vous pouvez également rendre

"cliquables" les éléments affichés (textes ou images), et vous en servir pour déclencher toutes sortes de mécanismes.

Dans l'application qui génère la figure ci-dessus, par exemple, le fait de cliquer sur le nom "Jean de la Fontaine" provoque le défilement automatique du texte (scrolling), jusqu'à ce qu'une rubrique décrivant cet auteur devienne visible dans le widget (Voir page suivante le script correspondant).

D'autres fonctionnalités sont présentes, mais nous ne présenterons ici que les plus fondamentales.

Veuillez donc consulter les démos et exemples accompagnant Pmw pour en savoir davantage.

Gestion du texte affiché : Vous pouvez accéder à n'importe quelle portion du texte pris en charge par le widget grâce à deux concepts complémentaires, les index et les balises :

Chaque caractère du texte affiché est référencé par un index, lequel doit être une chaîne contenant deux valeurs numériques reliées par un point (ex : "5.2"). Ces deux valeurs indiquent respectivement le numéro de ligne et le numéro de colonne où se situe le caractère.

N'importe quelle portion du texte peut être associée à une ou plusieurs balise(s), dont vous choisissez librement le nom et les propriétés. Celles-ci vous permettent de définir la police, les couleurs d'avant- et d'arrière-plan, les événements associés, etc.

Note : Pour la bonne compréhension du script ci-dessous, veuillez considérer que le texte de la fable traitée doit être accessible, dans un fichier nommé "CorbRenard.txt".

1. from Tkinter import * 2. import Pmw 3.

4. def action(event=None):

5. """défilement du texte jusqu'à la balise <cible>"""

6. index = st.tag_nextrange('cible', '0.0', END) 7. st.see(index[0])

8.

9. # Instanciation d'une fenêtre contenant un widget ScrolledText : 10. fen = Pmw.initialise() 23. st.pack(expand =YES, fill =BOTH, padx =8, pady =8) 24.

25. # Définition de balises, liaison d'un gestionnaire d'événement au clic de souris : 26. st.tag_configure('titre', foreground ='brown', font ='Helvetica 11 bold italic') 27. st.tag_configure('lien', foreground ='blue', font ='Helvetica 11 bold')

28. st.tag_configure('cible', foreground ='forest green', font ='Times 11 bold') 29. st.tag_bind('lien', '<Button-1>', action)

30.

31. titre ="""Le Corbeau et le Renard

32. par Jean de la Fontaine, auteur français 33. \n"""

34. auteur ="""

35. Jean de la Fontaine

36. écrivain français (1621-1695) 37. célèbre pour ses Contes en vers, 38. et surtout ses Fables, publiées 39. de 1668 à 1694."""

40.

41. # Remplissage du widget Text (2 techniques) : 42. st.importfile('CorbRenard.txt')

43. st.insert('0.0', titre, 'titre') 44. st.insert(END, auteur, 'cible') 45. # Insertion d'une image :

46. photo =PhotoImage(file= 'Penguin.gif') 47. st.image_create('6.14', image =photo) 48. # Mise en oeuvre dynamique d'une balise : 49. st.tag_add('lien', '2.4', '2.23')

50.

51. fen.mainloop()

Lignes 4-7 : Cette fonction est un gestionnaire d'événement, qui est appelé lorsque l'utilisateur effectue un clic de souris sur le nom de l'auteur (cfr. lignes 27 & 29). A la ligne 6, on utilise la méthode tag_nextrange() du widget pour trouver les index de la portion de texte associée à la balise "cible". La recherche de ces index est limitée au domaine défini par les 2e& 3earguments (dans notre exemple, on recherche du début à la fin du texte entier). La méthode tag_nextrange() renvoie une liste de deux index (ceux des premier et dernier caractères de la portion de texte associée à la balise "cible"). A la ligne 7, nous nous servons d'un seul de ces index (le premier) pour activer la méthode see(). Celle-ci provoque un défilement automatique du texte (scrolling), de telle manière que le caractère correspondant à l'index transmis devienne visible dans le widget (avec en général un certain nombre des caractères qui suivent).

Lignes 9 à 23 : Construction classique d'une fenêtre destinée à afficher un seul widget. Dans le code d'instanciation du widget, nous avons inclus un certain nombre d'options destinées à vous montrer une petite partie des nombreuses possibilités de configuration.

Ligne 12 : L'option labelpos détermine l'emplacement du libellé (titre) par rapport à la fenêtre de texte. Les valeurs acceptées s'inspirent des lettres utilisées pour désigner les points cardinaux (N, S, E, W, ou encore NE, NW, SE, SW). Si vous ne souhaitez pas afficher un libellé, il vous suffit tout simplement de ne pas utiliser cette option.

Lignes 13 à 15 : Le libellé n'est rien d'autre qu'un widget Label standard, intégré dans le widget composite ScrolledText. On peut accéder à toutes ses options de configuration, en utilisant la syntaxe qui est présentée dans ces lignes : on y voit qu'il suffit d'associer le préfixe label_ au nom de l'option que l'on souhaite activer, pour définir aisément les couleurs d'avant- et d'arrière-plans, la police, la taille, et même l'espacement à réserver autour du widget (option pady).

Lignes 16-17 : En utilisant une technique similaire à celle qui est décrite ci-dessus pour le libellé, on peut accéder aux options de configuration du widget Text intégré dans ScrolledText. Il suffit cette fois d'associer aux noms d'option le préfixe text_.

Lignes 18 à 20 : Il est prévu un cadre (un widget Frame) autour du widget Text. L'option borderframe = 1 permet de le faire apparaître. On accède ensuite à ses options de configuration d'une manière similaire à celle qui a été décrite ci-dessus pour label_ et text_.

Lignes 21-22 : Ces options permettent de fixer globalement les dimensions du widget. Une autre possibilité serait de définir plutôt les dimensions de son composant Text (par exemple à l'aide d'options telles que text_width et text_height), mais alors les dimensions globales du widget risqueraient de changer en fonction du contenu (apparition/disparition automatique de barres de défilement). Note : le mot hull désigne le contenant global, c.à.d. le méga-widget lui-même.

Ligne 23 : Les options expand = YES et fill = BOTH de la méthode pack() indiquent que le widget concerné pourra être redimensionné à volonté, dans ses 2 dimensions horiz. et verticale.

Lignes 26 à 29 : Ces lignes définissent les trois balises "titre", "lien" et "cible" ainsi que le formatage du texte qui leur sera associé. La ligne 29 précise en outre que le texte associé à la balise "lien" sera "cliquable", avec indication du gestionnaire d'événement correspondant.

Ligne 42 : Importation de texte à partir d'un fichier. Note : Il est possible de préciser l'endroit exact où devra se faire l'insertion, en fournissant un index comme second argument.

Lignes 43-44 : Ces instructions insèrent des fragments de texte (respectivement au début et à la fin du texte préexistant), en associant une balise à chacun d'eux.

Ligne 49 : L'association des balises au texte est dynamique. A tout moment, vous pouvez activer une nouvelle association (comme nous le faisons ici en rattachant la balise "lien" à une portion de texte préexistante). Note : pour "détacher" une balise, utilisez la méthode tag_delete().

14.3.3 "Scrolled Canvas"

Le script ci-après vous montre comment vous pouvez exploiter le méga-widget ScrolledCanvas, lequel étend les possibilités du widget Canvas standard en lui associant des barres de défilement, un libellé et un cadre. Notre exemple constitue en fait un petit jeu d'adresse, dans lequel l'utilisateur doit réussir à cliquer sur un bouton qui s'esquive sans cesse. (Note : si vous éprouvez vraiment des difficultés pour l'attraper, commencez d'abord par dilater la fenêtre).

Le widget Canvas est très versatile : il vous permet de combiner à volonté des dessins, des images bitmap, des fragments de texte, et même d'autres widgets, dans un espace parfaitement extensible. Si vous souhaitez développer l'un ou l'autre jeu graphique, c'est évidemment le widget qu'il vous faut apprendre à maîtriser en priorité.

Comprenez bien cependant que les indications que nous vous fournissons à ce sujet dans les présentes notes sont forcément très incomplètes. Leur objectif est seulement de vous aider à comprendre quelques concepts de base, afin que vous puissiez ensuite consulter les ouvrages de référence spécialisés dans de bonnes conditions.

Notre petite application se présente comme une nouvelle classe FenPrinc(), obtenue par dérivation à partir de la classe de méga-widgets Pmw.ScrolledCanvas(). Elle contient donc un grand canevas muni de barres de défilement, dans lequel nous commençons par planter un décor constitué de 80 ellipses de couleur dont l'emplacement et les dimensions sont tirés au hasard.

Nous y ajoutons également un petit clin d'oeil sous la forme d'une image bitmap, destinée avant tout à vous rappeler comment vous pouvez gérer ce type de ressource.

Nous y installons enfin un véritable widget : un simple bouton, en l'occurrence, mais la technique mise en oeuvre pourrait s'appliquer à n'importe quel autre type de widget, y compris un gros widget composite comme ceux que nous avons développés précédemment. Cette grande souplesse dans le développement d'applications complexes est l'un des principaux bénéfices apportés par le mode de programmation "orientée objet".

Le bouton s'anime dès qu'on l'a enfoncé une première fois. Dans votre analyse du script ci-après, soyez attentifs aux méthodes utilisées pour modifier les propriétés d'un objet existant.

1. from Tkinter import * 7. 'red','khaki','indian red','thistle','firebrick','salmon','coral']

8.

9. class FenPrinc(Pmw.ScrolledCanvas):

10. """Fenêtre principale : canevas extensible avec barres de défilement"""

11. def __init__(self):

12. Pmw.ScrolledCanvas.__init__(self,

13. usehullsize =1, hull_width =500, hull_height =300,

34. self.bou = Button(self.can, text ="Start", command =self.start) 35. self.fb = self.can.create_window(self.x, self.y, window =self.bou) 36. self.resizescrollregion()

50. self.bou.configure(text ="Restart", command =self.start) 51.

52. def start(self):

53. self.bou.configure(text ="Attrapez-moi !", command =self.stop) 54. self.run =1

60. FenPrinc().mainloop()

Commentaires :

Ligne 6 : Tous ces noms de couleurs sont acceptés par Tkinter. Vous pourriez bien évidemment les remplacer par des descriptions hexadécimales, comme nous l'avons expliqué page 177.

Lignes 12 à 17 : Ces options sont très similaires à celles que nous avons décrites plus haut pour le widget ScrolledText. Le présent méga-widget intègre un composant Frame, un composant Label, un composant Canvas et deux composants Scrollbar. On accède aux options de configuration de ces composants à l'aide d'une syntaxe qui relie le nom du composant et celui de l'option par l'intermédiaire d'un caractère "souligné".

Ligne 19 : Ces options définissent le mode d'apparition des barres de défilement. En mode

"static", elles sont toujours présentes. En mode "dynamic", elles disparaissent si les dimensions du canevas deviennent inférieures à celles de la fenêtre de visualisation.

Ligne 22 : La méthode interior() renvoie la référence du composant Canvas intégré dans le méga-widget ScrolledCanvas. Les instructions suivantes (lignes 23 à 35) installent ensuite toute une série d'éléments dans ce canevas : des dessins, une image et un bouton.

Lignes 25 à 27 : La fonction randrange() permet de tirer au hasard un nombre entier compris dans un certain intervalle (Veuillez vous référer aux explications de la page 135).

Ligne 35 : C'est la méthode create_window() du widget Canvas qui permet d'y insérer n'importe quel autre widget (y compris un widget composite). Le widget à insérer doit cependant avoir été défini lui-même au préalable comme un esclave du canevas ou de sa fenêtre maîtresse.

La méthode create_window() attend trois arguments : les coordonnées X et Y du point où l'on souhaite insérer le widget, et la référence de ce widget.

Ligne 36 : La méthode resizescrollregion() réajuste la situation des barres de défilement de manière à ce qu'elles soient en accord avec la portion du canevas actuellement affichée.

Lignes 38 à 46 : Cette méthode est utilisée pour l'animation du bouton. Après avoir repositionné le bouton au hasard à une certaine distance de sa position précédente, elle se ré-appelle elle-même après une pause de 250 millisecondes. Ce bouclage s'effectue sans cesse, aussi longtemps que la variable self.run contient une valeur non-nulle.

Lignes 48 à 55 : Ces deux gestionnaires d'événement sont associés au bouton en alternance. Ils servent évidemment à démarrer et à arrêter l'animation.

Dans le document Chapitre 1 : Penser comme un programmeur (Page 178-185)