Download lesson code from GitLab: https://gitlab.com/PythonRu/tkinter-uroki
The previous tutorials focused on the standard Tkinter widget. However, the Canvas widget has been left out of the spotlight. The reason is that it provides a lot of graphical possibilities and deserves a separate consideration. Canvas is a rectangular area in which not only text or geometric shapes such as lines, rectangles or ovals can be displayed, but also other Tkinter widgets. All nested objects are called Canvas elements, and each has its own identifier with which they can be manipulated even before they are displayed. Let’s look at methods of the Canvas
class using real-world examples, which will help familiarize you with common patterns that will help you create applications in the future.
Table of Contents
Understanding the coordinate system
To draw graphical elements on a canvas, you need to denote their position using a coordinate system. Since Canvas is a two-dimensional area, points will be denoted by horizontal and vertical axis coordinates – the traditional x and y, respectively. Using the example of a simple application, it is easy to depict exactly how to position these points in relation to the base of the coordinate system, which is in the upper left corner of the canvas area. The following program contains an empty canvas as well as a marker that shows the position of the cursor on it. You can move the cursor and see what position it is in. This clearly shows how the x and y coordinates change depending on the position of the cursor:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Basic canvas")
self.canvas = tk.Canvas(self, bg="white")
self.label = tk.Label(self)
self.canvas.bind("", self.mouse_motion)
self.canvas.pack()
self.label.pack()
def mouse_motion(self, event):
x, y = event.x, event.y
text = "cursor position: ({}, {})".format(x, y)
self.label.config(text=text)
if __name__ == "__main__":
app = App()
app.mainloop()
How the coordinate system works
A Canvas
instance is created similar to any other Tkinter widget. It receives the parent container as well as all the settings in the form of keywords:
def __init__(self):
# ...
self.canvas = tk.Canvas(self, bg="white")
self.label = tk.Label(self)
self.canvas.bind("", self.mouse_motion)
The following screenshot shows a point made up of perpendicular projections of two axes:
- The x coordinate corresponds to the distance on the horizontal axis and increases as you move from left to right;
- The y coordinate corresponds to the distance on the vertical axis and increases as you move from bottom to top;

You may notice that these coordinates correspond exactly to the x
and y
attributes of the event
instance that was passed to the handler:
def mouse_motion(self, event):
x, y = event.x, event.y
text = "cursor position: ({}, {})".format(x, y)
self.label.config(text=text)
<Motion>
sequence.Canvas Square is also capable of displaying elements with negative values of their coordinates. Depending on the size of the element, it may be partially visible at the left or top border of the canvas. Similarly, if you place an element so that its coordinates lie outside the canvas, part of it will be visible at the right and bottom edges.
Drawing lines and arrows
One of the basic things you can do on the canvas is to draw segments from one point to another. Although there are other ways to draw polygons, the create_line
method of the Canvas
class offers enough options to understand the basics of displaying elements. In this example, let’s create an application that allows you to draw lines by clicking on the canvas. Each of these will be displayed after two clicks: the first will indicate the beginning of the line and the second will indicate the end of the line. It will also be possible to set certain appearance elements, such as thickness and color:

The App
class will be responsible for creating an empty canvas and handling mouse clicks. Line information will come from the LineForm
class. This approach of separating the component into a separate class will allow to abstract the details of its implementation and focus on working with the Canvas widget. To put it simply, we bypass the LineForm
implementation in the following code:
import tkinter as tk
class LineForm(tk.LabelFrame):
# ...
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Basic canvas")
self.line_start = None
self.form = LineForm(self)
self.canvas = tk.Canvas(self, bg="white")
self.canvas.bind("", self.draw)
self.form.pack(side=tk.LEFT, padx=10, pady=10)
self.canvas.pack(side=tk.LEFT)
def draw(self, event):
x, y = event.x, event.y
if not self.line_start:
self.line_start = (x, y)
else:
x_origin, y_origin = self.line_start
self.line_start = None
line = (x_origin, y_origin, x, y)
arrow = self.form.get_arrow()
color = self.form.get_color()
width = self.form.get_width()
self.canvas.create_line(*line, arrow=arrow,
fill=color, width=width)
if __name__ == "__main__":
app = App()
app.mainloop()
The entire code can be found in the separate file lesson_18/drawing.py.
How to draw lines in Tkinter
Since we want to handle mouse clicks on the canvas, we associate the draw()
method with this event type. We also define a line_start
field to keep track of the start position of each line:
def __init__(self):
# ...
self.line_start = None
self.form = LineForm(self)
self.canvas = tk.Canvas(self, bg="white")
self.canvas.bind("", self.draw)
The draw()
method contains the basic logic of the application. The first click serves to determine the start for each line and does not draw anything. It gets the coordinates from the event
object, which is passed to the handler:
def draw(self, event):
x, y = event.x, event.y
if not self.line_start:
self.line_start = (x, y)
else:
# ...
If line_start
already has a value, we get it and pass the coordinates of the current event to draw the line:
def draw(self, event):
x, y = event.x, event.y
if not self.line_start:
# ...
else:
x_origin, y_origin = self.line_start
self.line_start = None
line = (x_origin, y_origin, x, y)
self.canvas.create_line(*line)
text = "The line is drawn from ({}, {}) to ({}, {})".format(*line)
The canvas.create_line()
method takes four arguments, where the first two are the horizontal and vertical coordinates of the start of the line and the second two are its endpoint.
How to get text to appear on canvas
In some cases it is necessary to display text on canvas. There is no need to use an extra widget for this, like the Label. The Canvas
class includes a create_text
method to display a string that can be manipulated just like any other item on the canvas. It is possible to use the same formatting parameters, which will allow you to set the text style: color, size and font family. In this example, we’ll merge the Entry widget with the content of the canvas text element. And if the former has a standard style, the text on the canvas can be styled:

