pythontkintertkinter-canvaspython-3.13

Child widgets not shown correctly if parent Frame is gridded later


I have an application that divides the main window in two:

enter image description here

In the previous screenshot, the data frame can be seen on the right side of the window, with information about a vehicle, and a vertical scrollbar to accommodate all the possible vehicles and people that share the same coordinates as the selected vehicle.

If I click on a person's name, the right frame changes (by means of tkraise()) and we can see this:

enter image description here

I can cycle through the list of vehicles and people, clicking on Next or Previous, and I can also see person and vehicle information by clicking on the colored labels.

As can be seen in the example code I am pasting below, this is working because I am gridding all the right-side frames at the beginning of the program. The example is stripped to a minimum working program, but in reality I have a few different frames to display on the right side, and if I grid them all at the beginning, I can see how each of them is drawn, until the last one to be drawn remains on top. This behaviour of flashing all the widgets until the last frame is shown is not very appealing.

So I thought I should only grid() one of the frames at the beginning, and the rest would be gridded (and raised) the first time they are visited. But my problem is that, if I don't grid() them at the beginning, the list of items sharing coordinates is not properly shown:

enter image description here

But something small can be seen in the scrollable frame.

The only difference is that I am gridding the frames later, not at the beginning.

Here is the code:

import tkinter as tk
from tkinter import ttk

class App:
    def __init__(self):
        self.root = tk.Tk()

        self.root.minsize(width=800, height=400)
        self.root.maxsize(width=800, height=400)

        self.style = ttk.Style(self.root)
        self.style.configure('Left.TFrame', background='#DBD9D9')
        self.style.configure('Right.TFrame', background='#EDE8E8')

        # Active data info frame (right side)
        self.active_data_frame = None
        self.current_person = 0
        self.current_vehicle = 0

        # Frames
        self.root.rowconfigure(0, weight=1)
        self.root.columnconfigure(0, weight=0, minsize=400)
        self.root.columnconfigure(1, weight=1)

        self.left = ttk.Frame(self.root, style='Left.TFrame')
        self.left.grid(row=0, column=0, sticky='nswe')
        self.left.rowconfigure(0, weight=1)
        self.left.columnconfigure(0, weight=1)

        self.right = ttk.Frame(self.root, style='Right.TFrame')
        self.right.grid(row=0, column=1, sticky='nswe')
        self.right.rowconfigure(0, weight=1)
        self.right.columnconfigure(0, weight=1)

        # People and vehicles have different ids
        self.people = [
            {'id': 1001, 'birth': '01/02/1980', 'name': 'Helen', 'surname': 'Doe', 'weight': 66, 'height': 171, 'profession': 'Doctor', 'Coordinates': (100, 100)},
            {'id': 1002, 'birth': '01/03/1981', 'name': 'Alicia', 'surname': 'Smith', 'weight': 55, 'height': 150, 'profession': 'Teacher', 'Coordinates': (200, 200)},
            {'id': 1003, 'birth': '01/06/1982', 'name': 'Laura', 'surname': 'Morgan', 'weight': 68, 'height': 175, 'profession': 'Attorney', 'Coordinates': (140, 170)},
            {'id': 1004, 'birth': '01/09/1983', 'name': 'Susan', 'surname': 'S. M.', 'weight': 76, 'height': 166, 'profession': 'Engineer', 'Coordinates': (205, 120)},
            {'id': 1005, 'birth': '01/12/1984', 'name': 'John', 'surname': 'Doe2', 'weight': 63, 'height': 171, 'profession': 'Footballer', 'Coordinates': (205, 120)},
            {'id': 1006, 'birth': '01/05/1985', 'name': 'Doug', 'surname': 'Williams', 'weight': 66, 'height': 160, 'profession': 'Fisherman', 'Coordinates': (200, 200)},
            {'id': 1007, 'birth': '01/08/1986', 'name': 'Frank', 'surname': 'Frankenstein', 'weight': 70, 'height': 188, 'profession': 'Fireman', 'Coordinates': (205, 120)}
            ]
        self.vehicles = [
            {'id': 8001, 'plates': 'ABC123', 'make': 'Ford', 'model': 'Mondeo', 'color': 'red', 'Coordinates': (200, 200)},
            {'id': 8002, 'plates': 'JBN134', 'make': 'Renault', 'model': 'Clio', 'color': 'blue', 'Coordinates': (100, 100)},
            {'id': 8003, 'plates': 'JBN251', 'make': 'Fiat', 'model': 'Marea', 'color': 'red', 'Coordinates': (205, 120)},
            {'id': 8004, 'plates': 'JSN368', 'make': 'Cadillac', 'model': 'Seville', 'color': 'orange', 'Coordinates': (140, 170)},
            {'id': 8005, 'plates': 'JSN485', 'make': 'Tesla', 'model': 'S', 'color': 'black', 'Coordinates': (100, 100)},
            {'id': 8006, 'plates': 'JSC602', 'make': 'Ford', 'model': 'Mustang', 'color': 'red', 'Coordinates': (200, 200)},
            {'id': 8007, 'plates': 'JSC719', 'make': 'BMW', 'model': '320i', 'color': 'yellow', 'Coordinates': (205, 120)}
            ]

        self.objects_at_xy = {}
        for item in self.people:
            coords = item['Coordinates']
            if coords in self.objects_at_xy:
                self.objects_at_xy[coords].append(('p', item['id'], item['name'], item['surname']))
            else:
                self.objects_at_xy[coords] = [('p', item['id'], item['name'], item['surname'])]

        for item in self.vehicles:
            coords = item['Coordinates']
            if coords in self.objects_at_xy:
                self.objects_at_xy[coords].append(('v', item['id'], item['plates'], item['make'], item['model']))
            else:
                self.objects_at_xy[coords] = [('v', item['id'], item['plates'], item['make'], item['model'])]


        # Initialize different viewers
        self.peopleviewer = PeopleViewer(self)
        self.vehicleviewer = VehicleViewer(self)



