Delgan
Delgan

Reputation: 19627

How to prevent image to fill entire Tkinter window while resizing?

Based on this answer, I wanted to add a second widget below the main image.

The image is intended to fit the window when this one is resized by the user.

See the example from @Marcin:

Resized image

The issue is that if I add any widget to the window, something weird is happening (here with a Text):

Weird resizing

The image is growing progressively until it fill the entire window, and the second widget disappears.

Here is my code:

from tkinter import *
from PIL import Image, ImageTk
from io import BytesIO
import base64

root = Tk()
root.title("Title")
root.geometry("600x600")


class ImageFrame(Frame):
    def __init__(self, master, *pargs):
        Frame.__init__(self, master, *pargs)


        self.black_pixel = BytesIO(base64.b64decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="))
        self.image = Image.open(self.black_pixel)

        self.img_copy= self.image.copy()


        self.background_image = ImageTk.PhotoImage(self.image)

        self.background = Label(self, image=self.background_image)
        self.background.pack(fill=BOTH, expand=YES)
        self.background.bind('<Configure>', self._resize_image)

        self.width = 1
        self.height = 1

    def _resize_image(self,event):

        new_width = event.width
        new_height = event.height

        if new_width != self.width or new_height != self.height:
            self.width = new_width
            self.height = new_height

            self.image = self.img_copy.resize((new_width, new_height))

            self.background_image = ImageTk.PhotoImage(self.image)
            self.background.configure(image =  self.background_image)


img = ImageFrame(root)
txt = Text(root)

img.pack(fill=BOTH, expand=YES)
txt.pack()

root.mainloop()

I tried by packing img and txt with different options but it chnanged nothing.

Does anyone have an idea of what I am doing wrong, please?

Upvotes: 2

Views: 1671

Answers (1)

Tadhg McDonald-Jensen
Tadhg McDonald-Jensen

Reputation: 21453

Calling the .configure on a widget from within the <Configure> callback on same widget is a lot like recursion, there is always a risk of never ending.

When <Configure> triggers the event.width and event.height include the automatic padding of 2 pixels, so setting the image to that size increases the size of the Label by 4 pixels firing the <Configure> event again.

This doesn't happen in the example by @markus because once the Label is the size of a window with forced geometry it won't reconfigure again, this is also what happens in your program once the image has finished consuming all the space on the window.

The really quick fix is to change:

    new_width = event.width
    new_height = event.height

To:

    new_width = event.width - 4
    new_height = event.height - 4

To compensate for the spacing but I give absolutely no guarantee that it will work consistently.


Here is a different implementation that uses a Canvas instead of a Frame and Label:

class ImageFrame(Canvas):
    def __init__(self, master, *pargs ,**kw):
        Canvas.__init__(self, master, *pargs,**kw)

        self.black_pixel = BytesIO(base64.b64decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="))
        self.img_copy = Image.open(self.black_pixel)
        self.image = None #this is overriden every time the image is redrawn so there is no need to make it yet

        self.bind("<Configure>",self._resize_image)

    def _resize_image(self,event):
        origin = (0,0)
        size = (event.width, event.height)
        if self.bbox("bg") != origin + size:
            self.delete("bg")
            self.image = self.img_copy.resize(size)
            self.background_image = ImageTk.PhotoImage(self.image)
            self.create_image(*origin,anchor="nw",image=self.background_image,tags="bg")
            self.tag_lower("bg","all")

This way instead of reconfiguring the widget it just redraws the image on the canvas.

Upvotes: 1

Related Questions