Cosmineer
Cosmineer

Reputation: 23

How to fix GTK Scrolled Window not updating scroll bars after contents have changed?

I wish to have an image in my GTK app that continually resizes to fit its parent container.

I've accomplished this by getting the parent container's size inside a size-allocate event callback, and resizing my image according to those dimensions. This works fine when I'm making the window smaller, but when I want to make it bigger, it refuses to resize because it has to be at least as big as the contents (the image).

To overcome that aspect, I've placed the image in a ScrolledWindow so that I can freely resize my window smaller.

The issue lies in that when I switch the image shown to one with different dimensions, the ScrolledWindow doesn't seem to realize it, and I'm left with a ScrolledWindow with the wrong content size and unnecessary scroll bars. But alas, I can hover over the scroll bar and it realizes that it's too big for its content and removes the scroll bars. See the below demonstration.

Can I somehow have this "correction" behavior happen right away instead of when I hover over the scroll bars?

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository import GdkPixbuf


class Minimal(Gtk.Window):


    imageShown = 0
    img = Gtk.Image.new()
    pixbufRed = GdkPixbuf.Pixbuf.new_from_file("kirby_red.png")
    pixbufBlue = GdkPixbuf.Pixbuf.new_from_file("kirby_blue.png")
    pixbuf = None


    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_default_size(400,300)

        button = Gtk.Button.new_with_label("Swap Image")
        button.connect("clicked", self.on_button_click)

        self.pixbuf = self.pixbufRed
        self.img.set_from_pixbuf(self.pixbuf)

        scrolled = Gtk.ScrolledWindow()
        scrolled.connect("size-allocate", self.on_size_allocated);
        scrolled.add(self.img)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,spacing=0)
        box.pack_start(button, False, False, 0)
        box.pack_end(scrolled, True, True, 0)

        self.add(box)


    #swap image shown using imageShown flag to keep track
    def on_button_click(self, button):
        if(self.imageShown == 0):
            self.pixbuf = self.pixbufBlue
            self.imageShown = 1
        else:
            self.pixbuf = self.pixbufRed
            self.imageShown = 0
        self.img.set_from_pixbuf(self.pixbuf)


    def on_size_allocated(self, widget, allocation):
        scaledPixbuf = Minimal.scale_image_from_allocation_keep_aspect(self.pixbuf, allocation)
        self.img.set_from_pixbuf(scaledPixbuf)


    @staticmethod
    def scale_image_from_allocation_keep_aspect(pixbuf, allocation):
        imgWidth = pixbuf.get_width()
        imgHeight = pixbuf.get_height()

        parentWidth = allocation.width
        parentHeight = allocation.height

        aspectWidth = parentWidth/imgWidth
        aspectHeight= parentHeight/imgHeight

        aspect=0

        if(aspectWidth < aspectHeight):
            aspect = aspectWidth
        else:
            aspect = aspectHeight

        newWidth = imgWidth*aspect
        newHeight = imgHeight*aspect

        return pixbuf.scale_simple(newWidth, newHeight, GdkPixbuf.InterpType.BILINEAR)


win = Minimal()
win.show_all()
Gtk.main()

video demonstration of problem

Upvotes: 1

Views: 656

Answers (1)

James Westman
James Westman

Reputation: 2690

size-allocate isn't really the right place to be changing the contents of your widget (like changing the image widget's pixbuf), and it usually doesn't work correctly if you try to use it like that. It's intended more for custom container widgets to layout their children once the size is already determined.

In GTK 3, I usually solve the problem of making images fill the available space by creating a very simple custom widget, like this:

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GdkPixbuf, Gdk

class ScaleImage(Gtk.DrawingArea):
    def __init__(self, pixbuf):
        Gtk.DrawingArea.__init__(self)
        self.pixbuf = pixbuf

    def do_draw(self, cr):
        alloc, baseline = self.get_allocated_size()
        factor = min(alloc.width / self.pixbuf.get_width(), alloc.height / self.pixbuf.get_height())
        cr.scale(factor, factor)

        Gdk.cairo_set_source_pixbuf(cr, self.pixbuf, 0, 0)
        cr.paint()

win = Gtk.Window()
img = ScaleImage(GdkPixbuf.Pixbuf.new_from_file("file.png"))
win.add(img)
win.show_all()

Gtk.main()

I haven't tried it yet, but in GTK 4 you should be able to use Gtk.Picture to get the same effect without a custom widget.

Upvotes: 1

Related Questions