Tkinter track window resize specifically?
Asked Answered
E

3

9

I am writing a small program, which the intention to run a resize method, when the window size is changed. for this reason, I used toplevel.bind("<Configure>", self.resize). While this does work for resizing, the method gets called hundreds of times: when scrolling, when using some of the buttons, even though the window size doesn't change with any of these actions. How can I bind the event, so that it only gets called upon changing the window size?

import tkinter as tk

def resize(event):
    print("widget", event.widget)
    print("height", event.height, "width", event.width)

parent = tk.Toplevel()
canvas = tk.Canvas(parent)
scroll_y = tk.Scrollbar(parent, orient="vertical", command=canvas.yview)
scroll_x = tk.Scrollbar(parent, orient="horizontal", command=canvas.xview)
frame = tk.Frame(canvas)

# put the frame in the canvas
canvas.create_window(0, 0, anchor='nw', window=frame)
# make sure everything is displayed before configuring the scrollregion
canvas.update_idletasks()

canvas.configure(scrollregion=canvas.bbox('all'), 
                yscrollcommand=scroll_y.set,
                xscrollcommand=scroll_x.set)

scroll_y.pack(fill='y', side='right')
scroll_x.pack(fill='x', side='bottom')
canvas.pack(fill='both', expand=True, side='left')

parent.bind("<Configure>", resize)

parent.mainloop()

Output:

widget .!toplevel
height 200 width 200
widget .!toplevel.!scrollbar
height 286 width 17
widget .!toplevel.!scrollbar2
height 17 width 382
widget .!toplevel.!canvas
height 269 width 382
widget .!toplevel
height 286 width 399
widget .!toplevel
height 286 width 399
widget .!toplevel.!can

Eventhough there is only one canvas plus the scrollbars on the window, the resize event gets called 7 times, before the user even has a change to interact with the window. How can I stop this madness and only call the resize function, when the window does get resized?

Expertise answered 10/5, 2020 at 12:53 Comment(1)
The explanation in this link may be helpful.Axon
F
9

In effbot DOC:

<Configure>

The widget changed size (or location, on some platforms). The new size is provided in the width and height attributes of the event object passed to the callback.

When you run your code, it will draw those widget in the window, so the width and the height of the window will change. You could use a if statement to check whether the width and height of the Toplevel changed.

Change the function resize() and initialize the global variable in your code:

def resize(event):
    global window_width, window_height
    if event.widget.widgetName == "toplevel":
        if (window_width != event.width) and (window_height != event.height):
            window_width, window_height = event.width,event.height
            print(f"The width of Toplevel is {window_width} and the height of Toplevel "
                  f"is {window_height}")

window_width, window_height = 0, 0

You could avoid the global variables by using a class (OOP).

Forthwith answered 11/5, 2020 at 1:0 Comment(0)
F
5

As far as I know, when you bind '<Configure>' events to a root or toplevel window it also gets bound to every widget it contains — and the only workaround is to filter-out all those other calls. Fortunately that's not too difficult. To avoid using global variables, I've defined a class to deal with the dirty details.

import tkinter as tk


class Tracker:
    """ Toplevel windows resize event tracker. """

    def __init__(self, toplevel):
        self.toplevel = toplevel
        self.width, self.height = toplevel.winfo_width(), toplevel.winfo_height()
        self._func_id = None

    def bind_config(self):
        self._func_id = self.toplevel.bind("<Configure>", self.resize)

    def unbind_config(self):  # Untested.
        if self._func_id:
            self.toplevel.unbind("<Configure>", self._func_id)
            self._func_id = None

    def resize(self, event):
        if(event.widget == self.toplevel and
           (self.width != event.width or self.height != event.height)):
            print(f'{event.widget=}: {event.height=}, {event.width=}\n')
            self.width, self.height = event.width, event.height


root = tk.Tk()
root.withdraw()
parent = tk.Toplevel(master=root)
parent.title('parent')
canvas = tk.Canvas(parent)
scroll_y = tk.Scrollbar(parent, orient="vertical", command=canvas.yview)
scroll_x = tk.Scrollbar(parent, orient="horizontal", command=canvas.xview)
frame = tk.Frame(canvas)

# put the frame in the canvas
canvas.create_window(0, 0, anchor='nw', window=frame)
# make sure everything is displayed before configuring the scrollregion
canvas.update_idletasks()

canvas.configure(scrollregion=canvas.bbox('all'),
                 yscrollcommand=scroll_y.set,
                 xscrollcommand=scroll_x.set)

scroll_y.pack(fill='y', side='right')
scroll_x.pack(fill='x', side='bottom')
canvas.pack(fill='both', expand=True, side='left')

tracker = Tracker(parent)
tracker.bind_config()

parent.mainloop()

Feudality answered 3/8, 2021 at 1:47 Comment(1)
Instead of binding and unbinding, consider storing a reference to the root Tk() object in your tracker class and resize iff the object is a Tk ObjectShearin
F
0

I modified the solution and came up with something:

Bind the event to any function and define a variable to store windows size.

self.win_size = 0   
self.bind("<Configure>", self.catch_win_size)

and then in the function, check if the windows size has changed or not

def catch_win_size(self,event):
    x = str(event.widget)
    if x == ".": # . is toplevel window
        if (self.win_size != event.height):
            self.win_size = event.height
            print(f"The height of Toplevel is {self.win_size}")

In my case, I only want to track changes in height.

Fad answered 5/12, 2023 at 0:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.