Trouble getting Properties to behave the way I want - also missing methods

741 views
Skip to first unread message

Josh Lee

unread,
Jun 19, 2014, 10:34:05 PM6/19/14
to kivy-...@googlegroups.com
Hey guys... hoping you can help me sort this out.  I've been pulling my hair out all afternoon over it.

Basically, I am trying to use a property to share data between two widgets.  These two widgets are two different ways that the user can view and edit the data.  So basically, the user makes edits, the information in the widget is converted to a common format, and then this data is shared between the two widgets.  The information displayed in the widget that is currently not in use should still update as the user makes edits in the other widget.

For this common data object, a ListProperty seemed to make a lot of sense.  It is pretty easy to read information from the two main edit widgets and convert it into a nested structure (with a list being the outermost level).

Luckily, the two widgets that need to share data are both children of my MainWidget class.  It seems like I should create a ListProperty in the first line of my MainWidget definition, then in __init__, I can pass this object on to the two children.  Then.... I think that MainWidget and the two children should all be able to modify and redefine this "ObservableList" instance, and they should all see the same data.

However, in my test cases, I could not get this behavior to work.  Sometimes, I could get the two children widgets to share data, but if I made a change to the data at the MainWidget level, then the link between the children and MainWidget was lost (but the children themselves still had the link).  This was particularly true if, in MainWidget, I assigned a completely different list (as opposed to just changing one of the elements).

I think this last part might be really important....  The shared data object did not have any of the methods that the documentation says it should.  Methods like ".get()" and ".set()" and ".dispatch()"... none of them were there.  I would get the error: "AttributeError: 'ObservableList' object has no attribute 'dispatch'"

If you all have any ideas, I could really use the help.  I don't know how else to do this.  There are other ways to share the data, but I really really like the binding system (it's the best way to update the info in the widget the user is not currently using) so I want to use Kivy properties.  Thanks!

Josh Lee

unread,
Jun 19, 2014, 11:14:42 PM6/19/14
to kivy-...@googlegroups.com
After a bit more poking... the problem really seem to be this:

I initialize the value of shareData in MainWidget __init__.  I then pass this self.shareData onto the children, who inherit it and point to it with their own self.shareData=shareData.  At this point, all of the objects in the various widgets are pointing at the same memory address.  Within any of the widgets, it appears I can then safely modify any elements of shareData without breaking the pointers.  Everything still points to the same memory address so the on_shareData bindings work on this change.  However, in any of the widgets, if I do something like self.shareData = [1,2,3], it gets assigned a new memory address.  This breaks the system.

I guess this shouldn't be too surprising.  But how do I get the intended behavior?  I will be modifying individual elements some, but I really need to be able to assign the memory address entirely different lists, and then have it propogate to on_shareData.  Any ideas?

I still couldn't get those methods either.  No set() or get() methods to be found.  Strange.

ZenCODE

unread,
Jun 20, 2014, 5:13:25 AM6/20/14
to kivy-...@googlegroups.com
If you just want to share data between classes, you can use a "static" method of the class, or more properly a class method. So;

class Bob():
    shared_data = None

    def on_touch_down(self, *args):
        # Using a normal instance method, use the reference to the class name
        Bob.shared_data = 1 

No?



Josh Lee

