Label not updating (I really, really tried)

1,581 views
Skip to first unread message

mister render

unread,
Dec 22, 2016, 7:59:13 AM12/22/16
to Kivy users support
Hello everyone!

I come to you because I am at the end of my rope. I kept on trying, persevered through a week of researching and trying everything I could find. I read the API docs, the tutorials, everything on SO I could find and tried approaches from 3 different kivy books. I just don't get it. I must be missing something.

I attach 4 files:
  • main.py: named as such for use with buildozer
  • words.txt: the dictionary I am using to match words
  • words.kv: very simple interface
  • logic.py: farming out some letter logic
Long story short, I just can't get 'scorelabel' to update automatically. It does work when I make a 4th button calling an 'update label text' function but that's not the point of the app or the label.

You may notice some peculiarities in the app....those are the signs of someone who doesn't know that much about how data should flow in kivy apps, it's all a bit hacky. It works though and I have managed to avoid global variables (yay!)

Here are some of the things I have tried:
  1. call " self.ids.scorelabel.text = self.a.thedict['score'] " in every way imaginable. I have called it in every place of every method, made it its own method and called it from another method, called it from outside the class, everything. Does nothing. Neither error nor anything else. 
  2. printed self.ids.scorelabel.text to the console. Believe it or not, Kivy tells me the text of the label is in fact = self.a.thedict['score'] after updating it. What?? How is that possible? How can the text attribute of a label be something different from what is displayed?
  3. added " scorelabel = ObjectProperty() " in Root(), makes no difference
  4. tried binding a new variable called " scorestr " to any changes in the score when score updates. This fires correctly and catches the correct values, as you will see from the associated print statement in line 68. Makes no difference.
  5. tried setting the text property in the .kv file to the score variable....that just turns it into 0 at the beginning and after that nothing changes
  6. tried creating an 'update' method associated with 'on_press' and 'on_release' in the buttongrid, no difference
  7. same for 'on_touch_down' or 'on_touch_up'
  8. created a myriad different methods all of which could access 'score' just fine and return it straight to the label.text attribute, at which point the label does nothing again
I have tried many more really random things that I have seen from similar discussions here or on SO, but it seems as though most people are just struggling with the concept of bind() or the different properties. I don't think I have an issue with that. I struggled getting reliable access to 'score' as well, but you may notice that I simply made it into a dictionary key in the App class and that works just fine....even though it feels a bit hacky

There must be something I am missing. Is there some problem with how and when or where labels are rendered? Should I add the label widget in python instead of the kv file? (That seems wrong....)

If anyone has the time to take a look at this I would be eternally grateful!
main.py
words.kv
words.txt
logic.py
main.py

mister render

unread,
Dec 22, 2016, 10:43:45 AM12/22/16
to Kivy users support
It seems main.py attached twice. They're identical, so download either :)

ZenCODE

unread,
Dec 22, 2016, 1:02:40 PM12/22/16
to Kivy users support
Insert as line 45 of main.py

                self.ids.scorelabel.text = "New score!"

Monty Mole

unread,
Dec 22, 2016, 5:42:19 PM12/22/16
to Kivy users support
Hi,
just checked your code.

In the function of your custombuttom class you want to call a function of your Root class. For this you must have a pointer to the instance of the Root class you created in the build function.
do it like this:

    def build(self):
        root = Root()  
        return root

then you can access the instance like this:
    def on_pressed(self, instance, id):
        #Root().add(val=self.text, addlis=True) #list that becomes word later         <----- error here, you are creating a new Instance each time
        self.a.root.add(val=self.text, addlis=True) #list that becomes word later     <----- you want to access the add function of the root instance of the running app

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            #Root().process() #when no longer swiping, check for word match
            self.a.root.process() #when no longer swiping, check for word match
            return True
        return super(CustomBtn, self).on_touch_up(touch)


Merry Christmas

mister render