class PeopleViewer:
    """Class for displaying and handling people data."""

    def __init__(self, app):
        self.app = app

        self.frame = ttk.Frame(self.app.right)
        # Only grid when calling update()
        self.frame.grid(row=0, column=0, sticky='nswe')

        self.frame.rowconfigure((0,1,2,3,4), weight=0, uniform='frame_r')
        self.frame.rowconfigure(5, weight=1)
        self.frame.columnconfigure((0,1,2,3), weight=0, uniform='frame_c')

        self.button_next = ttk.Button(self.frame, text='Next person', command=self.next_element)
        self.button_prev = ttk.Button(self.frame, text='Previous person', command=self.prev_element)
        self.button_next.grid(row=0, column=0, columnspan=2, sticky='NSWE')
        self.button_prev.grid(row=0, column=2, columnspan=2, sticky='NSWE')

        self.label_id = ttk.Label(self.frame, text="ID", relief=tk.GROOVE)
        self.label_id_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_id.grid(row=1, column=0, sticky='NSWE')
        self.label_id_val.grid(row=1, column=1, sticky='NSWE')

        self.label_birth_date = ttk.Label(self.frame, text="Birth date", relief=tk.GROOVE)
        self.label_birth_date_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_birth_date.grid(row=1, column=2, sticky='NSWE')
        self.label_birth_date_val.grid(row=1, column=3, sticky='NSWE')

        self.label_name = ttk.Label(self.frame, text="Name", relief=tk.GROOVE)
        self.label_name_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_name.grid(row=2, column=0, sticky='NSWE')
        self.label_name_val.grid(row=2, column=1, sticky='NSWE')

        self.label_surname = ttk.Label(self.frame, text="Surname", relief=tk.GROOVE)
        self.label_surname_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_surname.grid(row=2, column=2, sticky='NSWE')
        self.label_surname_val.grid(row=2, column=3, sticky='NSWE')

        self.label_weight = ttk.Label(self.frame, text="Weight", relief=tk.GROOVE)
        self.label_weight_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_weight.grid(row=3, column=0, sticky='NSWE')
        self.label_weight_val.grid(row=3, column=1, sticky='NSWE')

        self.label_height = ttk.Label(self.frame, text="Height", relief=tk.GROOVE)
        self.label_height_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_height.grid(row=3, column=2, sticky='NSWE')
        self.label_height_val.grid(row=3, column=3, sticky='NSWE')

        self.label_profession = ttk.Label(self.frame, text="Profession", relief=tk.GROOVE)
        self.label_profession_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_profession.grid(row=4, column=0, sticky='NSWE')
        self.label_profession_val.grid(row=4, column=1, sticky='NSWE')

        self.label_coordinates = ttk.Label(self.frame, text="Coordinates", relief=tk.GROOVE)
        self.label_coordinates_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_coordinates.grid(row=4, column=2, sticky='NSWE')
        self.label_coordinates_val.grid(row=4, column=3, sticky='NSWE')

        self.frame_elements = ttk.Frame(self.frame)
        self.frame_elements.grid(row=5, column=0, columnspan=4, sticky='NSWE', pady=(3, 3))

        # Vertical scroll implemented as in
        # https://stackoverflow.com/questions/3085696/adding-a-scrollbar-to-a-group-of-widgets-in-tkinter/3092341#3092341
        self.canvas_in_frame_elements = tk.Canvas(self.frame_elements, borderwidth=0)
        self.frame_inside_canvas = ttk.Frame(self.canvas_in_frame_elements)
        self.frame_inside_canvas.columnconfigure((0, 2), weight=0, minsize=20)
        self.frame_inside_canvas.columnconfigure((1, 3), weight=1, uniform='elements')
        self.vsb = ttk.Scrollbar(self.frame_elements, orient="vertical", command=self.canvas_in_frame_elements.yview)
        self.canvas_in_frame_elements.configure(yscrollcommand=self.vsb.set)
        self.vsb.pack(side="right", fill="y")
        self.canvas_in_frame_elements.pack(side="left", fill="both", expand=True)
        self.canvas_in_frame_elements.update()
        w = self.canvas_in_frame_elements.winfo_width()
        self.canvas_in_frame_elements.create_window((0, 0), window=self.frame_inside_canvas, anchor="nw", width=w)
        self.frame_inside_canvas.bind("<Configure>", self.onFrameConfigure)


    def onFrameConfigure(self, event) -> None:
        """Reset the scroll region to encompass the inner frame."""

        self.canvas_in_frame_elements.configure(scrollregion=self.canvas_in_frame_elements.bbox("all"))


    def next_element(self) -> None:
        """Iterates forward in the list of elements."""

        self.app.current_person += 1
        if self.app.current_person == len(self.app.people):
            self.app.current_person = 0

        person = self.app.people[self.app.current_person]
        self.update(person)


    def prev_element(self) -> None:
        """Iterates backward in the list of elements."""

        self.app.current_person -= 1
        if self.app.current_person < 0:
            self.app.current_person = len(self.app.people) - 1

        person = self.app.people[self.app.current_person]
        self.update(person)


    def update(self, person) -> None:
        """Updates the frame with a person's info."""

        if not self.frame.winfo_ismapped():
            self.frame.grid(row=0, column=0, sticky='nswe')
            print('PeopleViewer gridded')
        if self.app.active_data_frame != self:
            self.frame.tkraise()
            self.app.active_data_frame = self
            print('PeopleViewer raised')

        # Update labels' content
        self.label_id_val['text'] = person['id']
        self.label_birth_date_val['text'] = person['birth']
        self.label_name_val['text'] = person['name']
        self.label_surname_val['text'] = person['surname']
        self.label_weight_val['text'] = person['weight']
        self.label_height_val['text'] = person['height']
        self.label_profession_val['text'] = person['profession']
        x, y = person['Coordinates']
        self.label_coordinates_val['text'] = f'{x},{y}'

        # Destroy previous labels (if any) from the scrollable frame
        for element in self.frame_inside_canvas.winfo_children():
            element.destroy()

        self.person_labels = {}
        current_row = 0
        column = 0
        # Find objects at this coordinates
        for obj in self.app.objects_at_xy[person['Coordinates']]:
            if obj[0] == 'p':
                background = '#7cf576'
                text = f'{obj[2]} {obj[3]}'
            elif obj[0] == 'v':
                background = '#d3e35b'
                text = f'{obj[2]} - {obj[3]} {obj[4]}'

            label_id = ttk.Label(self.frame_inside_canvas, text=str(obj[1]), borderwidth=1, relief="ridge", anchor=tk.CENTER, background=background)
            label_info = ttk.Label(self.frame_inside_canvas, text=text, borderwidth=1, relief="ridge", anchor=tk.W, background=background)

            if column == 1:
                id1 = label_id.grid(row=current_row, column=2, sticky='NSWE')
                id2 = label_info.grid(row=current_row, column=3, sticky='NSWE')
            else:
                id1 = label_id.grid(row=current_row, column=0, sticky='NSWE')
                id2 = label_info.grid(row=current_row, column=1, sticky='NSWE')

            label_info.bind('<Enter>', self.label_enter)
            label_info.bind('<Leave>', self.label_leave)
            label_info.bind('<Button-1>', self.label_click)

            self.person_labels[label_info] = label_id

            column+= 1
            if column== 2:
                current_row += 1
                column= 0


    def label_enter(self, event):
        self.current_cursor = event.widget['cursor']
        event.widget['cursor'] = 'hand2'


    def label_leave(self, event):
        event.widget['cursor'] = self.current_cursor


    def label_click(self, event):
        element_id = int(self.person_labels[event.widget]['text'])

        # Show data about the clicked person
        found = False
        for person in self.app.people:
            if person['id'] == element_id:
                found = True
                break

        if not found:
            for vehicle in self.app.vehicles:
                if vehicle['id'] == element_id:
                    break

        # TO DO: "current" must change accordingly
        if element_id > 8000:
            self.app.vehicleviewer.update(vehicle)
        else:
            self.update(person)


