0

How to make circular progress bar in kivy?

I have found a source above for circular progress bar and noted something weird.

from kivy.app import App
from kivy.uix.progressbar import ProgressBar
from kivy.core.text import Label as CoreLabel
from kivy.lang.builder import Builder
from kivy.graphics import Color, Ellipse, Rectangle
from kivy.clock import Clock


class CircularProgressBar(ProgressBar):

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

        # Set constant for the bar thickness
        self.thickness = 40

        # Create a direct text representation
        self.label = CoreLabel(text="0%", font_size=self.thickness)

        # Initialise the texture_size variable
        self.texture_size = None

        # Refresh the text
        self.refresh_text()

        # Redraw on innit
        self.draw()

    def draw(self):

        with self.canvas:

            # Empty canvas instructions
            self.canvas.clear()

            # Draw no-progress circle
            Color(0.26, 0.26, 0.26)
            Ellipse(pos=self.pos, size=self.size)

            # Draw progress circle, small hack if there is no progress (angle_end = 0 results in full progress)
            Color(1, 0, 0)
            Ellipse(pos=self.pos, size=self.size,
                    angle_end=(0.001 if self.value_normalized == 0 else self.value_normalized*360))

            # Draw the inner circle (colour should be equal to the background)
            Color(0, 0, 0)
            Ellipse(pos=(self.pos[0] + self.thickness / 2, self.pos[1] + self.thickness / 2),
                    size=(self.size[0] - self.thickness, self.size[1] - self.thickness))

            # Center and draw the progress text
            Color(1, 1, 1, 1)
            Rectangle(texture=self.label.texture, size=self.texture_size,
                      pos=(self.size[0]/2 - self.texture_size[0]/2, self.size[1]/2 - self.texture_size[1]/2))

    def refresh_text(self):
        # Render the label
        self.label.refresh()

        # Set the texture size each refresh
        self.texture_size = list(self.label.texture.size)

    def set_value(self, value):
        # Update the progress bar value
        self.value = value

        # Update textual value and refresh the texture
        self.label.text = str(int(self.value_normalized*100)) + "%"
        self.refresh_text()

        # Draw all the elements
        self.draw()


class Main(App):

    def just_function(self):
        print(self.root) # <----- this will print None

    # Simple animation to show the circular progress bar in action
    def animate(self, dt):
        print(self.root) # <---- this prints CircularProgressBar object
        if self.root.value < 80:
            self.root.set_value(self.root.value + 1)
        else:
            self.root.set_value(0)

    # Simple layout for easy example
    def build(self):
        container = Builder.load_string(
            '''CircularProgressBar:
    size_hint: (None, None)
    height: 200
    width: 200
    max: 80''')

        # Animate the progress bar
        Clock.schedule_interval(self.animate, 0.1)
        print(self.root) # <---- this prints None
        self.just_function() # <---- this prints None
        return container


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

When you take a look at Main(App)

In this source, self.root is considered as CircularProgressBar in here.

But, when I do print(self.root) it prints None.

It only recognize CircularProgressBar when self.root is in a function that is called by Clock.scheduled_interval(func, rate).

Does anyone know what is happening in here?

BerryMan
  • 145
  • 1
  • 15
  • You say: *But, when I do print(self.root) it prints None* and I wonder where in your code do you print? – eyllanesc Oct 02 '18 at 07:08
  • @eyllanesc If I do `print(self.root)` inside `def animate(self.dt):`, it will print `CircularProgressBar object` and if I do it outside such as above `return container`, it prints `None` – BerryMan Oct 02 '18 at 08:06
  • please edit your question points where you get an incorrect output. – eyllanesc Oct 02 '18 at 08:07
  • @eyllanesc it is funny because if I create a random function such as `def function(self):` and put `print(root)` it will also return `None`. It only returns `CircularProgressBar` if it is called by `Clock` – BerryMan Oct 02 '18 at 08:07
  • Could you say where you define that function and when you invoke it? – eyllanesc Oct 02 '18 at 08:09
  • @eyllanesc I have just edited so you can see where (in `Main`) – BerryMan Oct 02 '18 at 08:09

1 Answers1

1

The explanation is very simple and is clearly explained in the docs:

root = None

The root widget returned by the build() method or by the load_kv() method if the kv file contains a root widget.

From the above it is understood that the root is the element that is returned in the build() method, so before something returns that function the root will be None, so when you print self.root within build() or call a function that prints self.root before returning that function you will always get None. After returning the root it will be what you returned, that is, the container an object of the class CircularProgressBar.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241