如何修复 GTK 滚动窗口在内容更改后不更新滚动条?

问题描述

我希望在我的 GTK 应用中有一个图像,它会不断调整大小以适合其父容器

我通过在 size-allocate 事件回调中获取父容器的大小并根据这些尺寸调整我的图像大小来实现这一点。当我将窗口变小时,这可以正常工作,但是当我想让它变大时,它拒绝调整大小,因为它必须至少与内容(图像)一样大。

为了克服这个问题,我将图像放置在 ScrolledWindow 中,以便我可以自由地将窗口缩小。

问题在于,当我将显示的图像切换为具有不同尺寸的图像时,ScrolledWindow 似乎没有意识到这一点,并且留下了内容大小错误的 ScrolledWindow 和不必要的滚动条。但是,唉,我可以将鼠标悬停在滚动条上,它意识到它对于其内容来说太大了,并删除了滚动条。请参阅下面的演示。

我能否以某种方式让这种“更正”行为立即发生,而不是在我将鼠标悬停在滚动条上时发生?

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,0)
        Box.pack_end(scrolled,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

解决方法

size-allocate 并不是更改小部件内容的正确位置(例如更改图像小部件的 pixbuf),如果您尝试这样使用它,它通常无法正常工作。自定义容器小部件的目的更多是在尺寸确定后为其子部件进行布局。

在 GTK 3 中,我通常通过创建一个非常简单的自定义小部件来解决使图像填充可用空间的问题,如下所示:

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)
        cr.paint()

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

Gtk.main()

我还没有尝试过,但在 GTK 4 中,您应该能够使用 Gtk.Picture 获得相同的效果,而无需自定义小部件。