© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
A. ElumalaiIntroduction to Python for Kids https://doi.org/10.1007/978-1-4842-6812-4_18

18. Project: Paint App with Tkinter

Aarthi Elumalai1  
(1)
Chennai, Tamil Nadu, India
 

In the previous chapter, we learned how to create a tic-tac-toe app with Tkinter . We also learned all about events and how to use them to make our app respond to external events (mouse click, keyboard key press, etc.).

In this chapter, we’ll learn all about “drawing” on your Tkinter screen using “canvas” and use that to make a paint app. You’ll be able to draw with a pen and draw circles/ovals, straight lines, and squares/rectangles. You’ll also be able to change the size of your pen and your shapes’ outline colors and fill colors. It’s a simple, but complete app!

Paint app – explanation

../images/505805_1_En_18_Chapter/505805_1_En_18_Figa_HTML.jpg

Our paint app is going to be awesome! You’re going to be able to do free-hand drawing and draw straight lines, squares, rectangles, ovals, and circles. You can also choose from hundreds of different color shades. Cool right?

Once we’re done, it’ll look something like Figure 18-1.
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig1_HTML.jpg
Figure 18-1

Final app

I’m no artist, so please forgive my basic drawings, but you can see how powerful this app is, right? And the best part is that this is just the starting point. You can expand this app, add more features, and make it into anything you want.

Share it with your friends, have paint competitions, or just have fun! ../images/505805_1_En_18_Chapter/505805_1_En_18_Figc_HTML.gif

Get started

Let’s start by importing Tkinter. Let’s import everything, as usual, but doing so will only import the “outer” classes. It won’t import the inner ones, like the colorchooser, for example. We need the color chooser to create color palettes for our app. So, let’s import that as well.
from tkinter import *
from tkinter import colorchooser

Now, let’s create and initialize our variables. To draw on the screen, you need coordinates, the x and y points of where your mouse pointer is clicking on the screen. Let’s create x and y variables and assign them 0 each to start with.

I’ve used a new way of assignment now. Makes things easy, doesn’t it?
x, y = 0,0
Next, let’s create a variable “color” and make it None (no value) to start with. You can make it an empty string as well. This variable will hold our shapes’ fill colors in the future. We also need a color for our “pen” or our shapes’ outline, so let’s create a variable “outline” and make it black as default. We also need a pen size. It’s going to be 1 by default.
color = None
outline = 'black'
sizeVal = 1

Set up the screen

Now, let’s set up our screen. We’re going to make the state of our screen “zoomed” by default, so it expands to the full screen. Also, we’re going to configure our rows and columns in such a way that the first cell (row 0 and column 0) is going to expand to the full width and height of the screen. We can place our canvas inside this cell, so it expands to the full screen as well.
w = Tk()
w.title('Paint Application')
w.state('zoomed')
w.rowconfigure(0,weight=1)
w.columnconfigure(0,weight=1)

We’ve given the weight as 1 to let our program know that this particular row and column should expand to its maximum capacity.

Let’s run our program, and we get this (Figure 18-2).
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig2_HTML.jpg
Figure 18-2

Our Tkinter screen

Great!

Create the canvas

Now, let’s create our canvas. We need to use the Canvas method to do that and place it in the window “w”. Let’s also make our canvas’ background “white” by default.
#create a canvas
canvas = Canvas(w, background="white")
Next, I’m going to place my canvas in the first row and column (0) and make it sticky in all directions (north, south, east, and west) so it expands in all directions and takes up the entire space (which is our entire screen as of now).
canvas.grid(row=0,column=0,sticky="NSEW")
Let’s run the program now, and we get this (Figure 18-3).
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig3_HTML.jpg
Figure 18-3

Canvas

Perfect! We have our white canvas now.

Create your first menu (shapes)

If you looked at the completed app, you’d have noticed that we had multiple menus to choose from. The first one is the shape menu. You’ll be able to choose between drawing with a pen and drawing a line, square, or circle. Let’s create that menu now.