unread,
Jun 20, 2014, 1:37:19 PM6/20/14
to kivy-...@googlegroups.com
Hmm... yes.  Sort of.  I need to be able to share the data in both directions.  A common data address that all children can read and write to (it's really being used as a sort of global variable).  I also need to be able to bind to changes in this variable.  For this binding, I have to use Kivy Properties right?  I can't bind on regular object right?

The real problem seems to be completely changing the item at the memory address.  I seem to be able to get a system working where I can change individual elements of the list  (modify it), but if I assign a new list to the variable, then the memory address changes.  The Property.set() method would seemingly be the way to deal with this, but my instances don't have the Property methods that the documentation suggests.

This is roughly what I'm doing:

class MainWidget(BoxProperty):
    sharedData = ListProperty([])

    def __init__(self, **kwargs):
        super(MainWidget, self).__init__(**kwargs)
        self.sharedData = [0,0]
        self.testDataModifier = testDataModifier(self.sharedData)
        self.add_widget(self.testDataModifier)
        self.bind(sharedData =self.callback)

    def callback(self):
        print 'Main', self.sharedData
        self.testDataModifier.callback()

class TestDataModifier(Button):
    def __init__(self, sharedData**kwargs):
        super(TestDataModifer, self).__init__(**kwargs)
        self.sharedData = sharedData
        self.bind(on_press=self.pressCallback)

    def pressCallback(self):
        print 'Press', self.sharedData
        self.sharedData = [1,2,3]  # this changes the memory address and breaks everything
        # self.sharedData[0] = 1  # this doesn't change the memory address and everything still works

    def callback(self):
        print 'testDataModifier', self.sharedData


Thanks for your help!

Josh Lee

unread,
Jun 20, 2014, 2:16:20 PM6/20/14
to kivy-...@googlegroups.com
In the meantime, I just realized a less ideal, but workable implementation.  I can initialize a ListProperty with two elements.  It will always have two elements.  The first element will be my shared data.  The second element is a flag.

If I need to assign a completely new list for the shared data, then I just reassign the first element of the shared ObservableList.  Doing this will trigger a "changed" event, allowing my bindings to fire.  However, if I only modify the shared data (I don't completely replace the first element of the ObservableList), then the event will not trigger.  Kivy cannot detect this as a "change".  To trigger the change event, I can change the second element in the ObservableList.  It doesn't matter what it changes to; if it changes, it will trigger the bindings.  Alternatively, I could probably do without the flag element and do something weird with making deepcopies of the shared data and then reassigning... but that seems inferior.  The key though, is that the ObservableList is always modified, never replaced.

However.... this seems like there should be a better way.  Is there?  I still think this mysterious .set() method is the ideal answer.

ZenCODE

unread,
Jun 20, 2014, 6:33:34 PM6/20/14
to kivy-...@googlegroups.com
Hi

Okay, before getting into the Kivy property stuff, I think there are some python fundamentals that need clarifying here...


    def pressCallback(self):
       
print 'Press', self.sharedData
       
self.sharedData = [1,2,3]  # this changes the memory address and breaks everything

Yes, There is a vast difference here between "TestDataModifier.sharedData" and "self.sharedData". By using self, you are creating an entirely new list and assigning it to the current instance of the class. "TestDataModifier.sharedData" is common: it's memory address will never change unless re-assigned. "self.sharedData" does exactly that re-assignment , so that instance looses contact with the base classes "sharedData" property.


# self.sharedData[0] = 1  # this doesn't change the memory address and everything still works

Yes, because here you are are modifying a list that already exists, the inherited "sharedData" list. You are not assigning it a new value. I agree it can look a bit ambiguous, which is why I prefer the syntax:

     TestDataModifier.sharedData = [1,2,3]
     TestDataModifier.sharedData[0] = 1

If you always use the base class name directly, it's totally clear that you are referring to the shared, "static" sharedData list. It avoids using "self" because it does not belong to the "self" instance, but all the "TestDataModifier" class instances...

Agreed? Once we're clear on that, then lets talk kivy properties...? Kivy properties add a bit of magic, but understanding Python class instancing is important to understand;-)

Josh Lee

unread,
Jun 20, 2014, 10:29:12 PM6/20/14
to kivy-...@googlegroups.com
Hmmm.... okay thanks for teaching me fundamentals.  I played around with it and it makes sense.  So when using a kivy property, I should NOT do the following:

class TestClass(BoxLayout):
    test = ObjectProperty()
    def __init__(self):
        self.test = 'thing'

Instead, I should do this:

class TestClass(BoxLayout):
    test = ObjectProperty()
    def __init__(self):
        TestClass.test = 'thing'

And in all other cases, I should refer to TestClass.thing to share and modify this object.  I think I understand that now.  But Kivy properties?  If there anything else wrong with how I am trying to use them, and why aren't the methods like .set() and .get() working?  I assume that the ListProperty is really supposed to become an ObservableList right?

Alexander Taylor

unread,
Jun 21, 2014, 6:24:03 AM6/21/14
to kivy-...@googlegroups.com
No! This renders the ObjectProperty meaningless, as you simply replace it when you do 'TestClass.test = 'thing'. Kivy properties are *declared* at class level, but give *instance*-level behaviour, acting as normal python attributes as far as your code is concerned.

I didn't follow what you're actually trying to do above, but it almost certainly isn't this.

Emil Salib

unread,
Jun 21, 2014, 11:27:53 PM6/21/14
to kivy-...@googlegroups.com
Here is my attempt to solve the issue you raised as I understood it.
I hope this helps
*****************************************************************
class MainWidget(BoxLayout):
    sharedData = ListProperty([])

    def __init__(self, **kwargs):
        super(MainWidget, self).__init__(**kwargs)
        self.sharedData = [0,0]
        self.testDataModifier0 = TestDataModifier("Button0")
        self.add_widget(self.testDataModifier0)
        self.testDataModifier1=TestDataModifier("Button1")
        self.add_widget(self.testDataModifier1)
        self.bind(sharedData =self.callback)

    def callback(self,*arg):
        print 'Main', self.sharedData
        self.testDataModifier0.callback()  #or self.textDataModifier1.callback()

class TestDataModifier(Button):
    def __init__(self, button,**kwargs):
        super(TestDataModifier, self).__init__(**kwargs)
        self.text=button
        self.bind(on_press=self.pressCallback)

    def pressCallback(self,*args,**kwargs):
        print 'Press', self.parent.sharedData
        self.parent.sharedData[1]=2  #or self.parent.sharedData.insert, remove, etc..

    def callback(self):
        print 'testDataModifier', self.parent.sharedData

ZenCODE

unread,
Jun 22, 2014, 5:10:33 AM6/22/14
to kivy-...@googlegroups.com
For my 2c, yes, you've got the idea of how to share data between instances by using a class level variable and always referring to it via the class name. But just use a simple list, no need for kivy properties.

Then I would add a kivy event that you raise after it is changed to alert all instances. They can then react and take action.


http://kivy.org/docs/api-kivy.event.html

Sorry, I would give you a code example, but I'm on my bicycle writing this on my phone, so can't right now. But post if you don't come right and I'll do that later..


Peace out

ZenCODE

unread,
Jun 23, 2014, 10:26:19 AM6/23/14
to kivy-...@googlegroups.com
Hi it's me again :-)

Strikes me that Kivy properties are not really what you are looking for here. They help bind values to instances of object, and not really shared. Something like this might be simpler and more efficient?

from kivy.app import App
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.clock import Clock


class BoundLabel(Label):
    instances
= []  # A list of all instances of the class

   
def __init__(self, **kwargs):
       
BoundLabel.instances.append(self)
       
super(BoundLabel, self).__init__(**kwargs)

   
@staticmethod
   
def set_shared_text(dt):
       
""" Loop through all instances and set to the shared data """
       
for instance in BoundLabel.instances:
            instance
.text = str(dt)  # use str(dt) as shared for simplicity


class TestApp(App):
   
def build(self):
       
# Set the clock to alter the all the label captions to the delta time
       
Clock.schedule_interval(BoundLabel.set_shared_text, 1)
       
return Builder.load_string('''
BoxLayout:
    orientation: "vertical"
    BoundLabel:
    BoundLabel:
    BoundLabel:
'''
)

TestApp().run()


Emil Salib

unread,
Jun 25, 2014, 12:35:57 PM6/25/14
to kivy-...@googlegroups.com
I have tried your code below and I really appreciate your insight into making the code as simple and efficient  (without the unnecessary use of Property objects) as possible.
Thanks again for your help.  Best

ZenCODE

unread,
Jun 25, 2014, 4:36:17 PM6/25/14
to kivy-...@googlegroups.com
@Emil Salib: Thank you for your efforts. Everyone who contributes helps  ;-)
Reply all
Reply to author
Forward
0 new messages