Score:0

GTK3 DrawingArea not rendering/updating other than when looking away and looking back

gt flag

I am migrating some older code from gtk2 to gtk3 and encountering problems in refreshing/rendering/updating. So gtk3 has moved to use draw instead of the old expose_event and this has caused the context to be an argument of the function and that bit I have figured out. Now in the GTK3 documentation I found this:

Draw signals are normally delivered when a drawing area first comes onscreen, or when it’s covered by another window and then uncovered. You can also force an expose event by adding to the “damage region” of the drawing area’s window; gtk_widget_queue_draw_area() and gdk_window_invalidate_rect() are equally good ways to do this. You’ll then get a draw signal for the invalid region.

And in my code I do this invalidating like this:

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)

And for the life of me I cannot figure out why the Drawing Area only updates when I switch between windows. I have set refresh_interval = 50 milliseconds, and if I print the refresh_interval just under the glib.timeout_add call, I get a few prints each time I switch from my gui window to the terminal and back, otherwise it prints nothing and clearly does not refresh at all unless the window switch has just happened.

In short the draw_queue[i] is drawn each time I switch active windows and then gets "glued" to that value until I switch again, and then it's refreshed about four times (so the redrawing happens for 200ms). And my goal is to have the drawing area be updated constantly (each 50ms).

Any tips on how to fix this?

Using Ubuntu 20.04, python 3.8 and gtk3

This used to work on the gtk2 implementation

Here is a screencap of when I print the time of a draw event emitted: I print the current time each time on_draw is called and manually start the program, toggle windows a few times and close the program.:wq

The same in text:

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.
Score:0
gt flag

Fixed it.

Now I must admit I am not 100% sure why this worked in the gtk2 version with expose_event but in the gtk3 version with draw I had to add an extra layer of emitting draw and responding to it.

So basically my controller is split into two, the views and the ui are in separate files. And thus in separate loops. (This is the structure I was given in the old python2 code and yes, I've never worked with gtk or any guis before so took me a while to realise what is going on.)

So in short I've got a 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

All I did in the main_controller.py was add the last line. That alone did not work, because I had nothing that would respond to this draw event being emitted. So due to the dual split of the views and the ui I had to add an extra on_draw command into the controller_view.py that takes the controller as an instance attribute:

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

And voilá, so it works!

I'm aware that this is probably not the best way to fix this, as now the image updates every time the sensor send data (up to 250Hz as far as I'm aware) and drawing at 20 or 25fps would be more than sufficient... So this will be a lot heavier now. Any suggestions to fixing that?

NotTheDr01ds avatar
vn flag
*"Any suggestions to fixing that?"* Keep in mind that we're not a discussion site; so an answer shouldn't ask a new question. You can post a new question on that topic, but I'd really recommend [Stack Overflow](https://stackoverflow.com) as a better place for this.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.