A text widget manages a multi-line text area. Like the canvas widget, Tk's text widget is an immensely flexible and powerful tool which can be used for a wide variety of tasks. It can provide a simple multi-line text area as part of a form. But text widgets can also form the basis for a stylized code editor, an outliner, a web browser, and much more.
Note: Text widgets are part of the classic Tk widgets, not the themed Tk widgets.
Text widgets.
While we briefly introduced text widgets in an earlier chapter, we'll go into much more detail here. You'll get a better sense of the level of sophistication they allow. Still, for any significant work with the text widget, the reference manual is well-organized, useful, and a highly-recommended read.
Text widgets are created using the Text
class:
text = Text(parent, width=40, height=10)
You'll often provide a width (in characters) and height (in lines). As always, you can ask the geometry manager to expand it to fill the available space in the window.
If you simply need a multi-line text field for a form, there are only a few things to worry about: creating and sizing the widget (check), providing an initial value, and retrieving the text after a user has submitted the form.
Text widgets start with nothing in them, so we'll need to add any initial content ourselves.
Because text widgets can hold a lot more than plain text, a simple mechanism (like the entry widget's
textvariable
configuration option) isn't sufficient.
Instead, we'll use the widget's insert
method:
text.insert('1.0', 'here is my\ntext to insert')
The "1.0" here is the position where to insert the text, and can be read as "line 1, character 0". This refers to the first character of the first line. Historically, especially on Unix, programmers tend to think about line numbers as 1-based and character positions as 0-based.
The text to insert is just a string. Because the widget can hold multi-line text, the string we
supply can be multi-line as well. To do this, simply embed \n
(newline) characters in the
string at the appropriate locations.
After users have made any changes and submitted the form (for example), your program can
retrieve the contents of the widget via the get
method:
thetext = text.get('1.0', 'end')
The two parameters are the start and end position; end
has the obvious meaning.
You can provide different start and end positions if you want to obtain only part of the text.
You'll see more on positions shortly.
We previously saw the width
and height
configuration options for text widgets.
Several others control its overall appearance. The most useful are:
foreground
:background
:padx, pady
:borderwidth
:relief
:flat
, raised
, sunken
, solid
, ridge
, groove
What if some lines of text in the widget are very long, longer than the width of the widget? By default,
the text wraps around to the next line. This behavior can be changed with the
wrap
configuration option. It defaults to char
, meaning wrap lines at any character.
Other options are word
to wrap lines only at
word breaks (e.g., spaces), and none
meaning to not wrap lines at all. In the latter case,
some text of longer lines won't be visible unless we attach a horizontal scrollbar to the widget.
(Users can also scroll through the text using arrow keys, even if scrollbars aren't present).
Scrollbars, both horizontal and vertical, can be attached to the text widget in the same way as with other widgets, e.g., canvas, listbox.
t = Text(root, width = 40, height = 5, wrap = "none") ys = ttk.Scrollbar(root, orient = 'vertical', command = t.yview) xs = ttk.Scrollbar(root, orient = 'horizontal', command = t.xview) t['yscrollcommand'] = ys.set t['xscrollcommand'] = xs.set t.insert('end', "Lorem ipsum...\n...\n...") t.grid(column = 0, row = 0, sticky = 'nwes') xs.grid(column = 0, row = 1, sticky = 'we') ys.grid(column = 1, row = 0, sticky = 'ns') root.grid_columnconfigure(0, weight = 1) root.grid_rowconfigure(0, weight = 1)
We can also ask the widget to ensure that a certain part of the text is visible. For example, let's say
we've added more text to the widget than will fit onscreen (so it will scroll). However, we want to ensure
that the top of the text rather than the bottom is visible. We can use the see
method.
text.see('1.0')
Some forms will temporarily disable editing in particular widgets unless certain conditions are met
(e.g., some other options are set to a certain value). To prevent users from changing
a text widget, set the state
configuration option to disabled
. Re-enable editing
by setting this option back to normal
.
text['state'] = 'disabled'
As text widgets are part of the classic widgets, the usual state
and instate
methods are not available.
While users can modify the text in the text widget interactively, your program can also make
changes. Adding text is done with the insert
method, which we used above to provide an
initial value for the text widget.
When we specified a position of 1.0
(first line, first character), this was an example of an index.
It tells the insert
method where to put the new text (just before the first line, first character, i.e., at the
very start of the widget). Indices can be specified in a variety of ways. We used another one with the get
method:
end
means just past the end of the text. (Why "just past?" Text is inserted right
before the given index, so inserting at end
will add text to the end of the widget).
Note that Tk will always add a newline at the very end of the text widget.
Here are a few additional examples of indices and how to interpret them:
3.end
:1.0 + 3 chars
:2.end -1 chars
:end -1 chars
:end -2 chars
:end -1 lines
:2.2 + 2 lines
:2.5 linestart
:2.5 lineend
:2.5 wordstart
:2.5 wordend
:Some additional things to keep in mind:
chars
can be abbreviated as c
, and lines
as l
.
1.0+3c
.
end + 100c
) is interpreted as end
.
1.0 + 10 chars
on a line with only five
characters will refer to a position on the second line.
To determine the canonical position of an index, use the index idx
method. Pass it any
index expression, and it returns the corresponding index in the form line.char
. For example,
to find the position of the last character (ignoring the automatic newline at the end), use:
text.index('end')
You can compare two indices using the compare
method, which lets you check for equality,
whether one index is later in the text than the other, etc.
if text.compare(idx1, "==", idx2"): # same position
Valid operators are ==
, !=
, <
, <=
, >
, and >=
.
While the insert
method adds new text anywhere in the widget, the delete start ?end?
method removes it.
We can delete either a single character (specified by index) or a range of characters (specified by
start and end indices). In the latter case, characters from (and including) the start index through to just before
the end index will be deleted (so the character at the end index is not deleted). So if we assume for each of these
we start off with "abcd\nefgh"
in the text widget:
text.delete('1.2') ⇒ "abd\nefgh" text.delete('1.1', '1.2') ⇒ "acd\nefgh" text.delete('1.0', '2.0') ⇒ "efgh" text.delete('1.2', '2.1') ⇒ "abfgh"
There is also a replace
method that performs a delete
followed by an insert
at the same location.
Here's a short example that illustrates using a text widget as an 80x24 logging window for an application. Users don't edit the text widget at all. Instead, the program writes log messages to it. We'd like to display more than 24 lines (so no scrolling). If the log is full, old messages are removed from the top before new ones are added at the end.
from tkinter import * from tkinter import ttk root = Tk() log = Text(root, state='disabled', width=80, height=24, wrap='none') log.grid() def writeToLog(msg): numlines = int(log.index('end - 1 line').split('.')[0]) log['state'] = 'normal' if numlines==24: log.delete(1.0, 2.0) if log.index('end-1c')!='1.0': log.insert('end', '\n') log.insert('end', msg) log['state'] = 'disabled'
Note that because the program placed the widget in a disabled state, we had to re-enable it to make any changes, even from our program.
So far, we've used text widgets when all the text is in a single font. Now it's time to look at how to add special formatting, such as bold, italic, strikethrough, background colors, font sizes, and much more. Tk's text widget implements these using a feature called tags.
Tags are objects associated with the text widget. Each tag is referred to via a name chosen by the programmer. Each tag has several configuration options. These are things like fonts and colors that control formatting. Though tags are objects having state, they don't need to be explicitly created. They are automatically created the first time the tag name is used.
Tags can be associated with one or more ranges of text in the widget. As before, ranges are specified via indices.
A single index represents a single character, and a pair of indices represent a range from the start
character to just before the end character. Tags are added to a range of text using the
tag_add
method.
text.tag_add('highlightline', '5.0', '6.0')
Tags can also be provided when first inserting text. The insert
method supports an optional parameter
containing a list of one or more tags to add to the text being inserted.
text.insert('end', 'new material to insert', ('highlightline', 'recent', 'warning'))
As the text in the widget is modified, whether by a user or your program, the tags will adjust automatically. For example, if we tagged the text "the quick brown fox" with the tag "nounphrase", and then replaced the word "quick" with "speedy," the tag still applies to the entire phrase.
Formatting is applied to tags via configuration options; these work similarly to configuration options for the entire widget. As an example:
text.tag_configure('highlightline', background='yellow', font='TkFixedFont', relief='raised')
Tags support the following configuration options: background
, bgstipple
, borderwidth
,
elide
, fgstipple
, font
, foreground
, justify
, lmargin1
, lmargin2
,
offset
, overstrike
, relief
, rmargin
, spacing1
, spacing2
,
spacing3
, tabs
, tabstyle
, underline
, and wrap
.
Check the reference manual for detailed descriptions of these. The tag_cget tag option
method allows us to query the configuration options of a tag.
Because multiple tags can apply to the same range of text, there is the possibility of conflict (e.g., two tags
specifying different fonts). A priority order is used to resolve these; the most recently created tags have
the highest priority, but priorities can be rearranged using the tag_raise tag
and tag_lower tag
methods.
To delete one or more tags altogether, we can use the tag_delete tags
method. This also, of course,
removes any references to the tag in the text. We can also remove a tag from a range of text using the
tag_remove tag start ?end?
method.
Even if that leaves no ranges of text with that tag, the tag object itself still exists.
The tag_ranges tag
method will return a list of ranges in the text that the tag has been applied to. There
are also tag_nextrange tag start ?end?
and tag_prevrange tag start ?end?
methods to
search forward or backward for the first such range from a given position.
The tag_names ?idx?
method, called with no additional parameters, will return a list of all tags currently
defined in the text widget (including those that may not be presently used). If we pass the method an index,
it will return the list of tags applied to just the character at the index.
Finally, we can use the first and last characters in the text having a given tag as indices, the same way we
can use "end" or "2.5". To do so, just specify tagname.first
or tagname.last
.
Both canvas and text widgets support "tags" that can be applied to several objects, style them, etc. However, canvas and text tags are not the same. There are important differences to take note of.
In canvas widgets, only individual canvas items have configuration options that control their appearance. When we refer to a tag in a canvas, the meaning of that is identical to "all canvas items presently having that tag." The tag itself doesn't exist as a separate object. So in the following snippet, the last rectangle added will not be colored red.
canvas.itemconfigure('important', fill='red') canvas.create_rectangle(10, 10, 40, 40, tags=('important'))
In contrast, with text widgets, it's not the individual characters that retain the state information about appearance, but tags, which are objects in their own right. So in this snippet, the newly added text will be colored red.
text.insert('end', 'first text', ('important')) text.tag_configure('important', foreground='red') text.insert('end', 'second text', ('important'))
One very cool thing we can do is define event bindings on tags. That allows us to do things like easily
recognize mouse clicks on particular ranges of text, and popup up a menu or dialog in response. Different
tags can have different bindings. This saves the hassle of sorting out questions like "what does a click
at this location mean?". Bindings on tags are implemented using the tag_bind
method:
text.tag_bind('important', '<1>', popupImportantMenu)
Widget-wide event bindings continue to work as they do for every other widget, e.g., to capture a mouse click
anywhere in the text. Besides the normal low-level events, the text widget generates a
<<Modified>>
virtual event whenever a change is made to the
content of the widget, and a <<Selection>>
virtual event
whenever there is a change made to which text is selected.
We can identify the range of text selected by a user, if any.
For example, an editor may have a toolbar button to bold the selected text. While you can tell when
the selection has changed (e.g., to update whether or not the bold button is active) via the <<Selection>>
virtual event, that doesn't tell you what has been selected.
The text widget automatically maintains a tag named sel
, which refers to the selected text. Whenever the
selection changes, the sel
tag will be updated. So we can find the range of text selected using the
tag_ranges ?tag?
method, passing it sel
as the tag to report on.
Similarly, we can change the selection by using tag_add
to set a new selection, or tag_remove
to remove the selection. The sel
tag cannot be deleted, however.
Though the default widget bindings prevent this from happening, sel
is like any other tag in that it can support
multiple ranges, i.e., disjoint selections. To prevent this from happening, when changing the selection from your
code, make sure to remove any old selection before adding a new one.
The text widget manages the concept of the insertion cursor (where newly typed text will appear) separate from the selection. It does so using a new concept called a mark.
Marks indicate a particular place in the text. In that respect, they are like indices. However, as the text is modified, the mark will adjust to be in the same relative location. In that way, they resemble tags, but refer to a single position rather than a range of text. Marks actually don't refer to a position occupied by a character in the text but specify a position between two characters.
Tk automatically maintains two different marks. The first, named insert
, is the present location of the
insertion cursor. As the cursor is moved (via mouse or keyboard), the mark moves with it. The second mark,
named current
, tracks the position of the character underneath the current mouse position.
To create your own marks, use the widget's mark_set name idx
method, passing it the name of the mark and an index
(the mark is positioned just before the character at the given index). This is also used to move an existing mark to
a different position. Marks can be removed using the mark_unset name
method, passing it the name of the mark.
If you delete a range of text containing a mark, that also removes the mark.
The name of a mark can also be used as an index (in the same way 1.0
or end-1c
are indices).
You can find the next mark (or previous one) from a given index in the text using the mark_next idx
or
mark_previous idx
methods. The mark_names
method will return a list of the names of all marks.
Marks also have a gravity, which can be modified with the mark_gravity name ?direction?
method,
which affects what happens when text is inserted at the mark. Suppose we have the text "ac" with a mark in between that
we'll symbolize with a pipe, i.e., "a|c." If the gravity of that mark is right
(the default), the mark
attaches itself to the "c." If the new text "b" is inserted at the mark, the mark will remain stuck to the "c,"
and so the new text will be inserted before the mark, i.e., "ab|c." If the gravity is instead left
, the mark
attaches itself to the "a," and so new text will be inserted after the mark, i.e., "a|bc."
Like canvas widgets, text widgets can contain not only text but also images and any other Tk widgets (including a frame, itself containing many other widgets). In a sense, this allows the text widget to work as a geometry manager in its own right. The ability to add images and widgets within the text opens up a world of possibilities for your program.
Images are added to a text widget at a particular index, with the image specified as an existing Tk image. Other options that allow you to fine-tune padding, etc.
flowers = PhotoImage(file='flowers.gif') text.image_create('sel.first', image=flowers)
Other widgets are added to a text widget in much the same way as images. The widget being added must be a descendant of the text widget in the widget hierarchy.
b = ttk.Button(text, text='Push Me') text.window_create('1.0', window=b)
Text widgets can do many more things. Here, we'll briefly mention just a few more of them. For details on any of these, see the reference manual.
The text widget includes a powerful search
method that allows you to locate a piece of text within the
widget. This is useful for a "Find" dialog, as one obvious example. You can search backward or forward from a
particular position or within a given range, specify the search term using exact text, case insensitive, or via
regular expressions, find one or all occurrences of the search term, etc.
The text widget keeps track of whether changes have been made to the text (useful to know whether it needs to be
saved to a file, for example). We can query (or change) using the edit_modified ?bool?
method. There is also
a complete multi-level undo/redo mechanism, managed automatically by the widget when we set its undo
configuration option to true
. Calling edit_undo
or edit_redo
modifies the current
text using information stored on the undo/redo stack.
Text widgets can include text that is not displayed. This is known as "elided" text, and is made
available using the elide
configuration option for tags. It can be used to implement
an outliner, a "folding" code editor, or even to bury extra meta-data intermixed with displayed text.
When specifying positions within elided text, you have to be a bit more careful. Methods that work with
positions have extra options to either include or ignore the elided text.
Like most Tk widgets, the text widget goes out of its way to expose information about its internal state. We've
seen this in terms of the get
method, widget configuration options, names
and
cget
for both tags and marks, etc. There is even more information available that can be useful
for a wide variety of tasks. Check out the debug
, dlineinfo
, bbox
,
count
, and dump
methods in the reference manual.
The Tk text widget allows the same underlying text data structure (containing all the text, marks, tags, images, etc.) to be
shared between two or more different text widgets. This is known as peering and is controlled via the
peer
method.