5

The Goal
I have a widget class with a property c_description. I need to be able to create and add a label (or some other type of widget) to this widget that inherits c_description as text. And changes to c_description propagate to the label's text. I need to be able to run this creation/addition of this label with a function.
I basically need to do exactly what this other question is asking about.


What I did and what problems I ran into
I formatted this label class StretchingLabel stretch to fit its contents. I to I have a script in kivy that shows how I want it to work. The end result should look like this. This is how the result should look

This is the other script in which I dynamically create and add a new widget after unsuccessfully binding its text property to c_description.
If I run that I get a result that looks like this. No Text or Formatting

The text of the label is "" rather than the contents of c_description, so that's one problem. But if I removing the property binding statement and change c_label = StretchingLabel() to c_label = StretchingLabel(pos=self.pos, width=self.width, text=self.c_description) we should be able to at least see what a successful property binding looks like.
When I do that, the result looks like this. GoodTextBadFormatting This is not what I want. I want it to look like the 1st picture.

My Code

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.clock import Clock
    from kivy.uix.widget import Widget
    from kivy.uix.label import Label
    from kivy.uix.boxlayout import BoxLayout
    from kivy.properties import StringProperty
    from kivy.uix.textinput import TextInput

    Builder.load_string('''
    <StretchingLabel>:
        size_hint_y: None
        text_size: self.width, None
        height: self.texture_size[1]
        group: 'test'
        canvas.before:
            Color:
                rgba: .7, .7, .7, 1
            Rectangle:
                pos: self.pos
                size: self.size

    <MyLabelFrame>:
        id: xLabel

    <ContainerBox>:
        orientation: 'horizontal'
        Button:
            text: 'h1'
            group: 'test'
        BoxLayout:
            orientation: 'vertical'
            size: root.size
            pos: root.pos
            Label:
                text: 'Description'
                size_hint_y: None
                height: 30
                bold: True
            MyLabelFrame:
            Label:
    ''')

    class StretchingLabel(Label):
        def __init__(self, **kwargs):
            super(StretchingLabel, self).__init__(**kwargs)
            #This is for debugging
            Clock.schedule_once(lambda dt: print("StretchingLabel.init(): ", self.text), timeout=0.01)
        def on_double_click(self, instance, p_ignoreme):
            #This is also for debugging
            print("StretchingLabel.on_double_click():", self.text)

    class MyLabelFrame(Widget):
        c_description = StringProperty(
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit. \n\nProin vitae turpis ornare urna elementum pharetra non et tortor. Curabitur semper mattis viverra. \nPellentesque et lobortis purus, eu ultricies est. Nulla varius ac dolor quis mattis. Pellentesque vel accumsan tellus. Donec a nunc urna. Nulla convallis dignissim leo, tempor sagittis orci sollicitudin aliquet. Duis efficitur ex vel auctor ultricies. Etiam feugiat hendrerit mauris suscipit gravida. Quisque lobortis vitae ligula eget tristique. Nullam a nulla id enim finibus elementum eu sit amet elit.')

        def __init__(self, **kwargs):
            super(MyLabelFrame, self).__init__(**kwargs)
            Clock.schedule_once(lambda dt: self.makeLabel(), timeout=0.01)

        def makeLabel(self):
            c_label = StretchingLabel()
            #HERE! This vvv does not seem to work for some reason.
            self.bind(pos=c_label.setter('pos'), width=c_label.setter('width'), c_description=c_label.setter('text'))
            #This vvv didn't work either.
            #c_label.bind(pos=self.setter('pos'), width=self.setter('width'), text=self.setter('c_description'))
            self.add_widget(c_label)

    class ContainerBox(BoxLayout):
        def __init__(self, **kwargs):
            super(ContainerBox, self).__init__(**kwargs)

    class Nested2App(App):
        def build(self):
            return ContainerBox()

    if __name__ == '__main__':
        Nested2App().run()

My questions to you

  1. Why does self.bind(c_description=c_label.setter('text')) not work? The label is being created, but it's not getting the text from c_description. What am I doing wrong?
  2. Why is the formatting wrong on the created label? I passed the same properties in the kv code that I did in makeLabel(). And I need to be able to do this properly with the makeLabel() function.

Note

I saw another question here that had the exact same problem I did (without the formatting issue), but for some reason the answer didn't work for me.

wimworks
  • 283
  • 3
  • 15

1 Answers1

1

Question 1

Why does self.bind(c_description=c_label.setter('text')) not work? The label is being created, but it's not getting the text from c_description. What am I doing wrong?

Answer

You have got the correct code,

self.bind(pos=c_label.setter('pos'), width=c_label.setter('width'), c_description=c_label.setter('text'))

The binding process does not immediately update the text. The text will only change when c_description changed.

Example

The following example illustrates the following:

  1. Initially display an empty label
  2. Displayed c_descripton after it was changed but at widget's default pos=[0,0] and width=100
  3. Maximize the window and c_description is displayed at the correct location because the pos and width has changed.

Note: - kv file

Added color: 0, 0, 0, 1 # black colour text into class rule, <StretchingLabel>: because the text is not visible. This is due to the background colour is white and the default colour for Label's text is also white.

main.py

from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty

Builder.load_string('''
<StretchingLabel>:
    color: 0, 0, 0, 1   # black color text    

    size_hint_y: None
    text_size: self.width, None
    height: self.texture_size[1]   
    group: 'test'

    canvas.before:
        Color:
            rgba: .7, .7, .7, 1
        Rectangle:
            pos: self.pos
            size: self.size

<MyLabelFrame>:
    id: xLabel

<ContainerBox>:
    orientation: 'horizontal'
    Button:
        text: 'h1'
        group: 'test'

    BoxLayout:
        orientation: 'vertical'
        size: root.size
        pos: root.pos

        Label:
            text: 'Description'
            size_hint_y: None
            height: 30
            bold: True

        MyLabelFrame:

        Label:
''')


class StretchingLabel(Label):

    def __init__(self, **kwargs):
        super(StretchingLabel, self).__init__(**kwargs)
        # This is for debugging
        Clock.schedule_once(lambda dt: print("StretchingLabel.init(): ", self.text), timeout=0.01)

    def on_double_click(self, instance, p_ignoreme):
        # This is also for debugging
        print("StretchingLabel.on_double_click():", self.text)


class MyLabelFrame(Widget):
    c_description = StringProperty(
        'Lorem ipsum dolor sit amet, consectetur adipiscing elit. \n\nProin vitae turpis ornare urna elementum pharetra non et tortor. Curabitur semper mattis viverra. \nPellentesque et lobortis purus, eu ultricies est. Nulla varius ac dolor quis mattis. Pellentesque vel accumsan tellus. Donec a nunc urna. Nulla convallis dignissim leo, tempor sagittis orci sollicitudin aliquet. Duis efficitur ex vel auctor ultricies. Etiam feugiat hendrerit mauris suscipit gravida. Quisque lobortis vitae ligula eget tristique. Nullam a nulla id enim finibus elementum eu sit amet elit.')

    def __init__(self, **kwargs):
        super(MyLabelFrame, self).__init__(**kwargs)
        Clock.schedule_once(lambda dt: self.makeLabel(), timeout=0.8)

    def makeLabel(self):
        c_label = StretchingLabel()
        self.bind(pos=c_label.setter('pos'), width=c_label.setter('width'), c_description=c_label.setter('text'))
        self.add_widget(c_label)

        Clock.schedule_once(lambda dt: self.chg_text(), 1)

    def chg_text(self):
        self.c_description = 'Updated: ...' + self.c_description


class ContainerBox(BoxLayout):
    def __init__(self, **kwargs):
        super(ContainerBox, self).__init__(**kwargs)


class Nested2App(App):
    def build(self):
        return ContainerBox()


if __name__ == '__main__':
    Nested2App().run()

Output

App started App staarted

c_description updated c_description updated

Maximised and Minimized Window Maximised and Minimized Window

Question 2

Why is the formatting wrong on the created label? I passed the same properties in the kv code that I did in makeLabel(). And I need to be able to do this properly with the makeLabel() function.

Root Cause

The problem was due to Kivy having unfinished styling.

Solution

Increase the timeout value to at least 0.8 seconds. This value varies i.e. depending on your computer's speed.

Snippets

    def __init__(self, **kwargs):
        super(MyLabelFrame, self).__init__(**kwargs)
        Clock.schedule_once(lambda dt: self.makeLabel(), timeout=0.8)

    def makeLabel(self):
        c_label = StretchingLabel(pos=self.pos, width=self.width, text=self.c_description)
        self.add_widget(c_label)

Output

Result

ikolim
  • 15,721
  • 2
  • 19
  • 29
  • This definitely solved problem #2. But it took me a minimum of 1.4 seconds (and sometimes that wasn't enough). Is this time going to vary depending on how fast a person's computer is? – wimworks Jul 16 '19 at 18:48
  • 1
    That's correct, it varies. It depends on how fast is the computer. – ikolim Jul 16 '19 at 19:58
  • Please refer to updated post for detail explanation to question 1. – ikolim Jul 16 '19 at 21:20
  • 1
    Alright, for all the people of the future, I'm going to paraphrase your explanation to see if I understand it. `self.bind(c_description=c_label.setter('text'))` did correctly bind `c_description` to the label's `text`. However, this binding only updates the text of the label when `c_description` is changed (thereby triggering a property event). So the text of the label is initially "" and will only change to `c_description`'s value when `c_description` is changed. Is that correct? – wimworks Jul 16 '19 at 23:29