Drawing items on the canvas

Let's now draw some items on the canvas. The Canvas widget natively supports drawing the following items:

Item

Code for adding the item

Arc

w.create_arc( bbox, **options)

Bitmap

w.create_bitmap( bbox, **options)

Image

w.create_image( bbox, **options)

Line

w.create_line( bbox, **options)

Oval

w.create_oval( bbox, **options)

Polygon

w.create_ploygon( bbox, **options)

Rectangle

w.create_rectangle( bbox, **options)

Text

w.create_text( bbox, **options)

Window

w.create_window( bbox, **options)

Let us add the ability to draw lines, rectangles, and ovals to our drawing program. We will also add a brush stroke feature to our program, as shown in the following screenshot:

Drawing items on the canvas

Engage Thrusters

Step 1 – creating a tuple of methods

We first create a tuple of methods that we intend to define here as follows:

all_toolbar_functions = ('draw_line', 'draw_rectangle', 'draw_oval', 'draw_brush')

Doing so ensures that we do not have to call each method explicitly from our code. We can instead use the index of the tuple to retrieve the method name and call it dynamically using:

getattr(self, self.all_toolbar_functions[index])

This makes sense here, because we would eventually add more features to our drawing program by simply extending our all_toolbar_functions.

Step 2 – add icons to our toolbar buttons

Our next task here is to add icons on the left toolbar for drawing these items.

We add the icons to our icons folder. We also ensure to rename each icon file to the name of the method called by it. This naming again helps in calling the methods dynamically, and this style of programming is what you could call programming using conventions over configuration.

Our current create_tool_bar_buttons()method creates eight buttons using a for loop. However, we will now modify our create_tool_bar_buttons()method to use the enumerate() method to loop overall items in our all_toolbar_functions tuple to add icons for each of the methods, as follows (see code 6.03.py):

def create_tool_bar_buttons(self):
	for i, item in enumerate(self.all_toolbar_functions):
		tbicon = PhotoImage(file='icons/'+item+'.gif')
		self.button = Button(self.toolbar, image=tbicon, 					command=lambda i=i:self.selected_tool_bar_item(i))
		self.button.grid(row=i/2, column=1+i%2, sticky='nsew')
		self.button.image = tbicon

Step 3 – keeping a tab on currently selected button

Next, we modify the method, selected_tool_bar_item(i); the only purpose of which is to keep a tab on the currently selected button. Having this information, we can later call the associated method from all_toolbar_functions by using this index, as follows (see code 6.03.py):

def selected_tool_bar_item(self, i):
	self.selected_toolbar_func_index = i

Step 4 – code for drawing line, rectangle, and oval shapes

Now is the time to code the methods to draw these basic shapes. Note that this will not automatically create the drawings. Eventually, these methods will have to be called from somewhere to actually make the drawings. We will do that in step 6.

def draw_line(self, x, y, x2, y2):
	self.currentobject = self.canvas.create_line(x, y, x2, y2, fill= 					self.foreground )
	
def draw_rectangle(self, x, y, x2, y2):
	self.currentobject = self.canvas.create_rectangle(x, y, x2, 						y2, fill= self.foreground)
def draw_oval(self, x, y, x2, y2):
	self.currentobject=  self.canvas.create_oval(x, y, x2, 						y2, fill= self.foreground)

Step 5 – code for drawing in continuous stroke

Drawing in a continuous stroke is similar to drawing lines, but the fresh lines are redrawn after every small change in coordinates. In the current state of things, the value of lastx and lasty are only updated when the mouse button is released. But here we need to update the value of lastx and lasty, not on mouse release, but on mouse motion. To achieve this, we bind the mouse motion to a newly defined method draw_brush_update_xy, which updates the x and y coordinate in every subsequent loop turn.

Earlier, we had bound mouse down motion to another method named mouse_down_motion. For drawing continuous stroke, we will now bind it to a method named draw_brush_update_xy.

Note

Adding an event binding to more than one method wipes away the previous binding, whereby the new binding replaces any existing binding. Thus, when you exit out of the draw_brush loop, you need to rebind the event back to the mouse_down_motion method.

Alternatively, you can use add="+" as an additional argument to keep more than one binding to the same event as follows:

mywidget.bind("<SomeEvent>", method1, add="+")
mywidget.bind("<SameEvent>", method2, add="+")

Thus, we create a loop where the draw_brush method calls another method, draw_brush_update_xy, on successive mouse motions to update the x and y coordinates as follows (see code 6.03.py):

def draw_brush(self, x, y, x2, y2):
	if not self.all_toolbar_functions[ 							self.selected_toolbar_func_index] == 'draw_brush':
		self.canvas.bind("<Button1-Motion>",								self.mouse_down_motion)
		return# if condition to break out of draw_brush loop
	self.currentobject = 									self.canvas.create_line(x,y,x2,y2,fill=self.foreground)

self.canvas.bind("<B1-Motion>", self.draw_brush_update_xy)

def draw_brush_update_xy(self, event):
	self.startx, self.starty = self.lastx, self.lasty
	self.lastx, self.lasty = event.x, event.y
	self.draw_brush(self.startx, self.starty,self.lastx, self.lasty)

If the Draw Brush button is unselected, we break out of the loop and rebind the mouse motion back to the canvas mouse_down_motion.

Step 6 – executing code dynamically

We have planned to execute methods dynamically, based on index from the names of methods given in a tuple named all_toolbar_functions. However, the names are stored as strings, and we just cannot take a piece of string and expect Python to evaluate it. In order to that, we will use Python's built-in getattr()method.

We now define a method that takes a string and makes it suitable for execution as a method, as follows:

def execute_method():
	fnc = getattr(self, self.all_toolbar_functions [self.selected_toolbar_func_index])
	fnc(self.startx, self.starty,self.lastx, self.lasty)

Step 7 – doing the actual drawing

Having defined methods to draw line, rectangle, oval, and brush strokes, we need to call them from somewhere for the drawing to happen. Intuitively, the drawings must begin on the first mouse down movement and the drawing must be deleted and redrawn up till the mouse button release.

Accordingly, these methods must be called from our mouse_down_motion method. We, therefore, modify our mouse_down_motion and mouse_up methods to do this, as follows:

def mouse_down_motion(self, event):
	self.lastx = self.canvas.canvasx(event.x)
	self.lasty = self.canvas.canvasy(event.y)
	if self.selected_toolbar_func_index:
		self.canvas.delete(self.currentobject)
		self.execute_method()

def mouse_up(self, event):
	self.lastx = self.canvas.canvasx(event.x)
	self.lasty = self.canvas.canvasy(event.y)
	self.canvas.delete(self.currentobject)
	self.currentobject = None
	self.execute_method()

Objective Complete – Mini Debriefing

This completes our objective for the iteration.

We began by creating a tuple of method names so as to be able to call a method dynamically by specifying its index in the tuple.

We then added icons for our toolbar buttons. We then associated a button click to a method that keeps tab on currently selected button by assigning its index to the variable, self.selected_toolbar_func_index. We then defined methods to draw line, rectangle, and oval shapes on our canvas. We also showed how to utilize the ability to draw lines to draw in continuous strokes.

Finally, we called all the draw methods from mouse_down_motion and mouse_release method to do the actual drawing.

A user can now draw basic shapes, such as lines, rectangles, ovals, and brush strokes on to the canvas. The shapes are drawn in the currently set foreground color.

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

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