Как исправить прокручиваемое окно GTK, не обновляющее полосы прокрутки после изменения содержимого?

#python #gtk #pygtk

Вопрос:

Я хочу, чтобы в моем приложении GTK было изображение, размер которого постоянно менялся в соответствии с его родительским контейнером.

Я добился этого, получив размер родительского контейнера внутри обратного вызова события выделения размера и изменив размер своего изображения в соответствии с этими размерами. Это прекрасно работает, когда я делаю окно меньше, но когда я хочу сделать его больше, оно отказывается изменять размер, потому что оно должно быть по крайней мере таким же большим, как содержимое (изображение).

Чтобы преодолеть этот аспект, я поместил изображение в прокрученное окно, чтобы я мог свободно изменять размер своего окна.

Проблема заключается в том, что, когда я переключаю показанное изображение на изображение с разными размерами, прокручиваемое окно, похоже, этого не осознает, и у меня остается прокручиваемое окно с неправильным размером содержимого и ненужными полосами прокрутки. Но, увы, я могу навести курсор на полосу прокрутки, и он поймет, что она слишком велика для своего содержимого, и удалит полосы прокрутки. Смотрите демонстрацию ниже.

Могу ли я каким-то образом добиться, чтобы это поведение «исправления» происходило сразу, а не при наведении курсора мыши на полосы прокрутки?

 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()
 

видеодемонстрация проблемы

Ответ №1:

size-allocate на самом деле это не совсем подходящее место для изменения содержимого вашего виджета (например, для изменения пикселя виджета изображения), и обычно это работает неправильно, если вы пытаетесь использовать его таким образом. Он больше предназначен для пользовательских виджетов контейнеров для размещения их дочерних элементов, как только размер уже определен.

В 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, 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, чтобы получить тот же эффект без пользовательского виджета.

Комментарии:

1. Это делает это, просто нужно было добавить функцию change_image в класс scaleImage, которая изменила pixbuf и вызвала self.queue_draw. Спасибо!