GTK3 DrawingArea не рендерится/не обновляется, кроме как когда отводит взгляд и оглядывается назад

Я переношу некоторый старый код с gtk2 на gtk3 и сталкиваюсь с проблемами при обновлении/рендеринге/обновлении. Итак, gtk3 перешел на использованиеdrawвместо старогоexpose_eventи это привело к тому, что контекст стал аргументом функции, и я выяснил этот бит. Теперь в документации GTK3 я нашел это:

Сигналы рисования обычно подаются, когда область рисования впервые появляется на экране или когда она закрыта другим окном, а затем открыта. Вы также можете форсировать событие экспонирования, добавив в «область повреждения» окна области рисования; gtk_widget_queue_draw_area() и gdk_window_invalidate_rect() — одинаково хорошие способы сделать это. Затем вы получите сигнал отрисовки для недопустимой области.

И в моем коде я делаю это недействительным следующим образом:

      import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk as gdk
from gi.repository import Gtk as gtk
from gi.repository import Pango as pango
from gi.repository import GLib as glib

def draw(self, ctx):
    """Draw the canvas."""
    # wallpaper
    ctx.set_source_rgb(0., 0., 0.)
    ctx.rectangle(0, 0, 1, 1)
    ctx.fill()

    ctx.set_line_width(0.005)
    ctx.set_line_join(cairo.LINE_JOIN_ROUND)

    txtstart = 0.05
    for i in sorted(self.draw_que):
        item = self.draw_que[i]
             #...
             #DRAW WHAT'S IN QUEUE
             #...
        
    glib.timeout_add(self.refresh_interval, self.redraw)

def on_draw(self, widget, context):
    """Callback for draw (used to be expose_event)."""
    rect = widget.get_allocation()
    context.rectangle(0, 0, rect.width, rect.height)
    context.clip()
    context.scale(rect.width, rect.height)

    self.draw(context)
    return False

def redraw(self):
    """Callback for the idle_add drawing-loop."""
    if self.get_property('window'):
        alloc = self.get_allocation()
        rect = gdk.Rectangle(0, 0, alloc.width, alloc.height)
        self.get_property('window').invalidate_rect(rect, True)
        self.get_property('window').process_updates(True)

И хоть убей, я не могу понять, почему область рисования обновляется только при переключении между окнами. я установилrefresh_interval = 50миллисекунд, и если я напечатаю refresh_interval прямо подglib.timeout_addcall, я получаю несколько отпечатков каждый раз, когда переключаюсь с окна графического интерфейса на терминал и обратно, в противном случае он ничего не печатает и явно не обновляется вообще, если только переключение окна не произошло.

Короче говоря, draw_queue[i] рисуется каждый раз, когда я переключаю активные окна, а затем «приклеивается» к этому значению, пока я снова не переключусь, а затем обновляется примерно четыре раза (поэтому перерисовка происходит в течение 200 мс). И моя цель - постоянно обновлять область рисования (каждые 50 мс).

Любые советы о том, как это исправить?

Использование Ubuntu 20.04, python 3.8 и gtk3

Раньше это работало над реализацией gtk2

Вот скриншот, когда я печатаю время события рисования:я печатаю текущее время каждый раз <code>on_draw</code>вызывается и вручную запускает программу, несколько раз переключает окна и закрывает программу.:wq

То же в тексте:

      pygame 2.1.2 (SDL 2.0.16, Python 3.8.10)
Hello from the pygame community. https://www.pygame.org/contribute.html
Connecting to:  <tobiiresearch.implementation.EyeTracker.EyeTracker object at 0x7ff0d53e5790>
draw event received at 2023-01-25 20:40:20.356367
draw event received at 2023-01-25 20:40:20.370740
draw event received at 2023-01-25 20:40:20.387894
draw event received at 2023-01-25 20:40:20.408587
draw event received at 2023-01-25 20:40:25.214985
draw event received at 2023-01-25 20:40:29.183180
draw event received at 2023-01-25 20:40:29.209396
draw event received at 2023-01-25 20:40:29.236468
draw event received at 2023-01-25 20:40:29.252261
draw event received at 2023-01-25 20:40:29.270075
draw event received at 2023-01-25 20:40:29.287464
draw event received at 2023-01-25 20:40:29.301483
draw event received at 2023-01-25 20:40:29.320029
draw event received at 2023-01-25 20:40:29.336251
draw event received at 2023-01-25 20:40:29.352848
draw event received at 2023-01-25 20:40:29.373359
draw event received at 2023-01-25 20:40:30.231043
draw event received at 2023-01-25 20:40:30.484992
ldrop mainloop stopped.
ldrop instance deleted.

1 ответ

Починил это.

Теперь я должен признать, что я не уверен на 100%, почему это работало в версии gtk2 сно в версии gtk3 сМне пришлось добавить дополнительный слой испускания отрисовки и ответа на нее.

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

Итак, вкратце, у меня есть:

      class Controller(EventEmitter):
    """Main controller of the class. Views+ui separated to different files."""

    def __init__(self):
        """Constructor."""
        # run the superclass constructor
        EventEmitter.__init__(self)

        # Model initialization code
        self.sensors = []
        self.tags = []
  
        # ...
        # all root dirs, plugins and such set here
        # ...

        def add_model(self, model):
        """Add a model to listen for."""
        model.on("data", self.on_data) #Called when new data comes in
        model.on("close_controller", self.on_close_controller)
        model.on("start_collecting_data", self.on_start_collecting_data)
        model.on("stop_collecting_data", self.on_stop_collecting_data)
        model.on("log_message", self.on_log_message)
        model.on("query", self.on_query)


    def on_data(self, dp):
        """Callback for data-signal."""
        if self.data_callback is not None:
            glib.idle_add(self.data_callback, dp)
            self.emit("draw") # All I did here is add this row

Все, что я сделал вбыла добавлена ​​последняя строка. Само по себе это не сработало, потому что у меня не было ничего, что могло бы отреагировать на это событие рисования. Поэтому из-за двойного разделения представлений и пользовательского интерфейса мне пришлось добавить дополнительныйкоманду вкоторый принимает контроллер в качестве атрибута экземпляра:

      class ControllerView:
"""A pygtk-view for drop controller."""

    def __init__(self, ctrl, savedir):
        """Constructor."""
        # view knows the controller function calls
        self.ctrl = ctrl
        self.ctrl.on("sensorcount_changed", self.on_sensors_changed)
        self.ctrl.on("participant_id_updated", self.on_id_updated)
        self.ctrl.on("log_update", self.on_log_update)
        self.ctrl.on("error", self.on_error)
        self.ctrl.on("draw", self.on_draw) # Added this here to catch the draw event


# Then I added the on_draw reaction to the draw event
    def on_draw(self):
        """Callback for draw event"""
        self.draw_area.queue_draw()

И вуаля, так оно и работает!

Я знаю, что это, вероятно, не лучший способ исправить это, так как теперь изображение обновляется каждый раз, когда датчик отправляет данные (насколько мне известно, до 250 Гц), и отрисовки со скоростью 20 или 25 кадров в секунду будет более чем достаточно. ... Так что теперь это будет намного тяжелее. Любые предложения по исправлению этого?

Другие вопросы по тегам