Outils pour utilisateurs

Outils du site


issue192:python

Over the years, I've talked about various aspects of the Tkinter toolkit that is a part of Python. Tkinter is a wrapper to the Tk/ttk toolkit that is a part of Tcl. For the most part Tkinter has kept up with the updates of Tk/ttk. Using the Tkinter toolkit provides almost anything you would need to create a GUI for your Python programs. I say almost because there are a few things missing, like a really good spreadsheet widget and a terminal widget. There are third party widgets that will, with a fair amount of work, will do the job, but those things that are missing from the “standard” toolkit leave a fairly large hole. Anyway, rather than dwelling on the missing parts, we should celebrate what is there. In my mind, there are two widgets that are extra useful because of the large number of things that can be done with them. The first is the Canvas widget. Not only can the Canvas widget draw lines, arcs, text and other things, it can also hold images and can be a container for other widgets. The other is the Text widget, which is the subject of this month's article.

Au fil des ans, j'ai parlé de divers aspects de la boîte à outils Tkinter qui fait partie de Python. Tkinter est un wrapper de la boîte à outils Tk/ttk qui fait partie de Tcl. Pour l'essentiel, Tkinter a suivi les mises à jour de Tk/ttk.

L'utilisation de la boîte à outils Tkinter fournit presque tout ce dont vous avez besoin pour créer une interface graphique pour vos programmes Python. Je dis presque, car il manque quelques éléments, comme un très bon widget de feuille de calcul et un widget de terminal. Il existe des widgets tiers qui, avec une bonne dose de travail, feront l'affaire, mais ces choses manquent à la boîte à outils « standard » et laissent un trou assez important.

Quoi qu'il en soit, plutôt que de s'attarder sur les parties manquantes, nous devrions nous réjouir de ce qui existe.

Selon moi, deux widgets sont particulièrement utiles en raison du grand nombre de choses que l'on peut faire avec eux. Le premier est le widget Canvas. Il permet non seulement de dessiner des lignes, des arcs, du texte et d'autres choses, mais il peut également contenir des images et servir de conteneur pour d'autres widgets. L'autre est le widget Texte, qui fait l'objet de l'article de ce mois-ci.

Anyone who is used to creating GUIs knows that there are two “normal” widgets that support multiple lines of text, the text widget and the Message widget, both of which are part of the “standard” Tk widget set. Both work for displaying multiple lines of text, but the Text widget allows for input of text as well. Tk Text Widget Not only can the Text widget handle simple text display and entry, there is a lot more that it can do. • Mix text with different fonts, colors and backgrounds. • Embed images along with the text. • The Text widget can contain invisible mark objects. • Handle special display handling through a process called tags. • Bind events to a tagged region. • Embed any of the Tk widgets within a “window”. This includes a Tk Frame that contains other widgets. In order to help show some of these features, I threw together a small demo. You can get the source code from my repository (see the end of the article). The image shown left is what the demo looks like when run.

Toute personne habituée à créer des interfaces graphiques sait qu'il existe deux widgets « normaux » qui prennent en charge plusieurs lignes de texte : le widget Texte et le widget Message, qui font tous deux partie du jeu de widgets « standard » de Tk. Ces deux widgets permettent d'afficher plusieurs lignes de texte, mais le widget Texte permet également de saisir du texte.

Widget Tk Texte

Le widget Texte peut non seulement gérer l'affichage et la saisie de texte simple, mais il peut faire beaucoup plus. ••Mélanger du texte avec différentes polices, couleurs et arrière-plans. ••Intégrer des images au texte. ••Le widget Texte peut contenir des objets de marquage invisibles. ••Gérer des affichages spéciaux par le biais d'un processus appelé « balises ». ••Lier des événements à une zone étiquetée. ••Intégrer n'importe quel widget Tk dans une « fenêtre ». Cela inclut un cadre Tk qui contient d'autres widgets.

Afin de présenter certaines de ces fonctionnalités, j'ai créé une petite démo. Vous pouvez obtenir le code source à partir de mon dépôt (voir la fin de l'article).

L'image montrée à gauche est ce à quoi ressemble la démo lorsqu'elle est exécutée.

