[Tkinter-discuss] Limit to the number of canvases in a top_level object?

Peter Otten __peter__ at web.de
Sun Oct 16 15:43:26 CEST 2011


Steve Solomon wrote:

>>> Is there  a limit to the number of canvases in a top_level object -- one
>>> maybe ? I have been trying to solve this problem with an application for
>>> several weeks. In the application I have two canvases, each with a grid
>>> of rectangle objects. They display perfectly but when I try to interact
>>> with the rectangles they behave anomalously. Using
>>> canvas.find_closest(event.x, event.y) the rectangles in the first drawn
>>> canvas behave normally, allowing configuring each, but within the second
>>> canvas the rectangles report back the id of only one of the rectangles
>>> and an attempt to change the fill-color of any one is applied only to
>>> the last item in the grid.
>>>
>>> Your thoughts would be appreciated, thank you

> I'm a bit in the tall weeds, please excuse the mess (in the attached
> code).

The best time to clean up your code is always right now; the second best 
time is when you run into an error ;)

> from Tkinter import *
> import random
> import palettebuilder as pb
> from colorservices import getHSV
> 
> def randomColors(count=1, sorted=False):
>     "returns list of color strings in #nnnnnn hexidecimal format"
>     rtn = []
>     for x in range(count):
>         intgr = random.randrange(0, 16777215, 1)
>         hex = "#{:0>6X}".format(intgr)
>         rtn.append(hex)
>     if sorted:
>         "the folowing should be sorted by ..."
> #        rtn.sort(key=lambda rtn: getHSV(rtn)[2]) #...value
>         rtn.sort(key=lambda rtn: getHSV(rtn)) #...full HSV
>     return rtn
>     
> class test(Toplevel):
>     def __init__(self):
>         Toplevel.__init__(self)
>         self.config(height = 500, width = 500)
>         self.activeCanvas = None
>         self.bind("<KeyPress-c>", self.reportXY)
>         size = 180
>         frameOne = Frame(self, height = 200, width =200, bg = "pink")
>         self.canvasOne = Canvas(frameOne, height=size, width=size, 
background="#bababa")
>         self.canvasOne.grid(row = 0, column = 0, sticky = W )
>         frameTwo = Frame(self, height = 200, width =200, bg="light blue")
>         self.canvasTwo = Canvas(frameTwo, height=size, width=size, 
bg='#bababa')
>         self.canvasTwo.grid(row = 1, column = 1)
>         self.canvasOne.bind("<Enter>", self.makeOneActive)
>         self.canvasTwo.bind("<Enter>", self.makeTwoActive)
>         frameOne.grid(row = 0, column = 0, sticky = W )
>         frameTwo.grid(row = 1, column = 1, sticky = E )
>         colors = randomColors(64, sorted = True)
>         pb.DrawPalette(self.canvasOne, colors, columns = 8)
>         colors = randomColors(64, sorted = True)
>         pb.DrawPalette(self.canvasTwo, colors, columns = 8)
>     
>     def makeOneActive(self, event):
>         self.activeCanvas = self.canvasOne
>         print "one = active self.activeCanvas>>>", type(self.activeCanvas)
>         
>     def makeTwoActive(self, event):
>         self.activeCanvas = self.canvasTwo
>         print "two = active self.activeCanvas>>>", type(self.activeCanvas)
> 
>     def reportXY(self, event):
>         print event.x, event.y
>         item=self.activeCanvas.find_closest(event.x, event.y)
>         print self.activeCanvas.itemcget(item, "tags"), "item>>", item
>         self.activeCanvas.itemconfigure(item, fill = "red")
>         
> root = Tk()
> root.withdraw()
> test()
> #for x in range(10):
> #    print randomColors(10)
> #    print "\n"
> root.mainloop()
> 

You bind the reportXY() method to an event triggered by your Toplevel 
subclass, so the event instance contains coordinates relative to the test 
widget. On the other hand the Canvas.find_closest() method expects 
coordinates in terms of the canvas widget. The difference doesn't matter for 
canvasOne which is located at the origin of the toplevel, but gives wrong 
results for canvasTwo with its non-zero offset.

While the diagnosis was easy it took me a while to find a way to convert 
between the two coordinate systems. The following seems to work:

    def reportXY(self, event):
        canvas = self.activeCanvas

        dx = self.winfo_rootx() - canvas.winfo_rootx()
        dy = self.winfo_rooty() - canvas.winfo_rooty()

        x = event.x + dx
        y = event.y + dy

        print "before", event.x, event.y
        print "after", x, y

        item = canvas.find_closest(x, y)
        print canvas.itemcget(item, "tags"), "item>>", item
        canvas.itemconfigure(item, fill="red")

Let me know if you find a better way or run into a problem with my approach.



More information about the Tkinter-discuss mailing list