0

I want to make a program that begins as a small window, then when given a path to an image, it maximises the screen and places the image in the centre.

If you run the code below you will see that the window maximises, the image is loaded into memory, the code runs with no errors and self.open_image calls self.draw_image(self.pimg) which runs without error, however the image is not present on the canvas.

If I click the button "Fix" and call self.fix it calls self.draw_image(self.pimg) which runs without error and correctly draws the image.

How can you call the same function twice with the same arguments and get different results. What is different.

I get the feeling this is happening because something has taken place in the main loop that hasn't taken place at the end of self.__init__, so that when i call self.draw_image the second time self.cv.create_image is able to interact with something in the resizable canvas.

In this example I am happy to assume the program will always begin as a small window and become a maximised window untill it is closed, never being resized again, however in my real program I would like to make it more dynamic where the window handles resizing sensibly, this is just a minimum reproducible example. It is for this reason that I would like to use the ResizingCanvas class (or one like it) even though I feel that it is likely the cause of the issue I am experiencing.

I have tried using breakpoints and stepping through the code watching the variables get created but I cant see the difference between the self.cv the first time around and self.cv after I click the button.

I read about a similar issue here on this question and he suggests binding "<Configure>" To the canvas and passing the coords from the event to the canvas. However this has already been implemented in ResizingCanvas

from tkinter import *
from PIL import Image, ImageTk

class ResizingCanvas(Canvas):
    # https://stackoverflow.com/a/22837522/992644
    def __init__(self,parent,**kwargs):
        Canvas.__init__(self,parent,**kwargs)
        self.bind("<Configure>", self.on_resize)
        self.height = self.winfo_reqheight()
        self.width = self.winfo_reqwidth()

    def on_resize(self,event):
        """ determine the ratio of old width/height to new width/height"""
        wscale = float(event.width)/self.width
        hscale = float(event.height)/self.height
        self.width = event.width
        self.height = event.height
        # resize the canvas 
        self.config(width=self.width, height=self.height)
        # rescale all the objects tagged with the "all" tag
        self.scale("all",0,0,wscale,hscale)

