While we managed to leverage some built-in functionality to gain a quick advantage, we need a more precise control over the text area, so as to bend it to our will. This would require the ability to target each character or location of the text with precision.
The Text widget offers us the ability to manipulate its content using index, tags, and mark , which lets us target a position or place within the text area for manipulation.
Indexing helps you target a particular place within a text. For example, if you want to mark a particular word in bold style or in red or in a different font size, you can do so if you know the index of the starting point and the index of end point to be targeted.
The index must be specified in one of the following formats:
Index format |
Description |
---|---|
The character that covers the | |
The end of the text. | |
The character after a named mark. | |
The first character in the text that has been tagged with a given tag. | |
The last character in the text that has been tagged with a given tag. | |
This corresponds to the current selection. The constants | |
The position of the embedded window whose name is | |
The position of the embedded image with the name | |
The position of the insertion cursor. | |
The position of the character closest to the mouse pointer. |
Indices can be further manipulated using modifiers and submodifiers. Some examples of modifiers and submodifers are as follows:
end - 1 chars
or end - 1 c
refers to the index of one character before the endinsert +5lines
refers to the index of five lines ahead of the insertion cursorinsertwordstart - 1 c
refers to the character just before the first one in the word containing the insertion cursorend linestart
refers to the index of the line start of the end lineIndexes are often used as arguments to functions. For example, refer to the following list:
text.delete(1.0,END)
: This means you can delete from line 1, column 0 up till the endtext.get(0.0, END)
: This gets the content from 0.0 up till the endtext.delete(insert-1c, INSERT)
: This deletes one character at the insertion cursorTags are used to annotate text with an identification string that can then be used to manipulate the tagged text. Tkinter has a built-in tag called SEL, which is automatically applied to the selected text. In addition to SEL, you can define your own tags. A text range can be associated with multiple tags, and the same tag can be used for many different text ranges.
Some examples of tagging are as follows:
mytext.tag_add('sel', '1.0', 'end') # add SEL tag from start(1.0) to end mytext.tag_add("danger", "insert linestart", "insert lineend+1c") mytext.tag_remove("danger", 1.0, "end") mytext.tag_config('danger', background=red) mytext.tag_config('outdated', overstrike=1)
You can specify the visual style for a given tag with tag_config
using options such as background(color)
, bgstipple (bitmap)
, borderwidth (distance)
, fgstipple (bitmap)
, font (font)
, foreground (color)
, justify (constant)
, lmargin1 (distance)
, lmargin2 (distance)
, offset (distance)
, overstrike (flag)
, relief (constant)
, rmargin (distance)
, spacing1 (distance)
, tabs (string)
, underline (flag)
, and wrap (constant)
.
For a complete reference of text indexing and tagging, type the following command into your Python interactive shell:
>>> import Tkinter >>> help(Tkinter.Text)
Equipped with a basic understanding of indexing and tagging, let's implement some more features in our code editor.
SEL
tag that applies a selection to a given text range. We want to apply this sel
tag to the complete text contained in our widget.We simply define a function to handle this. Refer to the code in 2.04.py
as shown in the following code snippet:
def select_all():
textPad.tag_add('sel', '1.0', 'end')
After this we add a callback to our Select All menu item:
editmenu.add_command(label="Select All", underline=7, accelerator='Ctrl+A', command=select_all)
Now, we are done adding the Select All functionality to our code editor. If you now add some text to the text widget and then click on the menu item select all, it should select the entire text in your editor. Note that we have not bound the Ctrl + A accelerator in the menu options. The keyboard shortcut will therefore not work. We will make the accelerator
function in a separate step.
Here's a quick summary of the desired functionality. When a user clicks on the Find menu item, a new Toplevel window opens up. The user enters a search keyword, and specifies if the search is to be case-sensitive. When the user clicks on the Find All button, all matches are highlighted.
For searching through the document, we will rely on the text.search()
method. The search
method takes in the following arguments:
search(pattern, startindex, stopindex=None, forwards=None, backwards=None, exact=None, regexp=None, nocase=None, count=None)
For our editor, we define a function called on_find
and attach it as a callback to our Find menu item (refer to the code in 2.04.py
):
editmenu.add_command(label="Find", underline= 0, accelerator='Ctrl+F', command=on_find)
We then define our function on_find
as follows (refer to the code in 2.04.py
):
def on_find(): t2 = Toplevel(root) t2.title('Find') t2.geometry('262x65+200+250') t2.transient(root) Label(t2, text="Find All:").grid(row=0, column=0, sticky='e') v=StringVar() e = Entry(t2, width=25, textvariable=v) e.grid(row=0, column=1, padx=2, pady=2, sticky='we') e.focus_set() c=IntVar() Checkbutton(t2, text='Ignore Case', variable=c).grid(row=1, column=1, sticky='e', padx=2, pady=2) Button(t2, text="Find All", underline=0, command=lambda: search_for(v.get(), c.get(), textPad, t2, e)).grid(row=0, column=2, sticky='e'+'w', padx=2, pady=2) def close_search(): textPad.tag_remove('match', '1.0', END) t2.destroy() t2.protocol('WM_DELETE_WINDOW', close_search)#override close
The description of the preceding code is as follows:
on_find
.on_find()
function creates a new Toplevel window, adds a title Find
, specifies it geometry (size, shape, and location), and sets it as a transient window. Setting it to transient means that it is always drawn on top of its parent or root window. If you uncomment this line and click on the root editor window, the Find window will go behind the root window.e
and c
, to track the value a user enters into the Entry widget, and whether or not the user has checked the check button. The widgets are arranged using the grid
geometry manager to fit into the Find window.command
option that calls a function, search_for()
, passing the search string as the first argument and whether or not the search is to be case-sensitive as its second argument. The third, fourth, and fifth arguments pass the Toplevel window, the Text widget, and the Entry widget as parameters.search_for()
method, we override the Close button of the Find window and redirect it to a callback named close_search()
. The close_search()
method is defined within the on_find()
function. This function takes care of removing the tag match
that was added during the search. If we do not override the Close button and remove these tags, our matched string will continue to be marked in red and yellow, even after our searching has ended.search_for()
function that does the actual searching. The code is as follows:def search_for(needle, cssnstv, textPad, t2, e) : textPad.tag_remove('match', '1.0', END) count =0 if needle: pos = '1.0' while True: pos = textPad.search(needle, pos, nocase=cssnstv,stopindex=END) if not pos: break lastpos = '%s+%dc' % (pos, len(needle)) textPad.tag_add('match', pos, lastpos) count += 1 pos = lastpos textPad.tag_config('match', foreground='red',background='yellow') e.focus_set() t2.title('%d matches found' %count)
The description of the code is listed as follows:
while True
loop, breaking out of the loop only if no more text items remain to be searched.match
tags as we do not want to append the results of the new search to previous search results. The function uses the search()
method provided in Tkinter on the Text widget. The search()
function takes the following arguments:search(pattern, index, stopindex=None, forwards=None, backwards=None, exact=None, regexp=None, nocase=None, count=None)
The method returns the starting position of the first match. We store it in a variable with the name pos
and also calculate the position of the last character in the matched word and store it in the variable lastpos
.
match
to the range of text starting from the first position to the last position. After every match, we set the value of pos
to be equal to lastpos
. This ensures that the next search starts after lastpos
.match
is configured to be of a red font color and with a background of yellow. The last line of this function updates the title of the Find window with the number of matches found.In the case of event bindings, interaction occurs between your input devices (keyboard/mouse) and your application. In addition to event binding, Tkinter also supports protocol handling.
The term "protocol" means the interaction between your application and the window manager. An example of a protocol is WM_DELETE_WINDOW
, which handles the close
window event for your window manager. Tkinter lets you override these protocols handlers by mentioning your own handler for the root or Toplevel widget. To override our window exit protocol, we use the following command:
root.protocol("WM_DELETE_WINDOW", callback)
Once you add this command, Tkinter bypasses protocol handling to your specified callback/handler.
Congratulations! In this iteration, we have completed coding the Select All and Find functionality into our program.
More importantly, we have been introduced to indexing and tagging—two very powerful concepts associated with many Tkinter widgets. You will find yourself using these two concepts all the time in your projects.
In the previous code, we used a line that reads: t2.transient(root)
. Let's understand what it means here.
Tkinter supports four types of Toplevel windows:
overrideredirect
flag to 1
. An undecorated window cannot be resized or moved.See the code in 2.05.py
for a demonstration of all these four types of Toplevel windows.
18.189.186.167