You already know how to create menus. Let’s create a main menu that’ll hold all our menus. Our “Draw Options” menu is going to be the first submenu in our “main” menu. Let’s add a cascade to it and label it.
main = Menu(w)
menu1 = Menu(main)
main.add_cascade(label='Draw Options',menu = menu1)
Finally, let’s add four commands, ‘Pen’, ‘Line’, ‘Square’ and ‘Circle’. But we need to send the selection values to the “select” function, which will in turn call the relevant function that’ll do the respective drawing. Let’s use a lambda to do that. We’re going to number our options, pen is 1, line is 2, square is 3, and circle is 4.
menu1.add_command(label='Pen', command=lambda: select(1))
menu1.add_command(label='Line', command=lambda: select(2))
menu1.add_command(label='Square', command=lambda: select(3))
menu1.add_command(label='Circle', command=lambda: select(4))
Finally, let’s configure our “main” menu to our window. In the future, this line should come after we’ve created all four of our menus.
w.config(menu=main)
If you run your program now, and try clicking the menu items, you’ll get an error because your “select” function isn’t defined yet, but still, you’ll see your menu, like this (Figure 18-4).
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig4_HTML.jpg
Figure 18-4

First menu (draw options)

Whoa! First step is a success! ../images/505805_1_En_18_Chapter/505805_1_En_18_Figd_HTML.gif

Let’s make our draw options work!

Now that we have our draw options menu, let’s make it work. Let’s first create the “select” function that binds the canvas with the relevant mouse clicks. Create this function above the menus (function calls). We need two kinds of binds.

For the free-hand drawing, we need a <B1-Motion> bind that draws a line every time our left mouse button clicks and drags on the screen. So, we’ll essentially get tiny lines between every 2 minute points, so essentially hundreds of tiny lines that join are joined together to make our free-hand drawing.

Then, we need a <ButtonRelease-1> bind that draws either a line, square, or circle whenever our left mouse button releases after it clicks and drags on the screen. So, the result would be a line, square, or circle from the point where it clicked to the point where it released.

Let’s do that now. Let’s receive our number as “options”. If options is 1, then unbind <ButtonRelease-1>, so if we’d previously selected the other options, it’ll be unselected now, and we won’t get a shape or line after we release the pen. Then, let’s bind <B1-Motion> and call the draw_line function.
def select(options):
    if options == 1:
        #selected Pen, create bind
        canvas.unbind("<ButtonRelease-1>")
        canvas.bind('<B1-Motion>',draw_line)
Similarly, for 2, unbind <B1-Motion> so the pen is no longer active and bind the <ButtonRelease-1> and call the draw_line function.
if options == 2:
    #selected line, create bind
    canvas.unbind("<B1-Motion>") #so pen is no longer active
    canvas.bind('<ButtonRelease-1>',draw_line)
For 3, call the draw_square function.
elif options == 3:
    #selected square, create bind
    canvas.unbind("<B1-Motion>")
    canvas.bind('<ButtonRelease-1>',draw_square)
For 4, call the draw_circle function.
elif options == 4:
    #selected circle, create bind
    canvas.unbind("<B1-Motion>")
    canvas.bind('<ButtonRelease-1>',draw_circle)

Get the mouse position

Before we create the draw_line functions, we need to get our mouse position. We can do that using our “event”, as you know. So, let’s create another bind outside of our functions (right above our menus and below the function definitions) that binds any left mouse button click to the canvas.

So, every time your user clicks the canvas, we’re going to make note of the x and y positions of the same in the background.

We won’t draw anything until the user selects a draw option, but let’s still make note in anticipation of that, alright?
canvas.bind('<Button-1>',position)

Now, define the function above the bind. Receive “event” in the function definition. Let’s also load the global x and y values and assign the event.x and event.y values (x and y coordinate positions of the mouse click) to the x and y global variables.

Get the current position of the mouse on each left mouse button click on the canvas.
def position(event):
    global x,y
    x,y = event.x,event.y

That’s it! You could print out x and y and see this function in action. Let’s make that our little activity, shall we? ../images/505805_1_En_18_Chapter/505805_1_En_18_Fige_HTML.gif

Let’s draw our lines

Now, let’s create the function that’ll draw both our mini lines for our free-hand drawing and our straight lines. What do we need here?

