pythontkinterlayout

Python Tkinter: understanding winfo_geometry()


I am using Python 3.11 on Windows.

I don't understand the behaviour of winfo_geometry(), winfo_height(), winfo_x(), etc.

Here is my code:

import tkinter as tk
from tkinter import ttk

class Application:
    def __init__(self, root):
        self.root = root
        self.root.title("Window title")
        self.root.geometry("800x600")
        self.define_styles()
        self.place_widgets()
        self.print_dimensions()

    def define_styles(self):
        self.dark = ttk.Style()
        self.dark.configure('Dark.TFrame', background='#343434')
        self.light_green = ttk.Style()
        self.light_green.configure('LightGreen.TFrame', background='#a4edde')
        self.light_blue = ttk.Style()
        self.light_blue.configure('LightBlue.TFrame', background='#73d9eb')

    def place_widgets(self):
        self.frame_for_canvas = ttk.Frame(self.root, style='Dark.TFrame', width=500)
        self.frm_right_panel = ttk.Frame(self.root, style='LightGreen.TFrame', width=200)

        self.btn_1 = ttk.Button(self.frame_for_canvas, text='btn_1', command=self.f_btn1)
        self.lbl_1 = ttk.Label(self.frame_for_canvas, text="lbl_1", width=40)
        self.btn_1.pack()
        self.lbl_1.pack()

        self.frame_for_canvas.pack_propagate(0)
        self.frame_for_canvas.pack(side='left', fill='both', expand=True)
        self.frm_right_panel.pack_propagate(0)
        self.frm_right_panel.pack(side='left', fill='both', expand=False)

        self.frm_header1 = ttk.Frame(self.frm_right_panel, style='LightBlue.TFrame')
        self.btn_2 = ttk.Button(self.frm_header1, text='btn_2', command=self.f_btn2)
        self.lbl_2 = ttk.Label(self.frm_header1, text="lbl_2", width=40)
        self.btn_2.pack()
        self.lbl_2.pack()
        self.frm_header1.pack(side='top', fill='x', expand=False)

        self.frm_header2 = ttk.Frame(self.frm_right_panel, style='Dark.TFrame')
        self.btn_3 = ttk.Button(self.frm_header2, text='btn_3', command=self.f_btn3)
        self.lbl_3 = ttk.Label(self.frm_header2, text="lbl_3", width=40)
        self.btn_3.pack()
        self.lbl_3.pack()
        self.frm_header2.pack(side='top', fill='x', expand=False)

    def f_btn1(self):
        self.lbl_1.config(text=self.frame_for_canvas.winfo_geometry())
        self.print_dimensions()

    def f_btn2(self):
        self.lbl_2.config(text=self.frm_header1.winfo_geometry())
        self.print_dimensions()

    def f_btn3(self):
        self.lbl_3.config(text=self.frm_header2.winfo_geometry())
        self.print_dimensions()

    def print_dimensions(self):
        self.root.update_idletasks()
        self.frame_for_canvas.update_idletasks()
        self.frm_right_panel.update_idletasks()
        self.frm_header1.update_idletasks()
        self.frm_header2.update_idletasks()

        print('self.root.winfo_geometry()', self.root.winfo_geometry())
        print('self.root.winfo_height()', self.root.winfo_height())
        print('self.root.winfo_width()', self.root.winfo_width())
        print('self.root.winfo_x()', self.root.winfo_x())
        print('self.root.winfo_y()', self.root.winfo_y())

        print('self.frame_for_canvas.winfo_geometry()', self.frame_for_canvas.winfo_geometry())
        print('self.frame_for_canvas.winfo_height()', self.frame_for_canvas.winfo_height())
        print('self.frame_for_canvas.winfo_width()', self.frame_for_canvas.winfo_width())
        print('self.frame_for_canvas.winfo_x()', self.frame_for_canvas.winfo_x())
        print('self.frame_for_canvas.winfo_y()', self.frame_for_canvas.winfo_y())

        print('self.frm_right_panel.winfo_geometry()', self.frm_right_panel.winfo_geometry())
        print('self.frm_right_panel.winfo_height()', self.frm_right_panel.winfo_height())
        print('self.frm_right_panel.winfo_width()', self.frm_right_panel.winfo_width())
        print('self.frm_right_panel.winfo_x()', self.frm_right_panel.winfo_x())
        print('self.frm_right_panel.winfo_y()', self.frm_right_panel.winfo_y())

        print('self.frm_header1.winfo_geometry()', self.frm_header1.winfo_geometry())
        print('self.frm_header1.winfo_height()', self.frm_header1.winfo_height())
        print('self.frm_header1.winfo_width()', self.frm_header1.winfo_width())
        print('self.frm_header1.winfo_x()', self.frm_header1.winfo_x())
        print('self.frm_header1.winfo_y()', self.frm_header1.winfo_y())

        print('self.frm_header2.winfo_geometry()', self.frm_header2.winfo_geometry())
        print('self.frm_header2.winfo_height()', self.frm_header2.winfo_height())
        print('self.frm_header2.winfo_width()', self.frm_header2.winfo_width())
        print('self.frm_header2.winfo_x()', self.frm_header2.winfo_x())
        print('self.frm_header2.winfo_y()', self.frm_header2.winfo_y())



