Editing Canvas Text Items

December 08, 1998 | Fredrik Lundh

Introduction

The Tkinter Canvas widget provides some basic editing support for text items, but no bindings. This guide briefly describes these editing functions, and shows how to use them.

Focus #

The canvas widget can redirect keyboard focus to any given canvas item that supports the internal text editing protocol. In the standard implementation, text is the only item type that supports this interface.

To change the focus, use the focus method:

focus(item) moves focus to the given canvas item. If the item has keyboard bindings, it will receive all further keyboard events, given that the canvas itself also has focus. It’s usually best to call focus_set on the canvas whenever you set focus to a canvas item.

To remove focus from the item, call this method with an empty string.

focus() returns the item that currently has the focus, or None if no item has focus.

Editing #

The canvas also keeps track of an insertion cursor for each item that supports text editing. Each item keeps track of it’s own cursor position, but the cursor is only display for the item that has focus.

To manipulate the cursor, and the text displayed by the text item, you use methods similar to those provided by the Edit widget:

index(item, index) returns the numerical index (an integer) corresponding to the given index. Numerical indexes work like Python’s sequence indexes; 0 is just to the left of the first character, and len(text) is just to the right of the last character.

The index argument can be INSERT (the current insertion cursor), END (the length of the text), or SEL_FIRST and SEL_LAST (the selection start and end). You can also use the form “@x,y” where x and y are canvas coordinates, to get the index closest to the given coordinate.

icursor(item, index) moves the insertion cursor to the given position. You can either use a numerical index, or one of the special indexes described above.

insert(item, index, text) inserts text at the given position. If you insert text at the INSERT index, the cursor is moved along with the text.

dchars(item, index) deletes the character at the given index. dchars(item, from, to) removes the characters in the given range.

Selections #

Finally, the canvas widget maintains a current text selection. Note that the selection is handled by the widget, so only one item per canvas widget can have an active selection.

select_item() returns the item that owns the text selection for this canvas widget. If there’s no selection, this method returns None.

Note! In Tkinter.py as shipped with 1.5.1 (and possibly also with later versions), this method always returns None. For a workaround, see the has_selection method in the code sample below.

select_adjust(item, index) adjusts the selection (if any), so that it includes the given index. It also sets the selection anchor to this position. This is typically used by mouse bindings.

select_clear() remove the selection if it’s in this canvas widget.

select_from(item, index) sets the selection anchor point to the given index. Use select_adjust or select_to extend the selection.

select_to(item, index) modifies the selection so it includes the region between the current selection anchor (as set by select_from or select_adjust) and the given index.

Example #

This example provides a partial implementation for canvas text editing. Note that it uses widget-wide bindings for the keyboard events; if you prefer, you can use item-specific bindings instead.

 
# File: canvas-editing-example-1.py
#
# editing canvas items
#
# fredrik lundh, december 1998
#
# fredrik@pythonware.com
# http://www.pythonware.com
#

from Tkinter import *

class MyCanvas(Frame):

    def __init__(self, root):
        Frame.__init__(self, root)

        self.canvas = Canvas(self)
        self.canvas.pack(fill=BOTH, expand=1)

        # standard bindings
        self.canvas.bind("<Double-Button-1>", self.set_focus)
        self.canvas.bind("<Button-1>", self.set_cursor)
        self.canvas.bind("<Key>", self.handle_key)

        # add a few items to the canvas
        self.canvas.create_text(50, 50, text="hello")
        self.canvas.create_text(50, 100, text="world")

    def highlight(self, item):
        # mark focused item.  note that this code recreates the
        # rectangle for each update, but that's fast enough for
        # this case.
        bbox = self.canvas.bbox(item)
        self.canvas.delete("highlight")
        if bbox:
            i = self.canvas.create_rectangle(
                bbox, fill="white",
                tag="highlight"
                )
            self.canvas.lower(i, item)

    def has_focus(self):
        return self.canvas.focus()

    def has_selection(self):
        # hack to work around bug in Tkinter 1.101 (Python 1.5.1)
        return self.canvas.tk.call(self.canvas._w, 'select', 'item')

    def set_focus(self, event):
        if self.canvas.type(CURRENT) != "text":
            return

        self.highlight(CURRENT)

        # move focus to item
        self.canvas.focus_set() # move focus to canvas
        self.canvas.focus(CURRENT) # set focus to text item
        self.canvas.select_from(CURRENT, 0)
        self.canvas.select_to(CURRENT, END)

    def set_cursor(self, event):
        # move insertion cursor
        item = self.has_focus()
        if not item:
            return # or do something else

        # translate to the canvas coordinate system
        x = self.canvas.canvasx(event.x)
        y = self.canvas.canvasy(event.y)

        self.canvas.icursor(item, "@%d,%d" % (x, y))
        self.canvas.select_clear()

    def handle_key(self, event):
        # widget-wide key dispatcher
        item = self.has_focus()
        if not item:
            return

        insert = self.canvas.index(item, INSERT)

        if event.char >= " ":
            # printable character
            if self.has_selection():
                self.canvas.dchars(item, SEL_FIRST, SEL_LAST)
                self.canvas.select_clear()
            self.canvas.insert(item, "insert", event.char)
            self.highlight(item)

        elif event.keysym == "BackSpace":
            if self.has_selection():
                self.canvas.dchars(item, SEL_FIRST, SEL_LAST)
                self.canvas.select_clear()
            else:
                if insert > 0:
                    self.canvas.dchars(item, insert-1, insert)
            self.highlight(item)

        # navigation
        elif event.keysym == "Home":
            self.canvas.icursor(item, 0)
            self.canvas.select_clear()
        elif event.keysym == "End":
            self.canvas.icursor(item, END)
            self.canvas.select_clear()
        elif event.keysym == "Right":
            self.canvas.icursor(item, insert+1)
            self.canvas.select_clear()
        elif event.keysym == "Left":
            self.canvas.icursor(item, insert-1)
            self.canvas.select_clear()

        else:
            pass # print event.keysym

# try it out (double-click on a text to enable editing)
c = MyCanvas(Tk())
c.pack()

mainloop()

Copyright © 1998 by Fredrik Lundh

 

A Django site. rendered by a django application. hosted by webfaction.