Welcome, guest | Sign In | My Account | Store | Cart

A simple, yet effective rectangle selection box. :)

Works with a tkinter canvas! Just add the class and set it up like in the example code. The cross heir was my own touch, the RectTracker only draws a box.

Have fun! And please don't just vote down, post what you don't like if you don't like it.

Python, 153 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""Rect Tracker class for Python Tkinter Canvas"""

def groups(glist, numPerGroup=2):
	result = []

	i = 0
	cur = []
	for item in glist:
		if not i < numPerGroup:
			result.append(cur)
			cur = []
			i = 0

		cur.append(item)
		i += 1

	if cur:
		result.append(cur)

	return result

def average(points):
	aver = [0,0]
	
	for point in points:
		aver[0] += point[0]
		aver[1] += point[1]
		
	return aver[0]/len(points), aver[1]/len(points)

class RectTracker:
	
	def __init__(self, canvas):
		self.canvas = canvas
		self.item = None
		
	def draw(self, start, end, **opts):
		"""Draw the rectangle"""
		return self.canvas.create_rectangle(*(list(start)+list(end)), **opts)
		
	def autodraw(self, **opts):
		"""Setup automatic drawing; supports command option"""
		self.start = None
		self.canvas.bind("<Button-1>", self.__update, '+')
		self.canvas.bind("<B1-Motion>", self.__update, '+')
		self.canvas.bind("<ButtonRelease-1>", self.__stop, '+')
		
		self._command = opts.pop('command', lambda *args: None)
		self.rectopts = opts
		
	def __update(self, event):
		if not self.start:
			self.start = [event.x, event.y]
			return
		
		if self.item is not None:
			self.canvas.delete(self.item)
		self.item = self.draw(self.start, (event.x, event.y), **self.rectopts)
		self._command(self.start, (event.x, event.y))
		
	def __stop(self, event):
		self.start = None
		self.canvas.delete(self.item)
		self.item = None
		
	def hit_test(self, start, end, tags=None, ignoretags=None, ignore=[]):
		"""
		Check to see if there are items between the start and end
		"""
		ignore = set(ignore)
		ignore.update([self.item])
		
		# first filter all of the items in the canvas
		if isinstance(tags, str):
			tags = [tags]
		
		if tags:
			tocheck = []
			for tag in tags:
				tocheck.extend(self.canvas.find_withtag(tag))
		else:
			tocheck = self.canvas.find_all()
		tocheck = [x for x in tocheck if x != self.item]
		if ignoretags:
			if not hasattr(ignoretags, '__iter__'):
				ignoretags = [ignoretags]
			tocheck = [x for x in tocheck if x not in self.canvas.find_withtag(it) for it in ignoretags]
		
		self.items = tocheck
		
		# then figure out the box
		xlow = min(start[0], end[0])
		xhigh = max(start[0], end[0])
		
		ylow = min(start[1], end[1])
		yhigh = max(start[1], end[1])
		
		items = []
		for item in tocheck:
			if item not in ignore:
				x, y = average(groups(self.canvas.coords(item)))
				if (xlow < x < xhigh) and (ylow < y < yhigh):
					items.append(item)
	
		return items

def main():
	from random import shuffle
	
	canv = Canvas(width=500, height=500)
	canv.create_rectangle(50, 50, 250, 150, fill='red')
	canv.pack(fill=BOTH, expand=YES)
	
	rect = RectTracker(canv)
	# draw some base rectangles
	rect.draw([50,50], [250, 150], fill='red', tags=('red', 'box'))
	rect.draw([300,300], [400, 450], fill='green', tags=('gre', 'box'))
	
	# just for fun
	x, y = None, None
	def cool_design(event):
		global x, y
		kill_xy()
		
		dashes = [3, 2]
		x = canv.create_line(event.x, 0, event.x, 1000, dash=dashes, tags='no')
		y = canv.create_line(0, event.y, 1000, event.y, dash=dashes, tags='no')
		
	def kill_xy(event=None):
		canv.delete('no')
	
	canv.bind('<Motion>', cool_design, '+')
	
	# command
	def onDrag(start, end):
		global x,y
		items = rect.hit_test(start, end)
		for x in rect.items:
			if x not in items:
				canv.itemconfig(x, fill='grey')
			else:
				canv.itemconfig(x, fill='blue')
	
	rect.autodraw(fill="", width=2, command=onDrag)
	
	mainloop()

if __name__ == '__main__':
	try:
		from tkinter import *
	except ImportError:
		from Tkinter import *
	main()

Plans for the future: - Add a mechanism so that the object is only selected if ALL of it is in the box. Not hard to do, just some extra lines.