Search code examples
kivykivy-language

Kivy:Can't put 'BorderImage' at the specified position


(1) By using the following kv file version was able to place BorderImagewidget at the specified position..

<Screen>:
    ProgressBar:
        max: 100
        pos_hint: {'top':0.86, 'x':0.01}
        size_hint_x: 0.49
        size_hint_y: 0.1
        canvas:
            BorderImage:
                border: (10, 10, 10, 10)
                pos: self.x, self.center_y
                size: self.width, 8
                source: '0.png'

(2) But, the following Pure Python code that should realize the same function as (1) doesn't work properly.BorderImagewidget is placed at the bottom of the screen. pos_hint={'top':0.86,'x':0.01} doesn't work. I think that how to specify pos=(bar.x, bar.center_y) is not good because bar.center_y value is different from the code of (1).

class BarWidget(FloatLayout):
    def __init__(self, **kwargs):
        super(BarWidget, self).__init__(**kwargs)
        self.build()
    def build(self):
        bar = ProgressBar(pos_hint={'top':0.86,'x':0.01}, max=100, size_hint_x=0.49, size_hint_y=0.1)
        with bar.canvas:
            BorderImage(border=(10, 10, 10, 10), pos=(bar.x, bar.center_y), size=(self.width/2, 8), source='0.png')
        self.add_widget(bar)

How should I modify bar.center_y?

(1):screen shot

(2):screen shot


Solution

  • In the kv language, when you set an attribute like size: self.size, this attribute is automatically updated whenever the 'self' widget changes size of shape. When things are loading up in a screen/layout, they start with funky positions and sizes then move to be correct. Since things automatically update upon changing sizes/positions if you work in kv, it works as you expect.

    In python, you have to explicitly bind some function to update the canvas if the size or position of the widget it's inheriting its size and pos from changes. You can do that by using the bind function (available in most/all kivy widgets). Using bind, you can say bind(<attribute>=<function>) which means anytime the widget's <attribute>, aka size or pos, is changed, it calls the <function>

    I didn't test this exactly with your code since not all of it is posted, but this is what I do for my projects. Let me know how it works. If it doesn't work, edit your answer to be a snippet of code that I can copy/paste to work with and I'll update my answer.

    class BarWidget(FloatLayout):
        def __init__(self, **kwargs):
            super(BarWidget, self).__init__(**kwargs)
            self.build()
        def build(self):
            # Make it so you can reference the bar and border image later
            self.bar = ProgressBar(pos_hint={'top':0.86,'x':0.01}, max=100, size_hint_x=0.49, size_hint_y=0.1)
            with self.bar.canvas:
                # Make it so you can reference the border image
                self.border_image = BorderImage(border=(10, 10, 10, 10), pos=(bar.x, bar.center_y), size=(self.width, 8), source='0.png')
            self.add_widget(self.bar)
            # Make it so whenever the widget's pos or size changes, we update the border image
            self.bar.bind(pos=self.update_border_image, size=self.update_border_image)
        # make a function to update border image
        def update_border_image(self, *args):
            self.border_image.size = (self.width, 8) # Should this 8 be 0.8 by the way?
            self.border_image.pos = (self.bar.x, self.bar.center_y)