在 Tkinter 第一个 运行 问题上调整大小 Canvas

Resizing Canvas on Tkinter first run issue

我想制作一个程序,从小 window 开始,然后在给定图像路径时,它最大化屏幕并将图像放在中央。

如果您 运行 下面的代码,您将看到 window 最大化,图像被加载到内存中,代码 运行s 没有错误并且 self.open_image 调用 self.draw_image(self.pimg) 运行 没有错误,但是 canvas.

上不存在图像

如果我单击“修复”按钮并调用 self.fix,它会调用 self.draw_image(self.pimg),其中 运行 没有错误并正确绘制图像。

如何使用相同的参数调用同一个函数两次并得到不同的结果。有什么不同。

我感觉这是在发生,因为主循环中发生了一些在 self.__init__ 结束时没有发生的事情,所以当我第二次调用 self.draw_imageself.cv.create_image 能够与可调整大小的内容进行交互 canvas。

在这个例子中,我很乐意假设程序总是从小的 window 开始,然后变成最大化的 window 直到它关闭,再也不会调整大小,但是在我的真实程序中我想让 window 合理处理调整大小的地方更加动态,这只是一个最小的可重现示例。出于这个原因,我想使用 ResizingCanvas class(或类似的),即使我觉得这可能是我遇到的问题的原因。

我已经尝试使用断点并单步执行代码来观察变量的创建,但是我看不出第一次 self.cv 和单击按钮后的 self.cv 之间的区别。

我在 this question 上读到了类似的问题,他建议将 "<Configure>" 绑定到 canvas 并将坐标从事件传递到 canvas。然而,这已经在 ResizingCanvas

中实施
from tkinter import *
from PIL import Image, ImageTk

class ResizingCanvas(Canvas):
    # 
    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()

视频中应该发生的事情: 我点击 运行 图像立即显示,无需点击修复按钮。

视频中发生了什么: 我单击 运行 并且直到我单击修复按钮后才显示图像,然后它就可以工作了。

改变

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

在你的代码中(我正在研究Python3)我只能得到:

[

上图,从How to get tkinter canvas to dynamically resize to window width?开始播放了一点

现在代码似乎适用于我:

from time import sleep

from tkinter import *
from PIL import Image, ImageTk

class ResizingCanvas(Canvas):
    # 
    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()

虽然不知道你的问题,但想确保你的起始示例工作正常。如果它可能与 python/pillow/tkinter 版本或其他

相关,请告诉我

这是我的 window 在按下修复按钮后广告之前的图像结果:

最后发现只要你使用

你的代码就可以工作
self.root.attributes('-zoomed', True) instead of `self.root.state("zoomed")`

问题就在这里。 self.root.winfo_screenwidth() 将其更改为 self.cv.width。不知道为什么。

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'))

将最后一行改为

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

解决了问题。

Tk.winfo_screenwidth()根据https://tkdocs.com/shipman/universal.htmlreturns屏幕的宽度,与window的大小无关,所以即使你有一个小window 在 1920x1080 显示器上,此函数将 return 1920.

self.cv.width returns canvas 对象的宽度。