You can see many of the features “in action” in the image. At the bottom of the form, you can see location indicators, the left showing the Line and Column position of the cursor and on the right is the selection start and end positions. You can also see the use of tags in the Text widget. There is a tag for Bold, Italic, Bold and Italic, Red foreground, Blue foreground and Green foregrounds. Indexes Before we can get to the code, there are some basics that need to be understood. The first is the use of indices which specifies the position of the content in the Text widget window. The index is a string which can be in many different forms. There are other index options, but they are rarely used.

L'image ci-contre permet de voir un grand nombre de ces fonctions « en action ». Au bas du formulaire, vous pouvez voir des indicateurs de position, la gauche montrant la position en ligne et en colonne du curseur et la droite les positions de début et de fin de la sélection.

Vous pouvez également voir l'utilisation des balises dans le widget Texte. Il existe une balise pour le gras, l'italique, l'italique gras, l'avant-plan rouge, l'avant-plan bleu et l'avant-plan vert.

Index

Avant d'entrer dans le code, il convient de comprendre quelques principes de base. Le premier est l'utilisation d'indices qui spécifient la position du contenu dans la fenêtre du widget Texte. L'index est une chaîne de caractères qui peut se présenter sous différentes formes.

Il existe d'autres options d'index, mais elles sont rarement utilisées.

Tags In my opinion, Tags are the most useful feature of the Text widget. However, if your tag is to include a font, you must define the font before you define the tag. To define a font, you need to import the font module from tkinter. from tkinter import font Once you’ve done this, you can start to define the font. However, there are a few rules that need to be followed. The most important rule is that if the font name (family) has a space, it must be enclosed not only in quotes, but also in curly brackets. If the font name is a single word, like “Arial”, then you just need to enclose it in quotes. The order of the attributes is fairly loose, but from experience, you want to keep to the order of family, size, weight and slant. If you need to include underline or overstrike, those can go last and should be set like “underline=0” for no underline or “underline=1” for underline. The same goes for overstrike. The following line of code would define a font named “fontBodyNormal” (shown below).

Balises

À mon avis, les balises sont la fonction la plus utile du widget Texte. Cependant, si votre balise doit inclure une police, vous devez définir la police avant de définir la balise. Pour définir une police, vous devez importer le module font de tkinter.

from tkinter import font

Une fois que vous avez fait cela, vous pouvez commencer à définir la police. Cependant, quelques règles doivent être respectées. La règle la plus importante est que si le nom de la police (famille) contient un espace, il doit être placé non seulement entre guillemets, mais aussi entre crochets. Si le nom de la police est en un seul mot, comme « Arial », il suffit de le mettre entre guillemets. L'ordre des attributs est assez libre, mais l'expérience montre qu'il est préférable de respecter l'ordre suivant : famille, taille, poids et inclinaison. Si vous devez inclure le soulignement ou la surimpression, ces attributs peuvent être placés en dernier et doivent être définis comme « underline=0 » pour l'absence de soulignement ou « underline=1 » pour le soulignement. Il en va de même pour la surimpression. La ligne de code suivante définit une police nommée « fontBodyNormal » (voir ci-dessous).

Once you have your font(s) defined, then you can start to define your tag(s). Of course, there is a caveat. There are MANY options available to a tag. The font is just one of the 19 options available. You can find the list of options at https://www.tcl.tk/man/tcl8.3/TkCmd/text.html or in the New Mexico Tech Tkinter 8.5 manual at https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text-methods.html under the .tag_config method. Basically, the syntax is something like this… .tag_config(tagname, option1, option2, …) Knowing which of the 19 options to use to create your tag takes experimentation. There are just too many things you can do. One of the neat things that you can set is the relief option. This, like most Tk widgets, is the 3-D effect that will be used to display the tagged text. The choices are “flat”, “raised”, ”sunken”, “groove”, “ridge” and “solid”. Then you can set the spacing1, spacing2, spacing3 and borderwidth to give a very nice 3-D effect that is great for headers when combined with background and foreground colors.

