How do I know when the preferred size of a widget is updated upon updating CSS providers?

I have the following PyGObject code:


import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk, GLib

class FontSizeApp(Gtk.Application):
    def __init__(self):
        super().__init__(application_id="com.example.FontSizeApp")

    def do_activate(self):

        window = Gtk.ApplicationWindow(application=app)
        window.set_title("Font Size Experiment")
        window.set_default_size(400, 100)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin_top=20, margin_bottom=20, margin_start=20, margin_end=20)
        window.set_child(vbox)

        button = Gtk.Button(label="Enlarge Text")
        vbox.append(button)

        self.entry = Gtk.Entry()
        self.entry.set_name("entry1")
        vbox.append(self.entry)

        # Connect button signal
        button.connect("clicked", self.on_button_clicked)

        window.present()

    def on_button_clicked(self, button):
        css = """
        #entry1 {
            font-size: 30pt;
        }
        """
        style_provider = Gtk.CssProvider()
        style_provider.load_from_data(css.encode())
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(),
            style_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

# Tried the following but does not work
        # self.entry.queue_resize()
# GLib.idle_add below works intermittently
        GLib.idle_add(self._print)
        
    def _print(self):
        min_size, nat_size = self.entry.get_preferred_size()
        print(f"Preferred height: {nat_size.height}")

if __name__ == "__main__":
    from gi.repository import Gdk
    app = FontSizeApp()
    app.run()

where I use GTK4 to add an Entry widget, and then later, when a button is pressed, change its text size.

In the code I’m working on, we have a custom LayoutManager, so we must know the preferred size of the widget immediately (or a short while, but we need to know when) after I apply the Gtk Style provider. I then need to know the preferred height of the widget or when it’s ready and updated – I’ve tried using GLib.idle_add, that does work sometimes, but sometimes it does not (it prints 34 when it’s supposed to be 45). I’ve also tried to queue a resize, as shown in the code – so after applying CSS to a Entry widget, when can I know its new updated preferred height?

Thanks!

First of all, please don’t use Widget.get_preferred_size(), like, at all. That method appears to do something useful, but actually it does that wrong, and you always want to call .measure() yourself instead.

Secondly, a resize will be queued on the entry (or any other widget affected) automatically when you change the CSS style (that affects sizing). You don’t need to call .queue_resize() externally. .queue_resize() is something that you call on your own widget (i.e. self) when your own internal state (that affects sizing) changes. For example, when a label’s text is updated, it will call self.queue_resize().

But note that you should be looking at the measurements in the layout phase, and not in the events phase! In particular, your button click handler gets run in the events phase, so it’s too early to measure the entry at that point, you have to wait till the layout phase. Read more about phases here.

If you’re writing a layout manager (as in, a class derived from Gtk.LayoutManager), then you should be looking at the entry’s measurements (i.e. what entry.measure() returns) inside your do_measure() and do_allocate() virtual methods; and indeed those are run in the layout phase.

Can you describe your use case / your layout manager in more details?

Solved – thanks! We’re caching the preferred sizes in our layout container, but then another internal calculation we did measured the dirty widgets and cleared the cache, so the measurement ended up not occuring in the layout phase.

GTK already caches measurements, and widget.measure() calls are already served from that cache, so you shouldn’t have to implement your own layer of caching. Moreover, it’s problematic to implement your own cache, because you need to know precisely when to invalidate it, and GTK doesn’t really give you a good indication of that. So please just call .measure() and let GTK handle the caching.

But yes, any measurements you do (including your internal calculation if it’s based on measurements) should happen in the layout phase (or later). Exactly because the measurements are stale/broken/irrelevant before that; it’s during the layout phase that measurements become stable/correct for the upcoming frame being prepared.

@bugaevc Thanks for the additional guidance. I’ll try to follow these.