class VehicleViewer:
    """Class for displaying and handling vehicle data."""

    def __init__(self, app):
        self.app = app

        self.frame = ttk.Frame(self.app.right)
        # Only grid when calling update()
        self.frame.grid(row=0, column=0, sticky='nswe')

        self.frame.rowconfigure((0,1,2,3), weight=0, uniform='frame_r')
        self.frame.rowconfigure(4, weight=1)
        self.frame.columnconfigure((0,1,2,3), weight=0, uniform='frame_c')

        self.button_next = ttk.Button(self.frame, text='Next vehicle', command=self.next_element)
        self.button_prev = ttk.Button(self.frame, text='Previous vehicle', command=self.prev_element)
        self.button_next.grid(row=0, column=0, columnspan=2, sticky='NSWE')
        self.button_prev.grid(row=0, column=2, columnspan=2, sticky='NSWE')

        self.label_id = ttk.Label(self.frame, text="ID", relief=tk.GROOVE)
        self.label_id_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_id.grid(row=1, column=0, sticky='NSWE')
        self.label_id_val.grid(row=1, column=1, sticky='NSWE')

        self.label_plates = ttk.Label(self.frame, text="Plates", relief=tk.GROOVE)
        self.label_plates_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_plates.grid(row=1, column=2, sticky='NSWE')
        self.label_plates_val.grid(row=1, column=3, sticky='NSWE')

        self.label_make = ttk.Label(self.frame, text="Make", relief=tk.GROOVE)
        self.label_make_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_make.grid(row=2, column=0, sticky='NSWE')
        self.label_make_val.grid(row=2, column=1, sticky='NSWE')

        self.label_model = ttk.Label(self.frame, text="Model", relief=tk.GROOVE)
        self.label_model_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_model.grid(row=2, column=2, sticky='NSWE')
        self.label_model_val.grid(row=2, column=3, sticky='NSWE')

        self.label_color = ttk.Label(self.frame, text="Color", relief=tk.GROOVE)
        self.label_color_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_color.grid(row=3, column=0, sticky='NSWE')
        self.label_color_val.grid(row=3, column=1, sticky='NSWE')

        self.label_coordinates = ttk.Label(self.frame, text="Coordinates", relief=tk.GROOVE)
        self.label_coordinates_val = ttk.Label(self.frame, relief=tk.GROOVE)
        self.label_coordinates.grid(row=3, column=2, sticky='NSWE')
        self.label_coordinates_val.grid(row=3, column=3, sticky='NSWE')

        self.frame_elements = ttk.Frame(self.frame)
        self.frame_elements.grid(row=4, column=0, columnspan=4, sticky='NSWE', pady=(3, 3))

        # Vertical scroll implemented as in
        # https://stackoverflow.com/questions/3085696/adding-a-scrollbar-to-a-group-of-widgets-in-tkinter/3092341#3092341
        self.canvas_in_frame_elements = tk.Canvas(self.frame_elements, borderwidth=0)
        self.frame_inside_canvas = ttk.Frame(self.canvas_in_frame_elements)
        self.frame_inside_canvas.columnconfigure((0, 2), weight=0, minsize=20)
        self.frame_inside_canvas.columnconfigure((1, 3), weight=1, uniform='elements')
        self.vsb = ttk.Scrollbar(self.frame_elements, orient="vertical", command=self.canvas_in_frame_elements.yview)
        self.canvas_in_frame_elements.configure(yscrollcommand=self.vsb.set)
        self.vsb.pack(side="right", fill="y")
        self.canvas_in_frame_elements.pack(side="left", fill="both", expand=True)
        self.canvas_in_frame_elements.update()
        w = self.canvas_in_frame_elements.winfo_width()
        self.canvas_in_frame_elements.create_window((0, 0), window=self.frame_inside_canvas, anchor="nw", width=w)
        self.frame_inside_canvas.bind("<Configure>", self.onFrameConfigure)

        # Start with the first vehicle
        vehicle = self.app.vehicles[self.app.current_vehicle]
        self.update(vehicle)


    def onFrameConfigure(self, event) -> None:
        """Reset the scroll region to encompass the inner frame."""

        self.canvas_in_frame_elements.configure(scrollregion=self.canvas_in_frame_elements.bbox("all"))


    def next_element(self) -> None:
        """Iterates forward in the list of elements."""

        self.app.current_vehicle += 1
        if self.app.current_vehicle == len(self.app.vehicles):
            self.app.current_vehicle = 0

        vehicle = self.app.vehicles[self.app.current_vehicle]
        self.update(vehicle)


    def prev_element(self) -> None:
        """Iterates backward in the list of elements."""

        self.app.current_vehicle -= 1
        if self.app.current_vehicle < 0:
            self.app.current_vehicle = len(self.app.vehicles) - 1

        vehicle = self.app.vehicles[self.app.current_vehicle]
        self.update(vehicle)


    def update(self, vehicle) -> None:
        """Updates the frame with a vehicle's info."""

        if not self.frame.winfo_ismapped():
            self.frame.grid(row=0, column=0, sticky='nswe')
            print('VehicleViewer gridded')
        if self.app.active_data_frame != self:
            self.frame.tkraise()
            self.app.active_data_frame = self
            print('VehicleViewer raised')

        # Update labels's info
        self.label_id_val['text'] = vehicle['id']
        self.label_plates_val['text'] = vehicle['plates']
        self.label_make_val['text'] = vehicle['make']
        self.label_model_val['text'] = vehicle['model']
        self.label_color_val['text'] = vehicle['color']
        x, y = vehicle['Coordinates']
        self.label_coordinates_val['text'] = f'{x},{y}'

        # Destroy previous labels (if any) from the scrollable frame
        for element in self.frame_inside_canvas.winfo_children():
            element.destroy()

        self.vehicle_labels = {}
        current_row = 0
        column = 0
        # Find objects at this coordinates
        for obj in self.app.objects_at_xy[vehicle['Coordinates']]:
            if obj[0] == 'p':
                background = '#7cf576'
                text = f'{obj[2]} {obj[3]}'
            elif obj[0] == 'v':
                background = '#d3e35b'
                text = f'{obj[2]} - {obj[3]} {obj[4]}'

            label_id = ttk.Label(self.frame_inside_canvas, text=str(obj[1]), borderwidth=1, relief="ridge", anchor=tk.CENTER, background=background)
            label_info = ttk.Label(self.frame_inside_canvas, text=text, borderwidth=1, relief="ridge", anchor=tk.W, background=background)

            if column == 1:
                id1 = label_id.grid(row=current_row, column=2, sticky='NSWE')
                id2 = label_info.grid(row=current_row, column=3, sticky='NSWE')
            else:
                id1 = label_id.grid(row=current_row, column=0, sticky='NSWE')
                id2 = label_info.grid(row=current_row, column=1, sticky='NSWE')

            label_info.bind('<Enter>', self.label_enter)
            label_info.bind('<Leave>', self.label_leave)
            label_info.bind('<Button-1>', self.label_click)

            self.vehicle_labels[label_info] = label_id

            column+= 1
            if column== 2:
                current_row += 1
                column= 0


        self.frame.update_idletasks()
        print('self.frame_inside_canvas.winfo_children')
        for widget in self.frame_inside_canvas.winfo_children():
            print(widget, widget.winfo_ismapped())


    def label_enter(self, event):
        self.current_cursor = event.widget['cursor']
        event.widget['cursor'] = 'hand2'


    def label_leave(self, event):
        event.widget['cursor'] = self.current_cursor


    def label_click(self, event):
        element_id = int(self.vehicle_labels[event.widget]['text'])
        print('Clicked', element_id)

        # Show data about the clicked item
        found = False
        for vehicle in self.app.vehicles:
            if vehicle['id'] == element_id:
                found = True
                break

        if not found:
            for person in self.app.people:
                if person['id'] == element_id:
                    break

        # TO DO: "current" must change accordingly
        if element_id > 8000:
            self.update(vehicle)
        else:
            self.app.peopleviewer.update(person)



