0

I am wondering how within a Frame I can hide a button under an image without distorting the image size. I assume I will need to use gridding but I am not sure how to make the image fit into that without its aspect ratio going crazy. It does not seem possible to create invisible buttons in a grid with grid(), then pack an image on top with pack(). Any ideas?

I have tried:

self.go_to_hydra = Button(self, text="Go to",
                          command=lambda: self.controller.show(Hydra_Level, self))
self.go_to_hydra.grid(row=20, column=20, rowspan=10, columnspan=10)
self.map_picture = PhotoImage(file=r"images/rsz_archipelago.gif")
self.image = Label(self, image=self.map_picture)
self.image.grid(row=0, column=0, columnspan=40, rowspan=40)

When I grid the button first, it appears on top and works. When I grid it second, it does not appear, which makes sense, but when I click on where it was, no event is triggered.

martineau
  • 119,623
  • 25
  • 170
  • 301
Jack Frye
  • 583
  • 1
  • 7
  • 28
  • 1
    as you mentioned, if you use grid, you have to go with grid all the way in your frame. cant use both pack(s) and grid(s). Idk if you have considered using grid_forget() http://stackoverflow.com/questions/3819354/in-tkinter-is-there-any-way-to-make-a-widget-not-visible – glls May 26 '16 at 22:15
  • 1
    please consider adding some code of what you are attempting for better assistance – glls May 26 '16 at 22:18
  • I want the user to be able to click somewhere on the image and if a button is underneath, to go to a different frame – Jack Frye May 26 '16 at 22:19
  • in that case why don't you make the image the button and associate a command to it? – glls May 26 '16 at 22:20
  • because depending on where on the image the user clicks, a different frame will be brought up – Jack Frye May 26 '16 at 22:24
  • 1
    O.o, interesting. sooo, instead of using a button, why dot you go with coordinates? and depending on what range of coordinates the user clicks, different actions take place? (sooo many questions=P) this will avoid the need of griding buttons in your frame and distorting it – glls May 26 '16 at 22:25
  • 1
    why do you need to hide a button behind an image? If it's hidden it's useless. Just set a binding on the label that contains the image. – Bryan Oakley May 27 '16 at 00:43
  • That is impossible and **useless**. A clever and effective solution is the one @glls mentioned in his last comment. – Billal Begueradj May 27 '16 at 05:43

1 Answers1

1

You can't hide elements under each other like that with Tkinter. However, you can obtain similar results by defining rectangles representing different "button" areas of the image and determining if any of them were clicked yourself. In Tkinter terms I made the image a Label widget and bound <Button-1> click event handler to it.

This requires manually figuring-out the coordinates of the various regions in the image. Fortunately this can usually be done fairly easily by loading the image into an image editor and recording the x and y values of the corners of each region you want to define and putting them into your script. For the demo code below, I divided my test image up into the six regions shown in red below:

test image with red rectangles superimposed

from collections import namedtuple
import Tkinter as tk

Rect = namedtuple('Rect', 'x0, y0, x1, y1')

class ImageMapper(object):
    def __init__(self, image, img_rects):
        self.width, self.height = image.width(), image.height()
        self.img_rects = img_rects

    def find_rect(self, x, y):
        for i, r in enumerate(self.img_rects):
            if (r.x0 <= x <= r.x1) and (r.y0 <= y <= r.y1):
                return i
        return None

class Demo(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.grid()
        self.create_widgets()

    def create_widgets(self):
        self.msg_text = tk.StringVar()
        self.msg = tk.Message(self, textvariable=self.msg_text, width=100)
        self.msg.grid(row=0, column=0)

        self.picture = tk.PhotoImage(file='archipelago2.gif')
        img_rects = [Rect(0, 26, 80, 78),
                     Rect(89, 26, 183, 78),
                     Rect(119, 120, 168, 132),
                     Rect(126, 74, 219, 125),
                     Rect(134, 135, 219, 164),
                     Rect(0, 148, 21, 164)]
        self.imagemapper = ImageMapper(self.picture, img_rects)
        # use Label widget to display image
        self.image = tk.Label(self, image=self.picture, borderwidth=0)
        self.image.bind('<Button-1>', self.image_click)
        self.image.grid(row=1, column=0)

        self.quitButton = tk.Button(self, text='Quit', command=self.quit)
        self.quitButton.grid(row=2, column=0)

    def image_click(self, event):
        hit = self.imagemapper.find_rect(event.x, event.y)
        self.msg_text.set('{} clicked'.format('nothing' if hit is None else
                                              'rect[{}]'.format(hit)))

app = Demo()
app.master.title('Image Mapper')
app.mainloop()

Here's some screenshots of it running:

screenshots of demo running and clicking in different locations

(You can download a copy of the archipelago2.gif image being used above from here.)

martineau
  • 119,623
  • 25
  • 170
  • 301