Une fois que vous avez défini votre (vos) police(s), vous pouvez commencer à définir votre (vos) balise(s). Bien sûr, il y a une mise en garde. Il existe de NOMBREUSES options pour une balise. La police n'est qu'une des 19 options disponibles. Vous trouverez la liste des options à l'adresse https://www.tcl.tk/man/tcl8.3/TkCmd/text.html ou dans le manuel Tkinter 8.5 de New Mexico Tech à l'adresse https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text-methods.html sous la méthode .tag_config.

En gros, la syntaxe est à peu près la suivante :

.tag_config(tagname, option1, option2, …)

Pour savoir laquelle des 19 options utiliser pour créer votre balise, il faut expérimenter. Il y a tout simplement trop de choses que vous pouvez faire. L'une des choses intéressantes que vous pouvez définir est l'option relief. Comme pour la plupart des widgets Tk, il s'agit de l'effet 3D qui sera utilisé pour afficher le texte étiqueté. Les choix possibles sont « plat », « en relief », « en creux », « rainure », « crête » et « solide ». Vous pouvez ensuite définir l'espacement 1, l'espacement 2, l'espacement 3 et la largeur de la bordure pour obtenir un très bel effet 3D, idéal pour les en-têtes lorsqu'il est combiné avec les couleurs d'arrière-plan et d'avant-plan.

Marks Marks are invisible objects that are positioned BETWEEN character positions that move with the text. There are a couple of Tk defined marks (INSERT and CURRENT) and any number of user-defined marks that can be created. From my testing, they aren’t really that useful outside of the two Tk defined marks. You will see me use marks later in the code.

Marques

Les marques sont des objets invisibles placés ENTRE les positions des caractères qui se déplacent avec le texte. Il existe deux marques définies par Tk (INSERT et CURRENT) et un grand nombre de marques définies par l'utilisateur qui peuvent être créées. D'après mes tests, elles ne sont pas vraiment utiles en dehors des deux marques définies par Tk. Vous me verrez utiliser des marques plus tard dans le code.

Images The (in my mind) second most useful thing that the Text widget can do is embed images amongst the text. It can be at the beginning of a sentence or paragraph, at the end of a sentence or paragraph or smack dab in the middle of a sentence. In order to use images in your Text widget, you must remember that the images have to be of the formats that Tkinter natively supports (.xbm, .gif, .pgm, .ppm or .png). If you want to support .jpg files, you can, but you need to use the Pillow library. Similar to tags, you need to use the .image_create() method, which has the syntax of .image_create(index, option1, option2, …) In the code, I decided to use the least number of options.

Images

La deuxième chose la plus utile (à mon avis) que peut faire le widget Texte est d'intégrer des images dans le texte. Elles peuvent être placées au début d'une phrase ou d'un paragraphe, à la fin d'une phrase ou d'un paragraphe ou en plein milieu d'une phrase.

Afin d'utiliser des images dans votre widget Texte, vous devez vous rappeler que les images doivent être dans les formats supportés nativement par Tkinter (.xbm, .gif, .pgm, .ppm ou .png). Si vous souhaitez prendre en charge les fichiers .jpg, vous pouvez le faire, mais vous devez utiliser la bibliothèque Pillow.

Comme pour les balises, vous devez utiliser la méthode .image_create(), dont la syntaxe est la suivante :

.image_create(index, option1, option2, …)

Dans le code, j'ai décidé d'utiliser le moins d'options possible.

The Code Finally, we can get started with the code. I chose to use PAGE 7.6 to create the GUI for me. There are many reasons for this, but the biggest one is that PAGE provides not only the Text widget but a ScrolledText widget that already has the scroll bars provided. When I designed the GUI, I used the ScrolledText widget just as it comes “out of the box” with only one attribute changed from the default. That was to set the “wrap” attribute to “word”, which I do 94% of the time. Once I had the GUI created, I started working on the code in the support module. First I needed to add to the import section (shown above). I needed to include the font module from tkinter as well as the messagebox (just in case) and then the filedialog set, so the user can import a text file. Next, I needed to add a startup module to get things going before the GUI is presented to the user. I set the bindings for the ScrolledText widget, which includes a keystroke event, a Button-1 event and a Button-3 event for the context menu.

Le code