There’s a create_line function in canvas which can be used to, yup, you guessed it, draw straight lines! You just need to give the start and end coordinate points. You can also specify the “fill”, which is essentially the line’s color.

We’ll be using the “outline” color for this because we want line colors and shape outline colors to be uniform. You can also specify the width of the line. Let’s give sizeVal as the value for this property.

You need to be careful how you mention the coordinate values though. Mention the x and y coordinates of the starting point first and then the x and y coordinates of the ending point. More importantly, mention all four values inside of a tuple, or you’ll get an error.
def draw_line(event):
Let’s load our x and y values, which is the point where the mouse first clicked, which we calculate constantly using the position() function. Let’s also load sizeVal, which is currently at 1. It’ll automatically get updated once we write the lines of code that’ll let the user manually change the width of the lines.
global x,y,sizeVal

Now, the starting x and y positions are the x and y positions that contain the point where the mouse clicked (the position() function). The ending x and y positions are the event’s x and y positions.

In case of a free-hand drawing, every time the mouse is dragged (while the left mouse button is still pressed down), we get a new event, and new x and y positions, for every minute change.

For drawing a straight line, the end point is when the mouse button is released.
canvas.create_line((x,y,event.x,event.y),fill=outline, width = sizeVal)
Finally, let’s update the x and y values with the event’s x and y values. We especially need this for the free-hand drawing, so we can start over.
x,y = event.x,event.y

Let’s run our program now.

When we try to draw on the screen as such, nothing happens. Why? Well, we haven’t activated any of the options yet. But, if I select either pen or line (from the menu), I can draw on the canvas (Figure 18-5).
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig5_HTML.jpg
Figure 18-5

Free hand and straight lines

Squares and rectangles!

Let’s draw our squares and rectangles now. The process is similar. You have a create_rectangle method in canvas. Give the start and end coordinates within a tuple again. In this case, you can mention two kinds of colors, outline and fill colors, and finally the width of the shape.

Then, let’s assign the current event’s x and y values (mouse release) to the first x and y values (left mouse click).
def draw_square(event):
    global x,y,sizeVal
    canvas.create_rectangle((x,y,event.x,event.y), outline=outline, fill=color, width = sizeVal)
    x,y = event.x,event.y

That’s it! Let’s run our program now. Select “Square”, hold your mouse button down, drag it to the point you want, and release the button. You’ll get yourself a square or a rectangle. Try and see! ../images/505805_1_En_18_Chapter/505805_1_En_18_Figf_HTML.gif

This is what I did (Figure 18-6). :P
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig6_HTML.jpg
Figure 18-6

Squares and rectangles

Beautiful squares and rectangles! ../images/505805_1_En_18_Chapter/505805_1_En_18_Figg_HTML.gif

Circles and ovals!

Finally, let’s draw circles and ovals. There’s another method called create_oval. A perfectly formed oval is a circle, am I right? You need to give the start and end points for this method as well.

Your start point is when you pressed the mouse button, and the end point is the x and y value of the point where you finally released the mouse button (mouse release event).
def draw_circle(event):
    global x,y,sizeVal
    canvas.create_oval((x,y,event.x,event.y), outline=outline, fill=color, width= sizeVal)
    x,y = event.x,event.y
Let’s run the program, and we get this (Figure 18-7).
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig7_HTML.jpg
Figure 18-7

Circles

Nice! We’ve finished all our draw functions. We’re almost there! ../images/505805_1_En_18_Chapter/505805_1_En_18_Figh_HTML.gif

Select size!

Now, let’s move on to the second menu in our program. So far, our lines and the outlines of our shapes are too narrow in width. What if we want them to be thicker? We need options for that as well. Let’s create them! I’m going to create sizes from 1, 5, 10, to 30. 1 is the default we’ve set.