unread,
Dec 26, 2016, 8:33:34 AM12/26/16
to Kivy users support
Some might say that it's sad to think this was my best Christmas present, but it's at least close! It works! Not only does it work, but I also understand why, which is perhaps even more important. I will be making a lot of use of that technique for sure.

Do you think this way of updating labels holds up in larger applications as well?

As for ZenCODE, I also want to thank you. I am not sure why that would work, but I am very grateful for your response as well :)

Thank you, thank you, thank you :)

Monty Mole

unread,
Dec 28, 2016, 9:29:21 AM12/28/16
to Kivy users support
I did not look into your code, but check out the concept of Properties (StringProperty for example) in Kivy. It is more elegant most of the time...

mister render

unread,
Jan 17, 2017, 12:51:47 PM1/17/17
to Kivy users support
So, I have made quite a few updates to the app...one of which is screen management. Please ignore the unoptimised jungle of rules for the buttongrid, it's crazy stuff and on day I will hopefully become better at optimisation.

As it turns out, the only way to get the screen manager to work seems to be to explicitly load your kv file with the builder. 

First question: Is that true? I tried following this tutorial and I attempted to get this to work without returning sm, but that seems to not be possible, as the screens do not get assigned to the screen manager otherwise (printing self.manager returns None for all screens unless I use the builder to load the .kv file)

Second question: How do I get access to the different classes in my app back? The lovely trick you showed me is unfortunately no longer working. I tried something similar, as shown in line 169 of main_test.py, but that does not work either, I just get attributeerror: secondlabel (line 191)

Any ideas? You mentioned binding a stringproperty, but I wouldn't know how to do that across classes?
main_test.py
test.kv

mister render

unread,
Jan 18, 2017, 6:12:46 AM1/18/17
to Kivy users support
I have done some further investigations and noticed some interesting behaviour on what works and what does not. I would be grateful for any input anybody has:

