When does self.ids get filled...

462 views
Skip to first unread message

Paul

unread,
Dec 1, 2015, 3:27:22 AM12/1/15
to Kivy users support
I'm reasonably new to Kivy, but donkey's years as a dev.

I like the concept of kv files (not polluting logic with UI layout) - 
so I've laid out my complete app inside the kv file.
The classes behind the major UI elements are in my main.py.

My issue is that:
- I call 
    theApp.run(),
- In the App build method, I construct my MainWindow().
    class PrivateApp(App):
    def build(self):
self.main = MainWindow()
print("self.main.ids", self.main.ids)
return self.main

This all works. The app is constructed, all the UI elements are constructed and work.

The problem is that in one corner of the UI I have a ListView (notelist_items below)
And in there I have an adapter. I declare the adapter in kv:

<NotesListContainer>:
notelist_header: notelist_header
notelist_items: notelist_items
orientation: 'vertical'
    ...
    ListView:
id: notelist_items
orientation: 'vertical'
adapter: main.NotesListAdapter(data=root.data, cls=Factory.NoteListItem, args_converter=root.args_converter, allow_empty_selection=False, selection_mode='single', on_selection_change=root.sel_change, propagate_selection_to_data=True)

I have learnt out that you cannot declare the on_selection_change in the kv file - it must be done in code. 
So I do that...

class NotesListContainer(BoxLayout):
data = ListProperty()
notelist_items = ObjectProperty()
note_contents = None

def __init__(self, **kwargs):
super(NotesListContainer, self).__init__(**kwargs)

print("IDS", self.ids)
        self.ids["notelist_items"].adapter.bind(on_selection_change=self.sel_change)


I have seen example code where you *can* access self.ids in the constructor.
(e.g. see the accepted answer in here: http://stackoverflow.com/questions/24297061/kivy-binding-a-callback-to-simplelistadapter-in-kv)
Which is what I am doing here. But self.ids is *empty* at this point (as the print proved).

You'll notice above that I print ids at the end of the app.build(), and it is full at that point.
But ids is empty end of the NotesListContainer constructor.

It seems I need to bind on_selection_change to my adapters after build, right?
If so, is there a widget event that fires after build is complete?

Or have I got this wrong?
Thx for any advice you can give.

Paul.

David Aldrich

unread,
Dec 2, 2015, 4:48:30 AM12/2/15
to Kivy users support
Hi Paul

I faced a similar problem and was advised to:

Do Clock.schedule_once(some_func, 0), where some_func does your thing with the ids

I called that from init() and it worked ok.

Best regards

David

Paul

unread,
Dec 3, 2015, 1:11:11 AM12/3/15
to Kivy users support
That did it! Thank you.
I made some pretty ugly calls from the MainWindow constructor to go back and complete construction
when the ids are all set. Doing a scheduled call
using a lambda right from the class where its required is much better.

    Clock.schedule_once(lambda dt: self.notelist_items.adapter.bind(on_selection_change=self.sel_change), 0)

Still a little ugly IMHO, but workable.
Cheers
Paul

ZenCODE

unread,
Dec 3, 2015, 2:51:56 AM12/3/15
to Kivy users support
Another option is to use the event to do the binding, not the init. Something like:

def on_notelist_items(self, widget, value):
    self.notelist_items.adapter.bind(on_selection_change=self.sel_change)   

Paul

unread,
Dec 3, 2015, 3:38:38 PM12/3/15
to Kivy users support
Interesting. I think you are suggesting that I create a custom event, and then send
that event to all objects that need post-init setup, right?

That's a pretty cool idea, although I would need a way to keep a list of objects
that require that post-initialiser setup.

Thank you.
Paul.

ZenCODE

unread,
Dec 4, 2015, 3:16:00 PM12/4/15
to Kivy users support
Not sure I'd word it like that. It's more like, instead of using your __init__ to bind, you use the event of the 'nodelist_items' property changing to do the binding. It's fired when your 'nodelist_items' property changes to something other than None.

It's not really a custom event as this is an automatic event provided by Kivy properties. So I'm not sure why you would need to keep a list? But then again I have not looked closely at what you are trying to do. Just wanted point out you can use these events for initialization i.s.o. __init__. It avoids that issue of the ids not being set up yet..:-)

Paul

unread,
Dec 4, 2015, 4:24:08 PM12/4/15
to Kivy users support
Got it - I had forgotten about that. Event on changing the nodelist_items value.
Will the adapter necessarily have a value at that point?

Thanks.
Paul.

ZenCODE

unread,
Dec 4, 2015, 4:32:29 PM12/4/15
to Kivy users support
Not sure. Try it and see? You can guard against that error but then the question remains on where to set it...

def on_notelist_items(self, widget, value):
   
if self.notelist_items.adapter:
       
self.notelist_items.adapter.bind(on_selection_change=self.sel_change)
   
else:
       
print('Eek! This is not going to work!')

 

Paul

unread,
Dec 5, 2015, 10:41:04 PM12/5/15
to Kivy users support
Yep. That did it.
I changed it to:

def on_notelist_items(self, widget, value):
    if self.notelist_items.adapter:
        print('Adapter set')
        self.notelist_items.adapter.bind(on_selection_change=self.sel_change)
    else:
        print('Eek! This is not going to work!')
    print("IDS: ", self.ids)

because I also wanted to see if the ids dict had been filled in at the time this event fires.
And it has!!! On the output appeared the following:

Adapter set
('IDS: ', {'notelist_header': <WeakProxy to <kivy.uix.boxlayout.BoxLayout object at 0x114d63db8>>, 'notelist_items': <WeakProxy to <kivy.uix.listview.ListView object at 0x114d9d7a0>>})

Thanks for the help.
Paul.
Reply all
Reply to author
Forward
0 new messages