Enfin, nous pouvons commencer avec le code. J'ai choisi d'utiliser PAGE 7.6 pour créer l'interface graphique. Il y a de nombreuses raisons à cela, mais la plus importante est que PAGE fournit non seulement le widget Texte mais aussi un widget Texte déroulant qui possède déjà les barres de défilement.

Lorsque j'ai conçu l'interface graphique, j'ai utilisé le widget ScrolledText tel qu'il est « prêt à l'emploi », en ne modifiant qu'un seul attribut par rapport à la valeur par défaut. Il s'agissait de définir l'attribut « wrap » en « word », ce que je fais 94 % du temps. Une fois l'interface graphique créée, j'ai commencé à travailler sur le code du module de support. Tout d'abord, j'ai dû ajouter quelque chose à la section d'importation (illustrée ci-dessus).

Je devais inclure le module de police de tkinter ainsi que la boîte de message (juste au cas où) et paramétrer filedialog, afin que l'utilisateur puisse importer un fichier texte.

Ensuite, j'ai eu besoin d'ajouter un module de démarrage pour lancer des choses avant que l'interface graphique ne soit présentée à l'utilisateur. J'ai défini les liaisons pour le widget ScrolledText, qui comprend un événement de frappe, un événement de bouton 1 et un événement de bouton 3 pour le menu contextuel.

It also calls a function (top right) that creates the fonts and tags before the Text widget is used. This is not required, but it’s much easier to do early on if you know what fonts and tags you want to use. Since this is just a demo I wanted to keep it simple. Here is the code for that. You can see that there are three font focused tags and three color focused tags. The Context menu will allow the user to set the font tags and color tags. The next function I created was the Keypress callback (shown bottom right). I use this to track the INSERT index. I basically query the INSERT index each time a keyboard key is released (better to keep key bounce from sending false signals). It comes in as a “line.column” message. I use the string .find method looking for the period. Then I check the tag_ranges method to see if there are characters that are selected as a “group”. Remember that if you try to use the SEL_FIRST and SEL_LAST indexes and nothing is selected, you will get an error. To avoid that, I call the tag_ranges method to get a tuple containing the First and Last values. If there is nothing selected, the returned value will be an empty tuple. So by checking the returned value to see if the length is greater than 0, I can safely see if and what the selection is. At the end of the function, I push the information to the two Label widgets at the bottom of the form.

Il appelle également une fonction (en haut à droite) qui crée les polices et les balises avant que le widget Texte ne soit utilisé. Ce n'est pas obligatoire, mais il est beaucoup plus facile de le faire dès le début si vous savez quelles polices et quelles balises vous voulez utiliser. Comme il ne s'agit que d'une démo, j'ai voulu rester simple. Voici le code pour cela.

Vous pouvez voir qu'il y a trois balises axées sur la police et trois balises axées sur la couleur. Le menu contextuel permet à l'utilisateur de définir les balises de police et de couleur.

La fonction suivante que j'ai créée est le rappel Keypress (en bas à droite). Je l'utilise pour suivre l'index INSERT. J'interroge l'index INSERT à chaque fois qu'une touche du clavier est relâchée pour éviter que les rebonds de touches n'envoient de faux signaux. Le message arrive sous la forme d'un message « line.column ». J'utilise la méthode des chaînes de caractères .find pour rechercher le point. Je vérifie ensuite la méthode tag_ranges pour voir si des caractères sont sélectionnés en tant que « groupe ». N'oubliez pas que si vous essayez d'utiliser les index SEL_FIRST et SEL_LAST et que rien n'est sélectionné, vous obtiendrez une erreur. Pour éviter cela, j'appelle la méthode tag_ranges pour obtenir un tuple contenant les valeurs First et Last. Si rien n'est sélectionné, la valeur renvoyée sera un tuple vide. Ainsi, en vérifiant la valeur renvoyée pour voir si la longueur est supérieure à 0, je peux voir en toute sécurité s'il y a eu sélection et laquelle. À la fin de la fonction, j'envoie les informations aux deux widgets Label au bas du formulaire.