Let’s create a new submenu, menu2, for the sizes. Place this after the menu1’s code, but before the menu configuration line of code. Every option is going to be a size, and I’m going to call the changeSize function for every option click. We’ll be sending the size as the parameter to this function.
menu2 = Menu(main)
main.add_cascade(label='Select Size', menu = menu2)
menu2.add_command(label='1', command=lambda: changeSize(1))
menu2.add_command(label='5', command=lambda: changeSize(5))
menu2.add_command(label='10', command=lambda: changeSize(10))
menu2.add_command(label='15', command=lambda: changeSize(15))
menu2.add_command(label='20', command=lambda: changeSize(20))
menu2.add_command(label='25', command=lambda: changeSize(25))
menu2.add_command(label='30', command=lambda: changeSize(30))

Now, define the function to change the size. You can place this function after the select() function, or anywhere you want, as long as it’s above menu2’s lines of code (function calls).

This is a very simple process. Let’s receive our size, load the global sizeVal, and assign our size to sizeVal. That’s it! Since sizeVal is global and loaded into all our draw functions, once we change the size, the next time we draw something, the new size will reflect in that drawing.
def changeSize(size):
    global sizeVal
    sizeVal = size
Let’s check if this works! I’m going to draw a bunch of lines after changing the size to 15 (Figure 18-8).
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig8_HTML.jpg
Figure 18-8

Change the width of your outlines

Those are some thick lines. :D

Lots and lots of colors!

Now, let’s create the third menu that’ll let us change the outline and fill colors of our drawings.

Let’s create a menu3 that holds just two options, one to change the line color and the other to change the fill color, each calling their respective functions.
menu3 = Menu(main)
main.add_cascade(label = 'Choose Color', menu = menu3)
menu3.add_command(label='Line Color', command = set_line_color)
menu3.add_command(label='Fill Color', command = set_fill_color)
Now, let’s define those functions. We’re going to use our colorchooser to create our color palettes. There’s an askcolor method in colorchooser that opens a palette when we need it (in this case, when the “Line color” option is clicked). This opens in a new window. Let’s also set a title for that window: Choose color.
def set_line_color():
    global outline
    getColor = colorchooser.askcolor(title="Choose color")
Now, you can’t just use getColor as it is. When we choose a color, let’s say red, this is the format in which it gets registered in getColor:
((255.99609375, 0.0, 0.0), '#ff0000')

The first value in the tuple contains another tuple that holds the rgb color value (red, green, and blue shades of our color). The second value in the tuple contains the hexadecimal value of the color we just chose. They’re both the same, and you can just write it as “red”. These are just different formats in which you can mention a color. You don’t really need to know about them or memorize them. Just know that every shade there are hexadecimal and rgb values you can use and your computer recognizes.

Now, we can’t use the entire tuple. We just need one of its values. Let’s just retrieve the second value and use it, shall we?
outline = getColor[1]

Now, every time we change the “Line color”, the value of “outline” changes and it’ll be reflected in our next drawing.

Now, let’s do the same for fill color.
def set_fill_color():
    global color
    getColor = colorchooser.askcolor(title="Choose color")
    color = getColor[1]
That’s it for our colors! Let’s check if it works, shall we? Let’s click “Line color” to see if the color palette opens up (Figure 18-9).
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig9_HTML.jpg
Figure 18-9

Colors!

It works! ../images/505805_1_En_18_Chapter/505805_1_En_18_Figi_HTML.gif

Now, let’s choose our colors (Figure 18-10).
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig10_HTML.jpg
Figure 18-10

Final app, done!

All our colors work perfectly!

I’ve finished drawing!

Okay, we have our little paint app. We’ve drawn to our heart’s content! But what if we want to start over? We need an option to clear the canvas. Let’s create that!

First, the menu.
menu4 = Menu(main)
main.add_cascade(label = 'Clear', menu = menu4)
menu4.add_command(label = 'Clear', command = clear_screen)
Now, the clear_screen() function. We just need a single line of code: canvas.delete(‘all’). This will delete everything on the canvas.
def clear_screen():
    canvas.delete('all')
This is how the option will come up (Figure 18-11).
../images/505805_1_En_18_Chapter/505805_1_En_18_Fig11_HTML.jpg
Figure 18-11

Clear

Draw something and select the clear option to see everything disappear! Take a screenshot before you do though!

We’ve finished our paint app! :O Finally, if you haven’t written it already, write the mainloop line of code, and we’re done.
w.mainloop()