class main():
    def __init__(self, name = None):
        self.root = Tk()
        self.name = name # Filename
        myframe = Frame(self.root)
        myframe.pack(fill=BOTH, expand=YES)
        self.cv = ResizingCanvas(myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
        self.cv.pack(fill=BOTH, expand=YES)
        self.b = Button(self.cv, text = 'Fix', command = self.fix).grid(row=1,column=1)
        self.open_img()

    def draw_image(self, img, x = None, y = None):
        """ Handles the drawing of the main image"""
        self.img = ImageTk.PhotoImage(img)
        self.cv.create_image(self.root.winfo_screenwidth()/2, 
                             self.root.winfo_screenheight()/2, image=self.img, tags=('all'))

    def open_img(self, event=''):
        self.pimg = Image.open(self.name)
        self.root.state("zoomed")
        self.draw_image(self.pimg)

    def fix(self, event=''):
        self.draw_image(self.pimg)

    def run(self):
        self.root.mainloop()

if __name__ == "__main__":
    path = 'example.png'
    app = main(path)
    app.run()

What should happen in the video: I click run and the image is displayed immediately, without having to click the fix button.

What does happen in the video: I click run and the image is not displayed until I click the fix button, afterwhich it works. Screencap of the bug

  • 1
    hi @hamsolo474 kind of tried out your code but wasnt able to make it run I am adding the by chance version that is working with me, dont know how to answer your question though – pippo1980 Sep 06 '21 at 20:21
  • @pippo1980 Thanks for replying, I have attached a video of the bug with my code as is. I am running this on Windows 10 and in your link it says `self.root.attributes('-zoomed', True)` on linux is equivalent to `self.root.attributes("-fullscreen", True)` on windows, where I am looking for maximise rather than full screen. I'm guessing your issue is due to slight differences between Tkinter on Linux and Tkinter on Windows. Thank you for posting your fix to get my example to work on Linux, if any Windows user sees this and also can't run my code please say something. – hamsolo474 - Reinstate Monica Sep 07 '21 at 12:09
  • 1
    try moving # self.pippo.state("zoomed") from ' def open_png:' to 'def fix:' you'll get 3 different result calling same function 3 different times – pippo1980 Sep 07 '21 at 15:36
  • 1
    try adding 'print('EVENT : ' ,event)' just after 'def on_resize:' and see if you can tell any difference – pippo1980 Sep 07 '21 at 15:38

2 Answers2

1

Changing

self.root.state("zoomed") to self.root.state("normal")

in your code (I am working on Python3) I can only get:

[https://stackoverflow.com/questions/22835289/how-to-get-tkinter-canvas-to-dynamically-resize-to-window-width1

the image above, played a little bit starting from How to get tkinter canvas to dynamically resize to window width?

and now the code seems to work with me:

from time import sleep

from tkinter import *
from PIL import Image, ImageTk

class ResizingCanvas(Canvas):
    # https://stackoverflow.com/a/22837522/992644
    def __init__(self,parent, **kwargs):
        # Canvas.__init__(self,parent,**kwargs)
        print(kwargs)
        Canvas.__init__(self,parent,**kwargs)
        self.bind("<Configure>", self.on_resize)
        # self.height = self.winfo_reqheight()
        # self.width = self.winfo_reqwidth()
        self.height = self.winfo_height()
        self.width = self.winfo_width()
        # self.height = height
        # self.width = width
        # self.__dict__.update(kwargs)

    def on_resize(self,event):
        """ determine the ratio of old width/height to new width/height"""
        wscale = (event.width)//self.width
        hscale = (event.height)//self.height
        self.width = event.width
        self.height = event.height
        # resize the canvas 
        self.config(width=self.width, height=self.height)
        # rescale all the objects tagged with the "all" tag
        self.scale("all",0,0,wscale,hscale)

class main():
    def __init__(self, name = None):
        self.pippo = Tk()
        self.name = name # Filename
        self.myframe = Frame(self.pippo)
        self.myframe.pack(side = BOTTOM, expand=YES)
        # myframe.pack(fill=BOTH, expand='TRUE')
        self.cv = ResizingCanvas(self.myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
        self.cv.pack(fill=BOTH, expand=YES)
        # sleep(2)
        self.b = Button(self.myframe, text = 'Fix', command = self.fix)#.grid(row=1,column=1)
        self.b.pack(side=TOP)
        self.open_img()
        # self.pippo.mainloop()  ## use it if you eliminate def run

    def draw_image(self, img, x = None, y = None):
        """ Handles the drawing of the main image"""
        self.img = ImageTk.PhotoImage(img)
        # self.cv.create_image(self.pippo.winfo_screenwidth()/2, 
        #                      self.pippo.winfo_screenheight()/2, image=self.img, tags=('all'))
        self.cv.create_image(self.pippo.winfo_width()/2, 
                             self.pippo.winfo_reqheight()/2, image=self.img, tags=('all'))

    def open_img(self, event=''):
        self.pimg = Image.open(self.name)
        self.pippo.state("normal")
        self.draw_image(self.pimg)

    def fix(self, event=''):
        self.draw_image(self.pimg)

    def run(self):
        self.pippo.mainloop()

if __name__ == "__main__":
    path = 'example.png'
    app = main(path)
    app.run()

don't know about your question though, but wanted to be sure your starting example works right. Let me know if it could be related to python/pillow/tkinter version or something else

Here my window image results before ad after pressing fix button :

enter image description here

enter image description here

At the end found out that your code does work as long as you use

self.root.attributes('-zoomed', True) instead of `self.root.state("zoomed")`
pippo1980
  • 2,181
  • 3
  • 14
  • 30
  • 1
    See here https://www.delftstack.com/howto/python-tkinter/how-to-create-full-screen-window-in-tkinter/ – pippo1980 Sep 06 '21 at 22:53
0

The problem is here. self.root.winfo_screenwidth() Change it to self.cv.width. I don't know why.

def draw_image(self, img, x = None, y = None):
        """ Handles the drawing of the main image"""
        self.img = ImageTk.PhotoImage(img)
        self.cv.create_image(self.root.winfo_screenwidth()/2, 
                             self.root.winfo_screenheight()/2, image=self.img, tags=('all'))

Change the last line to

self.cv.create_image(self.cv.width/2, 
                     self.cv.height/2, image=self.img, tags=('all'))

Fixes the issue.

Tk.winfo_screenwidth() according to https://tkdocs.com/shipman/universal.html returns the width of the screen, indepedant of the size of the window, so even if you have a small window on a 1920x1080 display, this function will return 1920.

self.cv.width returns the width of the canvas object.

  • What else were you expecting to be returned by `winfo_screenwidth()`..? It returns what you set initially, 850 and 400 – Delrius Euphoria Sep 11 '21 at 07:23
  • It shouldn't return 850 and 400, `w.winfo_screenwidth() Returns the width of the screen in pixels.` – hamsolo474 - Reinstate Monica Sep 11 '21 at 12:22
  • It should return 1920 and 1080 (on my monitor). In my actual code I have a slightly more complicated setup where I have the window resizing like in this example and my content resize to fit the new window dimension (not seen in this example), I think I was playing around with this code and swapped `self.cv.width` to `self.root.winfo_screenwidth()` to try and resolve that problem, that then created new issues and I forgot about this change. – hamsolo474 - Reinstate Monica Sep 11 '21 at 12:29