if __name__ == "__main__":
    app = App()
    app.root.mainloop()

In my real program, the list of people and vehicles (and pets, and cities, etc.) is not fixed, but dynamic, so that's why I need a vertical scroll, because many objects can share the same coordinates. It could be a Listbox instead of a Canvas (and frame inside the Canvas), but I want to use that 4-column layout inside, and also icons in the labels, so I chose the Canvas, as explained here.

Please pay attention to

self.frame.grid(row=0, column=0, sticky='nswe')

in lines 85 and 282.

If I comment one or both lines, I get the strange behaviour I showed in the picture, despite the fact that the frame is gridded in lines 187 and 378 when they are first visited. I check if the frame is gridded using .winfo_ismapped() . And, if the frame is not the active one, I raise it.

If I insert this at the end of VehicleViewer.update():

self.frame.update_idletasks()
print('self.frame_inside_canvas.winfo_children')
for widget in self.frame_inside_canvas.winfo_children():
    print(widget, widget.winfo_ismapped())

I see this when I grid() the frame at the beginning:

.!frame2.!frame2.!frame.!canvas.!frame.!label 1
.!frame2.!frame2.!frame.!canvas.!frame.!label2 1
.!frame2.!frame2.!frame.!canvas.!frame.!label3 1
.!frame2.!frame2.!frame.!canvas.!frame.!label4 1
.!frame2.!frame2.!frame.!canvas.!frame.!label5 1
.!frame2.!frame2.!frame.!canvas.!frame.!label6 1
.!frame2.!frame2.!frame.!canvas.!frame.!label7 1
.!frame2.!frame2.!frame.!canvas.!frame.!label8 1

