Additional material – More examples on tkinter
Although Tkinter is well suited for drawing graphs, a different coordinate system causes some sort of discomfort – we are used that y increases upwards (not downwards). To get rid of this problem, we can use move method which allows to move canvas objects horizontally and vertically. Therefore, we place all objects in a regular coordinate system and then use the move method.
1. Graph of a function
The following program plots the graph of y = sin(x):
from tkinter import * from math import sin window = Tk() w = 500 # canvas width h = 500 # canvas height area = Canvas(window, width=w, height=h, background="white") area.grid() # vertical axis area.create_line(0, h/2, 0, -h/2, arrow=LAST) # horisontal axis area.create_line(-w/2, 0, w/2, 0, arrow=LAST) points = [] # generate graph points in the form [x0,f(x0), x1,f(x1),..., xn, f(xn)] for x in range(w // -2, w // 2): increase = 30 points.append(x) y = sin(x * 1.0 / increase) points.append(y * increase) # draw a graph (using a list with points as an argument) area.create_line(points, fill="red") # shift all objects to the right and down (250px) area.move(ALL, w/2, h/2) window.mainloop()
2. External images
We can also add a .gif, .pgm, or .ppm image on the canvas.
Save the following files into the same folder as the program: ball.gif, opened.gif, closed.gif and run the program:
from tkinter import * window = Tk() window.title("Images") area = Canvas(window, width=600, height=600, background="white") area.grid() # first, download the image and place it in the canvas ball = PhotoImage(file="ball.gif") img = area.create_image(450, 80, image=ball) # activeimage specifies the image that is displayed when the mouse cursor is on the image # anchor shows how the image is placed (in this example, in the upper left corner) closed = PhotoImage(file="closed.gif") opened = PhotoImage(file="opened.gif") img = area.create_image(50, 400, image=closed, activeimage=opened, anchor=NW) window.mainloop()
3. Mouse clicks
In the following program, each mouse click invokes a function that registers mouse clicks:
from tkinter import * def register_mouse_click(event): print("The click was made on the position:", event.x, event.y) clicks.append([event.x, event.y]) print("All clicks made:", clicks) window = Tk() window.title("Mouse clicks") area = Canvas(window, width=600, height=600, background="white") area.grid() # <1> indicates the left mouse button area.bind('<1>', register_mouse_click) clicks = [] window.mainloop()
4. Updating the canvas
Update the content of the canvas according to the user's activities
Save the image juku.gif to the same folder.
from tkinter import * from random import randint # some additional constants juku_move_length = 50 area_width = 600 area_height = 600 # functions that are invoked according to the user activity def mouse_click_on_juku(event): # move Juku to a random position new_x = randint(0, area_width-50) new_y = randint(0, area_height-50) area.coords(juku_id, new_x, new_y) def key_up(event): area.move(juku_id, 0, -juku_move_length) def key_down(event): area.move(juku_id, 0, juku_move_length) def key_left(event): area.move(juku_id, -juku_move_length, 0) def key_right(event): area.move(juku_id, juku_move_length, 0) # window and canvas window = Tk() window.title("Juku") area = Canvas(window, width=area_width, height=area_height, background="white") area.grid() # image juku = PhotoImage(file="juku.gif") # create a variable juku_id for the image juku_id = area.create_image(100, 100, image=juku) # the corresponding function is associated with the mouse clicks on image using the image id # <1> indicates the left mouse button area.tag_bind(juku_id, '<1>', mouse_click_on_juku) # bind the keys with the corresponding functions window.bind_all("<Up>", key_up) window.bind_all("<Down>", key_down) window.bind_all("<Left>", key_left) window.bind_all("<Right>", key_right) window.mainloop()
This program is an example of how to automatically change the position of an image if the image is clicked.
5. Changing the image
Let's now look at a more general way how to change the content of an image (for example, on mouse click). Save the following files into the same folder as the program opened.gif, closed.gif and run the program.
from tkinter import * def change_image(event): # global declaration allows to change the variable defined outside the function global shown_images # change the image if shown_images == closed: shown_images = opened else: shown_images = closed # update the image in the canvas area.itemconfigure(image_id, image=shown_images) window = Tk() window.title("Images") area = Canvas(window, width=600, height=600, background="white") area.grid() closed = PhotoImage(file="closed.gif") opened = PhotoImage(file="opened.gif") shown_images = closed image_id = area.create_image(200, 200, image=shown_images, anchor=NW) area.tag_bind(image_id, '<1>', change_image) window.mainloop()
6. Currency converter
One more example – currency converter:
from tkinter import * eur_usd_rate = 1.1888 #prepare the functions that is run when something is typed in the corresponding box def calculate_euros_to_usd(event): calculate(eur_text, eur_result, eur_usd_rate, "USD") def calculate_usd_to_euros(event): calculate(usd_text, usd_result, 1.0 / eur_usd_rate, "EUR") def calculate(fromc, to, rate, result_symbol): text = fromc.get() try: ssum = round(float(text),2) to['text'] = str(round((ssum * rate),2))+" "+result_symbol except: #if the float conversion fails, the program will arrive here to['text'] = "???" #window window = Tk() window.title("Currency converter") #LabelFrame makes bounds and a title area first_part = LabelFrame(window, text=" EUR -> USD ") first_part.grid(row=0, column=0, padx=10, pady=8) #the input widget and the result widget go to LabelFrame eur_text = Entry(first_part, width=10) eur_text.grid(row=0, column=0, padx=5, pady=5) #put the function made above to respond to the upgrade key eur_text.bind("<KeyRelease>", calculate_euros_to_usd) eur_result = Label(first_part, text="", width=15) eur_result.grid(row=0, column=1, padx=5, pady=5) #the same story with the second part second_part = LabelFrame(window, text=" USD -> EUR ") second_part.grid(row=1, column=0, padx=10, pady=(0,8)) usd_text = Entry(second_part, width=10) usd_text.grid(row=0, column=0, padx=5, pady=5) usd_text.bind("<KeyRelease>", calculate_usd_to_euros) usd_result = Label(second_part, text="", width=15) usd_result.grid(row=0, column=1, padx=5, pady=5) window.mainloop()
7. Tic-tac-toe
A variant of the tic-tac-toe game was introduced in week 9.
Save the following images for the game into the same folder as the program: empty.gif, x.gif, o.gif and run the program.
from tkinter import * from tkinter import messagebox # function which checks if the elements in the list are the same or not def same_three(llist): if llist[0] == llist[1] == llist[2] and(llist[0] == 'O' or llist[0] == 'X'): return True return False # function which creates lists and looks for the winner def control(mx): for row in mx: if same_three(row): return row[0] for i in range(len(mx[0])): column = [] for row in mx: column.append(row[i]) if same_three(column): return column[0] diagonal = [] diagonal2 = [] for i in range(len(mx)): for j in range(len(mx[i])): if i == j: diagonal.append(mx[i][j]) if i == len(mx[i]) - j - 1: diagonal2.append(mx[i][j]) if same_three(diagonal): return diagonal[0] if same_three(diagonal2): return diagonal2[0] return "?" # function which checks if the game ends with a draw def draw_control(mx): draw = True for i in range(3): for j in range(3): if mx[i][j] == " ": draw = False return draw # function for starting a new game (create empty images and an empty matrix) def new_game(): # global variables can be changed outside the function global turn global sol_mx global images_mx images_mx = [] sol_mx = [] turn = "X" # in the following loop, create 9 images and place them in the canvas (3x3 grid) # in addition, save the image IDs to a 3x3 matrix and create an empty 3x3 solution matrix for i in range (3): images_row = [] row = [] for j in range(3): # calculate the coordinates of the image according to the column and row numbers xc = 50 + j * 105 yc = 50 + i * 105 image_id = area.create_image(xc, yc, image=empty) # the corresponding function is associated with mouse clicks on images using image id area.tag_bind(image_id, '<1>', mouse_click) # save the image to the appropriate place in the list images_row.append(image_id) row.append(" ") # one line is ready, add it to the matrix images_mx.append(images_row) sol_mx.append(row) # this function is executed when the image is clicked on def mouse_click(event): global turn global sol_mx global images_mx # the id of the object that was clicking on image_id = area.find_withtag(CURRENT)[0] # look at the images_mx matrix to find out where the image with that id is for i in range(3): for j in range(3): if image_id == images_mx[i][j]: if sol_mx[i][j] == " " and turn == "X": turn = "O" # change the turn sol_mx[i][j] = "X" # update the solution matrix # update the image in the canvas area.itemconfigure(images_mx[i][j], image=x) elif sol_mx[i][j] == " " and turn == "O": turn = "X" # change the turn sol_mx[i][j] = "O" # update the solution matrix # update the image in the canvas area.itemconfigure(images_mx[i][j], image=o) # check if there is a winner if control(sol_mx) != "?": winner = 'Winner ' + control(sol_mx) messagebox.showinfo(message=winner) print(winner) # we can output to the console as well new_game() # starting the new game # check if it is a draw if draw_control(sol_mx): winner = 'It is a draw' messagebox.showinfo(message=winner) print(winner) new_game() # starting the new game # window and canvas window = Tk() window.title("TicTacToe") area = Canvas(window, width=310, height=310, background="black") area.grid() # load the images empty = PhotoImage(file="empty.gif") x = PhotoImage(file="x.gif") o = PhotoImage(file="o.gif") # start the game new_game() window.mainloop()
The tic-tac-toe example demonstrates how to put a number of images on a canvas using the loop. To allow the images to be accessed later, image IDs are saved in the matrix.