GTK3 DrawingArea 不会渲染/更新,除了看向别处和回头时

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_add,每次从 GUI 窗口切换到终端并返回时,我都会得到一些打印,否则它什么都不打印,而且显然根本不刷新,除非刚刚发生窗口切换。

简而言之,每次我切换活动窗口时都会绘制 draw_queue[i],然后将其“粘”到该值,直到我再次切换,然后刷新大约四次(因此重新绘制发生 200 毫秒)。我的目标是让绘图区域不断更新(每 50 毫秒一次)。

关于如何修复此问题有什么建议吗?

使用 Ubuntu 20.04、python 3.8 和 gtk3

这曾经在 gtk2 实现上工作过

这是我打印发出的绘制事件时间的屏幕截图: 每次调用时我都会打印当前时间on_draw并手动启动程序,切换窗口几次并关闭程序。: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 版本中有效,expose_event但在 gtk3 版本中draw我必须添加一个额外的发射绘制层并对其做出响应。

所以基本上我的控制器被分成了两个,视图和 UI 位于不同的文件中。因此也位于不同的循环中。(这是我在旧的 python2 代码中给出的结构,是的,我以前从未使用过 gtk 或任何 gui,所以我花了一段时间才意识到发生了什么。)

简而言之,我得到了main_controller.py

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

我所做的main_controller.py只是添加最后一行。仅此而已没有用,因为我没有任何东西可以响应发出的绘制事件。因此,由于视图和用户界面的双重分割,我不得不on_draw在中添加一个额外的命令controller_view.py,将控制器作为实例属性:

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

瞧!成功了!

我知道这可能不是解决这个问题的最佳方法,因为现在每次传感器发送数据时图像都会更新(据我所知最高可达 250Hz),并且以 20 或 25fps 的速度绘制就足够了……所以现在这会变得繁重得多。有什么建议可以解决这个问题吗?

相关内容