Ceci est une ancienne révision du document !
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.
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.
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.
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).
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.
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.
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.
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.
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.
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).
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.
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.
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.
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!