but this when I grid() it later (that is, when I comment line 282) and visit the frame:

.!frame2.!frame2.!frame.!canvas.!frame.!label 1
.!frame2.!frame2.!frame.!canvas.!frame.!label2 0
.!frame2.!frame2.!frame.!canvas.!frame.!label3 1
.!frame2.!frame2.!frame.!canvas.!frame.!label4 0
.!frame2.!frame2.!frame.!canvas.!frame.!label5 1
.!frame2.!frame2.!frame.!canvas.!frame.!label6 0
.!frame2.!frame2.!frame.!canvas.!frame.!label7 1
.!frame2.!frame2.!frame.!canvas.!frame.!label8 0

The zeroes mean that not all labels have been gridded properly here:

            label_id = ttk.Label(self.frame_inside_canvas, text=str(obj[1]), borderwidth=1, relief="ridge", anchor=tk.CENTER, background=background)
            label_info = ttk.Label(self.frame_inside_canvas, text=text, borderwidth=1, relief="ridge", anchor=tk.W, background=background)

            if column == 1:
                id1 = label_id.grid(row=current_row, column=2, sticky='NSWE')
                id2 = label_info.grid(row=current_row, column=3, sticky='NSWE')
            else:
                id1 = label_id.grid(row=current_row, column=0, sticky='NSWE')
                id2 = label_info.grid(row=current_row, column=1, sticky='NSWE')

