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.
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.