Tkinter w.destroy() blocks/resets window resize

2020-07-27 06:12发布

问题:

I have a Python 3.5 app using tkinter and create/remove widgets in a canvas on the fly. I noticed that when a widget is being destroyed using w.destroy() during the callback of a window resize, the callback finished correctly, but the window is being reset to its original size.

I've tried to delay the w.destroy() using self.after(1000, self.resize) and that kind off works but when dragging/holding the cursor down while the callback is being fired, the resize jumps back anyway.

Anyone having a suggestion to remove widgets on the fly and release memory?

UPDATE: As a workaround I mark widgets to be deleted with w.delete=True and in an 'after' callback delete the widgets which are marked for deletion.

Here is a stripped down sample to illustrate the issue:

UPDATE: Simplified code to bare minimum to reproduce issue.

from tkinter import ALL, N, E, W, S, Canvas, Tk, ttk


class Resize(Tk):
    def __init__(self):
        super().__init__()
        self.init = True
        self.canvas = None
        self.position_window()
        self.create_canvas()

    def resize(self):
        if self.init:
            self.init = False
            return
        # delete(ALL) will remove the widget from the display, but widget 
        # remains in memory as child of canvas. Resizing works ok.
        self.canvas.delete(ALL)
        # widget.destroy() will release memory of widget, but blocks/resets resizing.
        # Window gets resized by 1-2 pixels and jumps back to its original size.
        [child.destroy() for child in self.canvas.winfo_children()]

    def create_canvas(self):
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

        self.canvas = Canvas(self)
        self.canvas.grid(row=0, column=0, sticky=(N, S, W, E))
        self.canvas.bind('<Configure>', lambda *args: self.resize())

        entry = ttk.Label(self.canvas, text='Some dummy text')
        self.canvas.create_window(20, 20, window=entry, anchor='w')

    def position_window(self):
        self.resizable(1, 1)
        sceenwidth = self.winfo_screenwidth()
        screenheight = self.winfo_screenheight()
        self.update_idletasks()
        width = 600
        height = 300
        left = sceenwidth / 2 - width / 2
        top = (screenheight / 2 - height / 2) / 3
        self.geometry("%dx%d%+d%+d" % (width, height, left, top))


if __name__ == '__main__':
    Resize().mainloop()

回答1:

I did not try your code, but I had the same problem with my own project.

The window wasn't just resizing/resetting on destroy(); the <Configure> event was seemingly interrupted. The window always reverted back to the very first <Configure> call back no matter how much or how little I resized. Because of that, or along with that, Button-1 was automatically releasing, preventing the window resize grip from following the mouse.

In other words, the window edge was "slipping out" from under the mouse, much like a scrollbar in Windows will reset mid-scroll when the mouse moves too far from it.

Anyways, a solution is to use ttk.Sizegrip and prohibit resizing the top level window with the window borders.