Since the context menu is used to insert the tags (both color and font), they are very similar code wise. Here is the function to make a selection of text to be bold (next page, top right). We need to use the .tag_add in order to set a tag and it really needs to be a selection group of text. You can see in the function that you need to include the tag name as well as index1 and index2. To turn the bold off (let’s assume you wanted to use italics instead or in addition), you can simply use the tag_remove method (shown middle right). Just like the .tag_add method, you need to include the tagname, the index1 and index2 positions. There is a .tag_delete method, but if you call that, it will completely remove ALL tags of that type AND will delete the definition as well, so you can’t use it again until the program is restarted or the tag is added again. One of the things that you might want to do is programmatically move the insertion cursor. I included two simple functions that will show how to do that. The on_btnGoToTop and on_btnGotoBottom functions handle this (right).

Comme le menu contextuel est utilisé pour insérer les balises (couleur et police), le code est très similaire. Voici la fonction permettant de sélectionner le texte à mettre en gras (page suivante, en haut à droite).

Nous devons utiliser .tag_add pour définir une balise et il doit s'agir d'un groupe de texte sélectionné. Vous pouvez voir dans la fonction que vous devez inclure le nom de la balise ainsi que l'index1 et l'index2.

Pour désactiver le gras (supposons que vous souhaitiez utiliser l'italique à la place ou en plus), vous pouvez simplement utiliser la méthode tag_remove (illustrée au milieu à droite). Tout comme la méthode .tag_add, vous devez inclure le nom de la marque, l'index1 et l'index2.

Il existe une méthode .tag_delete, mais si vous l'appelez, elle supprimera complètement TOUTES les balises de ce type ET supprimera également la définition, de sorte que vous ne pourrez plus l'utiliser tant que le programme n'aura pas été redémarré ou que la balise n'aura pas été ajoutée à nouveau.

L'une des choses que vous pourriez vouloir faire est de déplacer le curseur d'insertion de manière programmée. J'ai inclus deux fonctions simples qui montrent comment faire. Les fonctions on_btnGoToTop et on_btnGotoBottom s'en chargent (à droite).

There are only two lines that are needed to accomplish this task. First, we call the .yview(index) method which scrolls the Text window to the index position. Then we call the .mark_set(INSERT,index) to actually move the insertion cursor (bottom right). There is a method called .see(index) that does something similar, but depending on the distance that needs to be scrolled, the position set with index, might be at the top, bottom or even in the middle of the Text window. I find the .yview() method much better to use. Next, here is the function that will insert an image (next page, top right). First, like loading a file, we need to use the file dialog askopenfilename to get the name of the image we wish to use. Once we have the filename of the image file, we play some games with the filename. We also need to create a global copy of the file object so that Python’s garbage collection doesn’t get rid of it before it can actually be seen. The method to insert the image has the following syntax… .image_create(index,image, image_name, align, padx, pady) The align, padx and pady attributes are optional.

Seules deux lignes sont nécessaires pour accomplir cette tâche. Tout d'abord, nous appelons la méthode .yview(index) qui fait défiler la fenêtre de texte jusqu'à la position de l'index. Ensuite, nous appelons la méthode .mark_set(INSERT,index) pour déplacer le curseur d'insertion (en bas à droite).

Il existe une méthode appelée .see(index) qui fait quelque chose de similaire, mais en fonction de la distance à parcourir, la position définie avec l'index peut se trouver en haut, en bas ou même au milieu de la fenêtre de texte. Je trouve que la méthode .yview() est beaucoup plus facile à utiliser.

Ensuite, voici la fonction qui insère une image (page suivante, en haut à droite). Tout d'abord, comme pour le chargement d'un fichier, nous devons utiliser la boîte de dialogue de fichier askopenfilename pour obtenir le nom de l'image que nous souhaitons utiliser.

Une fois que nous avons le nom du fichier image, nous jouons avec le nom du fichier. Nous devons également créer une copie globale de l'objet fichier afin que le ramasse-miettes de Python ne s'en débarrasse pas avant qu'il ne soit visible. La méthode d'insertion de l'image a la syntaxe suivante :

.image_create(index,image, nom_image, align, padx, pady)

Les attributs align, padx et pady sont facultatifs.

So the image_name is simply a string that is associated with the image itself. I chose to use the filename without the extension or path for this (shown bottom left). Finally, here is the function that loads a text file into the Text widget. You can see that the first part of the function is almost identical to the one we used to get the image filename (shown below). Now, we clear the text widget (assuming there was a filename chosen) by using the .delete(index1, index2) method, then we use the .insert method which requires the index and the text and optionally has a tag definition at the end (next page, top right). Finally, we call .focus_set() to return focus to the Text widget.

Le nom de l'image est donc simplement une chaîne de caractères associée à l'image elle-même. J'ai choisi d'utiliser le nom du fichier sans l'extension ni le chemin d'accès (en bas à gauche).

Enfin, voici la fonction qui charge un fichier texte dans le widget Text. Vous pouvez constater que la première partie de la fonction est presque identique à celle que nous avons utilisée pour obtenir le nom de fichier de l'image (voir ci-dessous).

Ensuite, nous effaçons le widget texte (en supposant qu'un nom de fichier ait été choisi) en utilisant la méthode .delete(index1, index2), puis nous utilisons la méthode .insert qui requiert l'index et le texte et comporte éventuellement une définition de balise à la fin (page suivante, en haut à droite).

Enfin, nous appelons la méthode .focus_set() pour redonner le focus au widget Texte.

Conclusion The biggest downside of Tags is that there can be more than one tag applied to a block of text at any given time. In fact, it is possible that every tag that has been defined can be applied to any given block of text. This can be a problem, because when there are multiple tags for any given block, the tag that was most recently created is the one that takes control and will be shown. You can use the .tag_raise() and the .tag_lower methods like this… .tag_raise(tagname, abovethis=None) To “activate” a different tag in the stack, but you need to know which and how many tags are set for that block of text. In order to find what tags are set for a block of text, you can use .tag_names(index) Which will return a tuple of the tag names that are associated with that text block.

Conclusion

Le principal inconvénient des balises est qu'il peut y avoir plus d'une balise appliquée à un bloc de texte à un moment donné. En fait, il est possible que chaque balise définie soit appliquée à un bloc de texte donné.

Cela peut poser un problème, car lorsqu'il existe plusieurs balises pour un bloc donné, c'est la balise la plus récemment créée qui prend le contrôle et qui est affichée. Vous pouvez utiliser les méthodes .tag_raise() et .tag_lower comme suit :

.tag_raise(tagname, abovethis=None)

Pour « activer » une balise différente dans la pile, vous devez savoir laquelle et combien de balises sont définies pour ce bloc de texte.

Pour savoir quelles balises sont définies pour un bloc de texte, vous pouvez utiliser la fonction :

.tag_names(index)

qui renverra un tuple des noms de balises associés à ce bloc de texte.

There are many other features that the Text widget provides like search, edit undo and edit redo and more. However, I wanted to try to keep the demo and article down to a reasonable length. Seriously, I could do half a book just on the Text widget. Here are the links again for the two resources to learn more. https://www.tcl.tk/man/tcl8.3/TkCmd/text.html https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text-methods.html I sincerely hope that I’ve been able to give you a new appreciation for the humble Text widget. The best way to learn more about it is to create a sample program and play with all the available options. As I normally do, I’ve placed the source code into a repository on Github at https://github.com/gregwa1953/FCM192 . Until next time, as always; stay safe, healthy, positive and creative!

Le widget Texte offre de nombreuses autres fonctionnalités, telles que la recherche, l'annulation et le rétablissement des modifications, et bien d'autres encore. Cependant, je voulais essayer de limiter la démo et l'article à une longueur raisonnable.

Sérieusement, je pourrais écrire la moitié d'un livre sur le widget Texte. Voici à nouveau les liens vers les deux ressources pour en savoir plus :

https://www.tcl.tk/man/tcl8.3/TkCmd/text.html

https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text-methods.html

J'espère sincèrement que j'ai réussi à vous faire apprécier l'humble widget Texte. La meilleure façon d'en savoir plus est de créer un exemple de programme et de jouer avec toutes les options disponibles.

Comme je le fais habituellement, j'ai placé le code source dans un dépôt sur Github à l'adresse https://github.com/gregwa1953/FCM192.

Jusqu'à la prochaine fois, comme toujours, restez en sécurité, en bonne santé, positifs et créatifs !

issue192/python.txt · Dernière modification : 2023/05/04 15:00 de andre_domenech