# File: textcanvas.py # References: # http://infohost.nmt.edu/tcc/help/pubs/tkinter//canvas.html # http://tkinter.unpythonic.net/wiki/A_tour_of_Tkinter_widgets from tkinter import * from tkinter import ttk from demopanels import MsgPanel, SeeDismissPanel class TextCanvasDemo(ttk.Frame): def __init__(self, isapp=True, name='textcanvasdemo'): ttk.Frame.__init__(self, name=name) self.pack(expand=Y, fill=BOTH) self.master.title('Text Canvas Demo') self.isapp = isapp self._create_widgets() def _create_widgets(self): if self.isapp: MsgPanel(self, ["This window displays a string of text to demonstrate ", "the text facilities of canvas widgets.\n", "Click in the 'Text Position' grid to adjust the position of ", "the text relative to its anchor (red square).\n", "Click in the 'Justification' grid to change the text's justification.\n", "The text also supports the following simple bindings for editing: \n\n", " 1. You can point, click, and type in the text area.\n", " 2. You can select text with the left mouse button.\n", " 3. You can copy the selection to the mouse position with \n", " the right mouse button (must be within text).\n", " 4. Backspace deletes the selection if there is one;\n", " otherwise it deletes the character just before the insertion cursor.\n", " 5. 'Delete' deletes the selection if there is one; otherwise it deletes \n", " the character just after the insertion cursor.\n", " 6. Use left and right arrow keys to position icursor within text."]) spd = SeeDismissPanel(self) # prevent 'Return' event in text object from # propagating to the 'See Code' button event spd.winfo_toplevel().unbind('<Return>') self._create_demo_panel() def _create_demo_panel(self): demoPanel = Frame(self) demoPanel.pack(side=TOP) self.canvas = self._create_canvas(demoPanel) self._create_text() self._text_pos_grid() self._text_justify_grid() self._add_canvas_bindings() # ============================================================================== # Canvas and objects # ============================================================================== def _create_canvas(self, parent): canvas = Canvas(relief=FLAT, borderwidth=0, width=500, height=350) # grid fill colours canvas.posBoxFill = 'LightSkyBlue1' canvas.justifyBoxFill = 'SeaGreen2' canvas.selBoxFill = 'black' canvas.anchorBoxFill = 'red' canvas.pack(in_=parent, side=TOP, expand=Y, fill=BOTH) return canvas def _create_text(self): # create editable text, red square shows text anchor position rect = self.canvas.create_rectangle(245, 195, 255, 205, outline='black', fill=self.canvas.anchorBoxFill) txtStr = "This is just a string of text to demonstrate the text " \ "facilities of canvas widgets. Bindings have been been " \ "defined to support editing (see above)." txt = self.canvas.create_text(250, 200, text=txtStr, width=440, anchor=N, font=('Helv', 18), justify=LEFT, tag=('text', ) ) self.canvas.etext = txt # save id of editable text item def _text_pos_grid(self): # set up text position control grid # the posTags are the reverse of the 'row' placements # as they refer to the position of the ANCHOR; the text # is moved to place the anchor in the chosen position posTags = (('se', 's', 'sw'), ('e','c','w'), ('ne','n','nw')) x0, y0 = 50, 50 # start position size = 30 # size of square # draw grid for r in range(3): y = y0 + r * size tags = posTags[r] for c in range(3): x = x0 + c * size item = self.canvas.create_rectangle(x, y, x+size, y+size, outline='black', fill=self.canvas.posBoxFill, tag=('posBox', tags[c], )) # add small red rect to represent the text anchor rect = self.canvas.create_rectangle(x0+40, y0+40, x0+50, y0+50, outline='black', fill=self.canvas.anchorBoxFill, tag=('anchorBox', 'c')) # label for text position control grid self.canvas.create_text(x0+45, y0-5, text='Text Position', anchor=S, font=('Times', 18), fill='brown') def _text_justify_grid(self): # set up text justification grid jtags = ('left', 'center', 'right') x0, y0 = 350, 50 # start position size = 30 # size of square # draw the grid for c in range(3): x = x0 + c * size item = self.canvas.create_rectangle(x, y0, x+size, y0+size, outline='black', fill=self.canvas.justifyBoxFill, tag=('jbox', jtags[c] )) # add label for grid self.canvas.create_text(x0+45, y0-5, text='Justification', anchor=S, font=('Times', 18), fill='brown') # ============================================================================== # Canvas Bindings # ============================================================================== def _add_canvas_bindings(self): self.canvas.tag_bind('posBox', '<Any-Enter>', self._enter_box) self.canvas.tag_bind('posBox','<Any-Leave>', lambda evt: self._leave_box(evt, self.canvas.posBoxFill)) self.canvas.tag_bind('posBox', '<1>', lambda evt: self._position_etext(evt, 'anchor')) self.canvas.tag_bind('anchorBox','<Any-Leave>', lambda evt: self._leave_box(evt, self.canvas.anchorBoxFill)) self.canvas.tag_bind('anchorBox', '<1>', lambda evt: self._position_etext(evt,'anchor')) self.canvas.tag_bind('jbox', '<Any-Enter>', self._enter_box) self.canvas.tag_bind('jbox','<Any-Leave>', lambda evt: self._leave_box(evt, self.canvas.justifyBoxFill)) self.canvas.tag_bind('jbox', '<1>', lambda evt: self._position_etext(evt, 'justify')) # following bindings handle 'text' edits self.canvas.tag_bind('text', '<1>', self._text_set_cursor) self.canvas.tag_bind('text', '<B1-Motion>', self._text_sel) self.canvas.tag_bind('text', '<Delete>', self._text_delete) self.canvas.tag_bind('text', '<KeyPress>', self._text_insert) self.canvas.tag_bind('text', '<BackSpace>', self._text_bs) self.canvas.tag_bind('text', '<Left>', self._text_left) self.canvas.tag_bind('text', '<Right>', self._text_right) self.canvas.tag_bind('text', '<3>', self._text_paste) # ============================================================================== # Bound methods # ============================================================================== def _enter_box(self, evt): self.canvas.itemconfigure(CURRENT, fill=self.canvas.selBoxFill) def _leave_box(self, evt, color): self.canvas.itemconfigure(CURRENT, fill=color) def _position_etext(self, evt, option): tags = self.canvas.gettags(CURRENT) for t in tags: if t not in ('posBox', 'jbox', 'anchorBox', 'current'): d = {option: t} # option can be 'justify' or 'anchor' self.canvas.itemconfigure(self.canvas.etext, d) # following methods handle text edits def _text_set_cursor(self, evt): pos = '@{},{}'.format(evt.x, evt.y) self.canvas.icursor(CURRENT, pos) self.canvas.focus(CURRENT) self.canvas.focus_set() self.canvas.select_clear() self.canvas.select_from(CURRENT, pos ) def _text_sel(self, evt): self.canvas.select_to(CURRENT, '@{},{}'.format(evt.x, evt.y)) def _text_delete(self, evt): if self.canvas.select_item(): self.canvas.dchars(CURRENT, 'sel.first', 'sel.last') else: self.canvas.dchars(CURRENT, 'insert') def _text_insert(self, evt): self.canvas.insert(CURRENT, 'insert', evt.char) def _text_bs(self, evt): if self.canvas.select_item(): self.canvas.dchars(CURRENT, 'sel.first', 'sel.last') else: self.canvas.dchars(CURRENT, self.canvas.index(CURRENT, 'insert' )-1 ) def _text_left(self, evt): self.canvas.icursor(CURRENT, self.canvas.index(CURRENT, 'insert') - 1) self.canvas.selection_clear() def _text_right(self, evt): self.canvas.icursor(CURRENT, self.canvas.index(CURRENT, 'insert') + 1) self.canvas.selection_clear() def _text_paste(self, evt): pos = '@{},{}'.format(evt.x, evt.y) try: self.canvas.insert(CURRENT, pos, self.canvas.selection_get()) except TclError: # ignore, no selection to paste pass if __name__ == '__main__': TextCanvasDemo().mainloop()
Saturday, August 11, 2012
Tkinter Canvas with Text Demo
This code is based on the Tcl ctext.tcl demo. It presents a text item that may be repositioned, re-justified, and/or edited.
Labels:
Tkinter Canvas,
Tkinter Demos