0

I have many overlapping shapes representing irrelevant background items on a canvas. I also have a pattern of non-overlapping circles, each of which is a "hole". Each "hole" sprite (circle) has an associated "hole" object, though never explicitly in the code. (side note: I would love to have a logical association between model and view with these objects, but haven't found a smart way to do that). Each "hole" is different, and has different effects.

There is a small circular "ball" which can be dragged into any "hole". I found how to drag and drop from this question. I need to find which hole the ball went into.

The best way I have found to do that so far is to:

  • create a dict mapping the coordinates of the center of the hole sprite to the hole object

  • tag each hole like this:

t=("hole", "hole_at_{}_{}".format(x, y))

  • on releasing the ball, do this:

    def on_ball_release(self, event): '''Process button event when user releases mouse holding ball.'''

    # use small invisible rectangle and find all overlapping items
    items = self._canvas.find_overlapping(event.x - 10, event.y - 10, event.x + 10, event.y + 10)
    
    for item in items:
        # there should only be 1 overlapping hole
        if "hole" in self._canvas.gettags(item):
    
            # get the coordinates from the tag
            coords = tuple([int(i) for i in self._canvas.gettags(item)[1].replace("hole_at_", "").split("_")])
    
            # get associated object using dictionary established before
            hole = self._hole_dict[coords]
    
            hole.process_ball()
            return
    

That seems very messy. I feel there should be some smarter way to do this.

Community
  • 1
  • 1
Daniel Kats
  • 5,141
  • 15
  • 65
  • 102

1 Answers1

1

Disclaimer: I don't use Python, but many Tkinter questions can be answered in a useful from an experience with Tcl/Tk, which I have. In this case, it takes some more work to figure out whether what I would do in Tcl is easy to represent with Tkinter.

First, I wouldn't add "identifier tags" (hole_at_...): if I have model objects corresponding to canvas items, I would use the item id (which canvas returns during item creation) as an index, to be able to find an object for an item id without parsing tags. (And if I had to add string identifiers, even if I decided to make them from coordinates, I would use that very string as my dictionary key, to avoid reparsing it. Do we need coordinates later? Then make them properties of the hole object).

Second, I would use pathName find subcommand with multiple criteria to find (canvas id of) item which is tagged as hole and is nearest to the given point (overlapping is fine when we want to ignore drops too far from any hole, closest is for the case where nearest hole should be used even if it's not too near). Here is the problematic part: does Tkinter support multiple criteria in canvases' $pathName find?

Anton Kovalenko
  • 20,999
  • 2
  • 37
  • 69
  • I like your other suggestions, but the problem with using `find_closest` is the plethora of background objects that could be closer, but I am not interested in them. – Daniel Kats Jan 22 '13 at 22:15
  • Is there a more direct way to tie a model to its sprite than with a dict? – Daniel Kats Jan 22 '13 at 22:18
  • @BlackSheep that's why I suggested multi-criteria find: when it's given `withtag hole` as a first criteria and `closest x y ?halo? ?start?` as a second (that's all in TCL, sorry), it will consider nothing but *holes*. – Anton Kovalenko Jan 22 '13 at 22:18
  • @BlackSheep in TCL, it would be equivalent to a dict, more or less. As of Python, I wouldn't be surprised if it could create "proxy objects" for canvas items to save us trouble working with ids, but I have no idea whether it's actually possible. – Anton Kovalenko Jan 22 '13 at 22:20
  • 1
    @AntonKovalenko, @BlackSheep: the multi-criteria find can be done in Python by using the `find` method of the `Canvas` class, it won't be pretty but can be done. `mycanvas.find('withtag', 'hole', 'closest', x, y)`. – mmgp Jan 23 '13 at 03:20