ColumnView and bi-directional bindings - lockups when scrolling

I’ve set up bi-directional bindings with a ColumnView ( having only recently noticed that they exist ). So in my ‘bind’ handler for each column, I have something like:

            grid_row.bind_property( column_name , widget , "text" , GObject.BindingFlags.SYNC_CREATE
                                                                  | GObject.BindingFlags.BIDIRECTIONAL
                                  , self.bind_transform_to )

This ( using bi-directional binding ) removes some complexity ( having to manually capture changes eg made in GtkEntry widgets and set the corresponding value in the model ). However I’ve noticed that if I populate my model beyond a certain number of rows, and then scroll down, I get 100% CPU usage, and an effective application lockup.

I ran in sysprof to try to see what was happening:

It looks like there’s lots of signals being emitted, and g_object_set_property() happening … so based on this, added some debugging print() statements in places. ( Frustratingly, my python IDE refuses to step into the class I’m using as the model for ColumnViews, probably because it’s being generated at run-time … but this is a side issue ).

Anyway, after adding some debugging print()s, my model has:

    def id( self , id ):
        print( "In [id].setter() ... current: [{0}] ... new: [{1}]".format( id , self._id ) )
        if str( self._id ) != id:
            print( "[id] changed" )
            self._id = id
            self.notify( "id" )
            if self.row_state == 'emblem-default':
                print( "grid row state ==> changed" )
                self.row_state = 'media-playlist-shuffle'
                self.notify( "row_state" )

I then run my app ( disabling output buffering in bash ), and watch the output as I scroll. Things look normal until I scroll “too far”, and then I get an endless loop of:

In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]
In [id].setter() ... current: [308] ... new: [308]

Interestingly, it’s always the same ID it chokes on - I assume this is just the limit at which widgets in the ColumnView are recycled?

Anyway:

  1. Why is this happening, and how do I avoid it?

  2. Why is this only happening with bi-directional bindings? As noted above, I used to use the default single-directional binding, but this requires more manual code to take input from the widgets and apply them back to the model - and it causes some usability issues ( another side issue at this point ). However I don’t see this infinite loop with single-directional bindings, no matter how many rows I put in the model.

Hi,

I suspect the row widgets get recycled and get binded to other model elements while still binded to the old ones.

There should be some way to undo the original binding with Binding — GNOME Python API from the factory’s unbind handler.

I heard GtkExpression is more suitable to avoid this caveat, but it’s only supported since very recent PyGobject versions, so I haven’t had to opportunity to try…

Hey. Thanks for the response! I’ve connected to the ‘unbind’ signal of the Gtk.SignalListItemFactory():

f = Gtk.SignalListItemFactory()
f.connect( "setup" , self.setup , d['type'] , 1 , -1 , d['name'] )
f.connect( "bind" , self.bind , d['type'] , d['name'] )
f.connect( "unbind" , self.unbind )

… and then in the ‘unbind’ method:

    def unbind( self , factory , item ):
        widget = item.get_child()
        widget.binding.unbind()
        print( "just unbound {0} with value {1}".format( widget , widget.get_text() ) )

I’m indeed seeing lots of calls to the method. Further, the app no longer locks up when scrolling large lists.

HOWEVER … all is not well. Now, if I create a large list, with 1 column all empty, and edit two rows ( for some reason, one is not enough ), filling in some values in the empty columns, and then scroll down through the list, I see the edited values repeated over and over again.

I think maybe the ‘unbind’ is happening AFTER the ‘bind’ for another row, instead of before it?

hmm… if it was the case, you would see that value only twice I suppose.

I suspect an issue in your setup or bind implementation.

Quick update. It turns out there was an issue with my bind transformation ( which I have to do to handle ‘None’ values ). It’s still not clear exactly what was happening, but I’ve simplified things, and it’s all working now.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.