and I don't know why.

All the labels and buttons are shown correctly if the frames are gridded later, but not the content of the scroll area.

I am using Python 3.13.7 on Windows 11.


Solution

  • The problem lies in the variable w. When the frame is gridded in __init__(), w=382, but when it is gridded later, w=1.

    The solution is to create the window inside the canvas after the frame has been gridded.

    Both in PeopleViewer.__init__() and VehicleViewer.__init__(), comment these 3 lines:

            self.canvas_in_frame_elements = tk.Canvas(self.frame_elements, borderwidth=0)
            self.frame_inside_canvas = ttk.Frame(self.canvas_in_frame_elements)
            self.frame_inside_canvas.columnconfigure((0, 2), weight=0, minsize=20)
            self.frame_inside_canvas.columnconfigure((1, 3), weight=1, uniform='elements')
            self.vsb = ttk.Scrollbar(self.frame_elements, orient="vertical", command=self.canvas_in_frame_elements.yview)
            self.canvas_in_frame_elements.configure(yscrollcommand=self.vsb.set)
            self.vsb.pack(side="right", fill="y")
            self.canvas_in_frame_elements.pack(side="left", fill="both", expand=True)
            # self.canvas_in_frame_elements.update()
            # w = self.canvas_in_frame_elements.winfo_width()
            # self.canvas_in_frame_elements.create_window((0, 0), window=self.frame_inside_canvas, anchor="nw", width=w)
            self.frame_inside_canvas.bind("<Configure>", self.onFrameConfigure)
    

    Then, in PeopleViewer.update(), substitute

            if not self.frame.winfo_ismapped():
                self.frame.grid(row=0, column=0, sticky='nswe')
                print('PeopleViewer gridded')
    

    with

            if not self.frame.winfo_ismapped():
                self.frame.grid(row=0, column=0, sticky='nswe')
                print('PeopleViewer gridded')
    
                self.canvas_in_frame_elements.update()
                w = self.canvas_in_frame_elements.winfo_width()
                self.canvas_in_frame_elements.create_window((0, 0), window=self.frame_inside_canvas, anchor="nw", width=w)
    

    Which is the same as moving the 3 lines from __init__() to update().

    And the same for and VehicleViewer.update().

    Thanks @acw1668 for the heads up.