if __name__ == '__main__':
    root = tk.Tk()
    app = Application(root)
    root.mainloop()

The code creates this window:

enter image description here

At the same time, it prints this on my console:

self.root.winfo_geometry() 800x600+208+208
self.root.winfo_height() 600
self.root.winfo_width() 800
self.root.winfo_x() 208
self.root.winfo_y() 208
self.frame_for_canvas.winfo_geometry() 1x1+0+0      <--- weird
self.frame_for_canvas.winfo_height() 1
self.frame_for_canvas.winfo_width() 1
self.frame_for_canvas.winfo_x() 0
self.frame_for_canvas.winfo_y() 0
self.frm_right_panel.winfo_geometry() 1x1+0+0
self.frm_right_panel.winfo_height() 1
self.frm_right_panel.winfo_width() 1
self.frm_right_panel.winfo_x() 0
self.frm_right_panel.winfo_y() 0
self.frm_header1.winfo_geometry() 1x1+0+0      <--- weird
self.frm_header1.winfo_height() 1
self.frm_header1.winfo_width() 1
self.frm_header1.winfo_x() 0
self.frm_header1.winfo_y() 0
self.frm_header2.winfo_geometry() 1x1+0+0      <--- weird
self.frm_header2.winfo_height() 1
self.frm_header2.winfo_width() 1
self.frm_header2.winfo_x() 0
self.frm_header2.winfo_y() 0

Notice how, except for root, the dimensions are incorrect.

However, when I click on any of the 3 buttons, the corresponding labels below them are updated with the correct dimensions of the frames. If I resize the window and click the buttons, they are updated as well.

enter image description here

So if I call self.root.winfo_geometry(), self.root.winfo_width(), etc. after packing and displaying the widgets, the returned dimensions/values are not correct. Only after clicking the buttons and calling f_btn1(), f_btn2(), f_btn3() are these values displayed correctly.

According to the documentation here,

w.winfo_geometry() Returns the geometry string describing the size and on-screen location of w. See Section 5.10, “Geometry strings”.

Warning The geometry is not accurate until the application has updated its idle tasks. In particular, all geometries are initially '1x1+0+0' until the widgets and geometry manager have negotiated their sizes and positions. See the .update_idletasks() method, above, in this section to see how to insure that the widget's geometry is up to date.

That's why I am calling update_idletasks() inside print_dimensions(), but winfo_geometry() still returns 1x1+0+0.

Thanks in advance.


Solution

  • As suggested by @TheLizzard, I changed update_idletasks() for update()and it works.

    There seems to be a problem on Windows because @TheLizzard tested the original code on Ubuntu and it worked.