There are multiple ways of creating a ScreenManager and multiple Screens. There is very little information about what is 'best practice', here is what I tried:
  1. In my main_test.py file I created a class inheriting from ScreenManager and multiple classes inheriting from Screen, so far so good
  2. I then pointed the build method in my App class at the ScreenManager. I figured that if I do self.add_widget for my screens in the ScreenManager's init method, that should simply work. Turns out that the ScreenManager then throws an exception saying only Screen objects....even though each widget I am trying to add is a screen inheriting from Screen...how is that possible?
  3. Then I tried a different approach I saw in the tutorial I referenced earlier. Namely doing sm = ScreenManager(). The problem here is that I now don't have a nice 'class Manager(ScreenManager)' any longer that I can modify anyway I like. So I then created that class and made sm = Manager(). 
  4. Now to add my two screens to the ScreenManager instance, which is now called 'sm'. Here is where things get interesting. Initially I figured that simply by having a .kv file called 'test.kv' it should be loaded as part of 'TestApp(App)'....but somehow that does not work (no UI is loaded at all. the app starts with a blank screen) once ScreenManager is involved??? Is that also true? Why?
  5. Thus, I used the Builder load_string / load_file method. Both of these work, the UI is now created properly again, but get this: if I add the block
    Manager:
    Root:
    Settings:
    at the top of my kv file to indicate that Root and Settings need to be added to the ScreenManager, the exception that ScreenManager only accepts Screen is thrown again. But both Root() and Settings() inherit from Screen! What is going wrong? (I should note that I have given them individual names via name: 'root' / 'settings' in their __init__ methods
  6. So, I remove that block again and rely on sm.add_widget()
Now, apart from the questions already contained within the list above, I am most surprised by the fact that I can't do what the tutorials are telling me to do. Namely, this should work:

main_test.py:

class Manager(ScreenManager):
pass

class Settings(Screen):
pass

class Root(Screen):
[tons of logic]

class TestApp(App):

def build(self):
return Manager() 

test.kv:

Manager:
Root:
Settings:

<Root>:
[lots of UI]

but there are two main points of failure. The first being that the .kv file is somehow not automatically loaded anymore, even though it has the same name as the app and ScreenManager things ky classes are not Screen objects, but they should be...

Has anyone else worked with ScreenManager and encountered similar problems? The documentation is not really very helpful, neither are tutorials, because in all instances it seems builder load string or load file is used, or they simply add Screen objects directly to a ScreenManager object in python, which is obviously going to work, but isn't very flexible.

Thoughts?

Michael Kramer

unread,
Jan 18, 2017, 8:17:19 AM1/18/17
to Kivy users support
I am not sure if this will help but I ran into similar issues and my solution was to embed a screen manager into a custom screen and then override the add_widget() method in the custom screen to have it add widgets to the embedded screen manager, and this worked with kv specification. 

For example

class MyScreenManager(Screen):
    def __init__(self, **kwargs):
        self.sm = ScreenManaer()

   def add_widget(self, **kwargs):


--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Michael Kramer

unread,
Jan 18, 2017, 8:40:27 AM1/18/17
to Kivy users support
whoops - that got sent off too fast... sorry. 

class MyScreenManager(Screen):
    def __init__(self, **kwargs):
        self.sm = ScreenManager()
        # The screens list hold references to the screens so that self.sm can switch between them
        self.screens = list()
        self.add_widget(self.sm)

    def add_widget(self, widget):
        # The first call to add_widget from the __init__ method will add the screen manager itself to this screen
        # Remaining calls (e.g. from .kv) will add actual screens to the screen manager
        if len(self.children) == 0:
            super(MyScreenManager, self).add_widget(widget)
            return   
        self.sm.add_widget(widget)
        self.screens.append(widget)

and then I could override the Screen class something like:

class MyScreen(Screen):
    pass


This allowed me to add screens to the screen manager in kv something like:

(in .kv)
<SomeScreenManager:>

    SomeScreen1:
        some_property: value

    SomeScreen2:
        some_property: value
       

(in python)

class SomeScreenManager(MyScreenManager):
    pass

class Screen1(MyScreen):
    pass

class Screen2(MyScreen):
    pass


This is basically what I am doing in my project, but the code above is a bit simplified compared to what I am using  so there may be bugs in the above example, and I am sure it could be made more efficient, but hopefully you get the idea

This method also allowed me to effectively add screen managers as child widgets to other screen managers to nest them, and create different kinds of screen managers, e.g. a tabbed screen manager or one that is based on the Carousel class for a manager to swipe between different screen.


mister render

unread,
Jan 18, 2017, 2:57:16 PM1/18/17
to Kivy users support
Ha, no worries. This is an excellent and thorough explanation. Particularly for tabbed interfaces that is very, very interesting.

I have meanwhile figured out how to make this work. I am not sure how elegant the solution is, but I found out a few more interesting behaviours:
  1. I needed access to my root screen from the buttons in the gridlayout. I renamed it Home() as I was paranoid about shadowing 'root' as a term used elsewhere. Turns out that 'root' isn't a thing in python, but 'parent' is. So, in the button class I put self.home = self.parent.parent.parent and et voila, self.home.[something] is now accessible from the button grid
  2. I then thought I'd be clever and make this happen on __init__. Doesn't work. At __init__ self.home just becomes 'None'. Need to do this in a method later on. I cheekily tried overriding on_parent, which also did not work...grrrr. For now I haven't done anything else to make this more efficient, but eventually I will try to figure out a way to automate this better for all methods in the class, not repeat it over and over with local variables (might use Clock to schedule it once, a few milliseconds after __init__)
  3. One more thing to note (particularly for Monty Mole) is that the root = Root() declaration in the App class caused the home screen to init twice, which could be problematic in RAM sensitive environments. This is no longer the case with this new solution
I have attached the files for anyone to do with as they please :)
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
main_test.py
test.kv
Reply all
Reply to author
Forward
0 new messages