The default text element will display with canvas.create_text()
and additional parameters to add the Consolas font family and blue color. The dynamic behavior of the text item is implemented with StringVar
. By tracking this Tkinter variable, you can change the content of the element:
import tkinter as tk
class App(tk.tk):
def __init__(self):
super().__init__()
self.title("Canvas text elements")
self.geometry("300x100")
self.var = tk.StringVar()
self.entry = tk.Entry(self, textvariable=self.var)
self.canvas = tk.Canvas(self, bg="white")
self.entry.pack(pady=5)
self.canvas.pack()
self.update()
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
options = {"font": "courier", "fill": "blue",
" activefill": "red"}
self.text_id = self.canvas.create_text((w / 2, h / 2), **options)
self.var.trace("w", self.write_text)
def write_text(self, *args):
self.canvas.itemconfig(self.text_id, text=self.var.get())
if __name__ == "__main__":
app = App()
app.mainloop()
You can familiarize yourself with this program by entering any text in the input box, which will automatically update it on the canvas.
How text output on the canvas works
First, an Entry instance is created with the StringVar
variable and the Canvas widget:
self.var = tk.StringVar()
self.entry = tk.Entry(self, textvariable=self.var)
self.canvas = tk.Canvas(self, bg="white")
The widget is then placed using geometry manager Pack method calls. It is important to note that update()
has to be called in the root window, thanks to which Tkinter will have to handle all changes, in this case rendering the widgets before the __init__
method will continue execution:
self.entry.pack(pady=5)
self.canvas.pack()
self.update()
This is done because the next step will calculate the web dimensions, and until the geometry manager places the widget, it won’t have any real height and width values. After that you can safely get the dimensions of the web. Since the text needs to be aligned to the center of the web, it is sufficient to divide the width and length values in half. These coordinates will determine the position of the element, and together with the style parameters they must be passed to the create_text()
method. The argument-keyword text
is a standard parameter, but you can skip it because it will be set dynamically when you change the value of StringVar
:
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
options = { "font": "courier", "fill": "blue",
" activefill": "red" }
self.text_id = self.canvas.create_text((w/2, h/2), **options)
self.var.trace("w", self.write_text)
The identifier that create_text()
returns will be stored in the text_id
field. It will be used in the write_text()
method to reference the element. And this method will be called due to the mechanism for tracking the write operation in the var
instance. To update the text
parameter in the write_text()
handler, the canvas.itemconfig()
method is called with the item ID as the first argument and the setting as the second argument. In this program, we use the field_id
field stored when the App
instance was created and the contents of the StringVar
using the get()
method: The write_text()
method is defined so that it can get a variable number of arguments, although they are not needed because the trace()
method of Tkinter variables passes them to the callback function. The canvas.create_text()
method has many other parameters for changing the appearance of canvas elements.
Placement of text in the upper left corner
The anchor
parameter allows you to control the position of the element relative to the coordinates passed as the first argument to canvas.create_text()
. The default value is tk.CENTER
, which means the text will be centered in those coordinates. If you want to place it in the upper left corner, however, just pass (0, 0)
and set tk.NW
to anchor
, which aligns it to the northwest position of the rectangular area where the text is located:
# ...
options = { "font": "courier", "fill": "blue",
"activefill": "red", "anchor": tk.NW }
self.text_id = self.canvas.create_text((0, 0), **options)
This code will provide that result:

Line Break
By default, the content of the text item will be rendered as a single line. The parameter width
, on the other hand, allows you to set the maximum width of the line. If it is larger, the content will be moved to the new line:
# ...
options = { "font": "courier", "fill": "blue",
"activefill": "red", "width": 70 }
self.text_id = self.canvas.create_text((w/2, h/2), **options)
Now if you write Hello World
, some of the text will go beyond the specified width and will be moved to a new line:
