jsexauer
jsexauer

Reputation: 681

Kivy ObjectProperty to update label text

I am working on creating a kivy user interface to display the values generated by a data model I've written as a standard python object. In essence, I would like the user to be able to press a button, which would change the underlying data model and the results of this change would be automatically updated and displayed. It is my understanding that this can be implemented using kivy properties (in this case, ObjectProperty).

Here is some example code:

import kivy
kivy.require('1.7.0')

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
from kivy.lang import Builder

Builder.load_string("""
<RootWidget>:
    cols: 2
    Label:
        text: "Attribute a:"
    Label:
        text: root.data_model.a
    Label:
        text: "Attribute b:"
    Label:
        text: root.data_model.b
    Label:
        text: "Attribute c:"
    Label:
        text: root.data_model.c
    Button:
        text: "Make data_model.a longer"
        on_press: root.button_press()
    Button:
        text: "Make data_model.b shorter"
        on_press: root.button_press2()
""")


class DataModel(object):
    def __init__(self):
        self.a = 'This is a'
        self.b ='This is b'

    @property
    def c(self):
        return self.a + ' and ' + self.b

class RootWidget(GridLayout):
    data_model = ObjectProperty(DataModel())

    def button_press(self, *args):
        self.data_model.a = 'This is a and it is really long now'
        print self.data_model.c

    def button_press2(self, *args):
        self.data_model.b = 'B'
        print self.data_model.c

class TestApp(App):
    def build(self):
        return RootWidget()

app = TestApp()
app.run()

The desired result is for when user presses either button, the labels would automatically update to show the new properties. As can be seen by the print statements, the data_model is getting updated correctly. However, none of the labels are updating. Can someone clarify how to do this?

Upvotes: 10

Views: 12669

Answers (2)

inclement
inclement

Reputation: 29488

However, none of the labels are updating. Can someone clarify how to do this?

The attributes you reference need to be Kivy properties, but the a, b and c you reference are all just python attributes so Kivy has no way to bind to their changes.

To work with properties you need your object to inherit from EventDispatcher (Kivy widgets do this automatically, which is why their properties work).

from kivy.event import EventDispatcher

class DataModel(EventDispatcher):
    a = StringProperty('')
    b = StringProperty('')
    c = StringProperty('')

    def __init__(self, *args, **kwargs):
        super(DataModel, self).__init__(*args, **kwargs)
        self.a = 'This is a'
        self.b ='This is b'
        self.bind(a=self.set_c)
        self.bind(b=self.set_c)

    def set_c(self, instance, value):
        self.c = self.a + ' and ' + self.b        

Note that this is not the only way (or even necessarily the best way) to get the behaviour you wanted for c. You could create the binding in kv language (I'd usually do it that way) or you can look at Kivy's AliasProperty for something more like your original definition.

Of course you could also set the values of a and b when the properties are declared.

Upvotes: 7

jsexauer
jsexauer

Reputation: 681

Here is my solution:

https://gist.github.com/jsexauer/8861079

In essence I created a wrapper class that bridges the data model and the UI. There are a few things I don't love about it that maybe some other people will be able to improve upon:

  • Any UI function which updates the underlying data model must be decorated with the "update" decorator provided by the UI_DataModel class. It would be ideal to not have to include that decorator, though I'm not sure how to do that without substantially modifying how the DataModel class works (but the whole point of this is that you shouldn't have to touch the underlying data model class)
  • I would like to be able to pass the common data model instance to the __init__() of the UI_DataModel instead of having it use a global variable (and thus is hard coded). I tried to get this to work, but ran into a weakref recursion hell. Maybe someone who has a better understanding of the Python Object Model could implement this.

Upvotes: 4

Related Questions