Entire program

Now, the entire program in the order it should be created:
from tkinter import *
from tkinter import colorchooser
x, y = 0,0
color = None
outline = 'black'
sizeVal = 1
w = Tk()
w.title('Paint App')
w.state('zoomed')
w.rowconfigure(0,weight=1)
w.columnconfigure(0,weight=1)
#create a canvas
canvas = Canvas(w, background="white")
canvas.grid(row=0,column=0,sticky="NSEW")
def draw_line(event):
    global x,y,sizeVal
    canvas.create_line((x,y,event.x,event.y),fill=outline, width = sizeVal)
    x,y = event.x,event.y
def draw_square(event):
    global x,y,sizeVal
    canvas.create_rectangle((x,y,event.x,event.y), outline=outline, fill=color, width = sizeVal)
    x,y = event.x,event.y
def draw_circle(event):
    global x,y,sizeVal
    canvas.create_oval((x,y,event.x,event.y), outline=outline, fill=color, width= sizeVal)
    x,y = event.x,event.y
def select(options):
    if options == 1:
        #selected Pen, create bind
        canvas.unbind("<ButtonRelease-1>")
        canvas.bind('<B1-Motion>',draw_line)
    if options == 2:
        #selected line, create bind
        canvas.unbind("<B1-Motion>") #so pen is no longer active
        canvas.bind('<ButtonRelease-1>',draw_line)
    elif options == 3:
        #selected square, create bind
        canvas.unbind("<B1-Motion>")
        canvas.bind('<ButtonRelease-1>',draw_square)
    elif options == 4:
        #selected circle, create bind
        canvas.unbind("<B1-Motion>")
        canvas.bind('<ButtonRelease-1>',draw_circle)
def position(event):
    global x,y
    x,y = event.x,event.y
def changeSize(size):
    global sizeVal
    sizeVal = size
def set_line_color():
    global outline
    getColor = colorchooser.askcolor(title="Choose color")
    outline = getColor[1]
def set_fill_color():
    global color
    getColor = colorchooser.askcolor(title="Choose color")
    color = getColor[1]
def clear_screen():
    canvas.delete('all')
canvas.bind('<Button-1>',position)
#options
main = Menu(w)
menu1 = Menu(main)
main.add_cascade(label='Draw Options',menu = menu1)
menu1.add_command(label='Pen', command=lambda: select(1))
menu1.add_command(label='Line', command=lambda: select(2))
menu1.add_command(label='Square', command=lambda: select(3))
menu1.add_command(label='Circle', command=lambda: select(4))
menu2 = Menu(main)
main.add_cascade(label='Select Size', menu = menu2)
menu2.add_command(label='1', command=lambda: changeSize(1))
menu2.add_command(label='5', command=lambda: changeSize(5))
menu2.add_command(label='10', command=lambda: changeSize(10))
menu2.add_command(label='15', command=lambda: changeSize(15))
menu2.add_command(label='20', command=lambda: changeSize(20))
menu2.add_command(label='25', command=lambda: changeSize(25))
menu2.add_command(label='30', command=lambda: changeSize(30))
menu3 = Menu(main)
main.add_cascade(label = 'Choose Color', menu = menu3)
menu3.add_command(label='Line Color', command = set_line_color)
menu3.add_command(label='Fill Color', command = set_fill_color)
menu4 = Menu(main)
main.add_cascade(label = 'Clear', menu = menu4)
menu4.add_command(label = 'Clear', command = clear_screen)
w.config(menu=main)
w.mainloop()
../images/505805_1_En_18_Chapter/505805_1_En_18_Figb_HTML.jpg

Summary

In this chapter, we learned about “drawing” on our Tkinter screen using “canvas” and used that to make a paint app. We drew with a pen and drew circles/ovals, straight lines, and squares/rectangles. We also changed the size of your pen and our shapes’ outline colors and fill colors.

In the next chapter, let’s go back to our original package, the Turtle package. Let’s create a full-blown snake app with Turtle, scoreboards, and all. It’s going to be a fun and interesting ride. Buckle up!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.248.47