Search code examples
pythonkivy

Reference attribute of element that is embedded in canvas (Kivy)


I am trying to reference the source image of a Rectangle object in a canvas. I don't seem to find the correct code that will correctly reference it. Trying with ids doesn't work because the rectangle cant be given an id, only a group. Therefore, I need to use a group, but this is complex because I have several embedded attributes. Can anyone help? The current solution using self.canvas.get_group("firstQelement")[0].source I found online when a user had an issue with a similar problem. I have tried using different paths to get to the group, including referencing ids and layouts.

AnswerCycle refers to a pre formatted toggle button

Kivy File:

<QuestionDisplay>:
    name: "questionDisplay"
    RelativeLayout:
        canvas.before:
            Color:
                rgba: utils.get_color_from_hex('#90E0EF')
            Rectangle:
                pos: self.pos
                size: self.size
    GridLayout:
        padding: 20
        spacing: 20
        cols: 1
        size: root.width, root.height
        Title: 
            id: questionTitle
        Image:
            canvas.before:
                Color:
                    rgba: utils.get_color_from_hex('#0077B6')
                Rectangle:
                    size: self.size
                    pos: self.pos
            id: mainQuestion
            source: "empty.png"
            size_hint_y: None
            size: root.width, 195
        GridLayout:
            size_hint_y: None
            size: root.width, 200
            cols: 2
            AnswerCycle:
                id: firstQ
                canvas.after:
                    Rectangle
                        group: "firstQelement"
                        pos: self.pos
                        size: self.size
                        source: "empty.png"
            AnswerCycle:
                id: secondQ
                canvas.after:
                    Rectangle
                        group: "secondQelement"
                        pos: self.pos
                        size: self.size
                        source: "empty.png"
            AnswerCycle:
                id: fourthQ
                canvas.after:
                    Rectangle
                        group: "thirdQelement"
                        pos: self.pos
                        size: self.size
                        source: "empty.png"
            AnswerCycle:
                id: thirdQ
                canvas.after:
                    Rectangle
                        group: "fourthQelement"
                        pos: self.pos
                        size: self.size
                        source: "empty.png"

Python Code:

if Value.correctButtonNumber ==  1: self.canvas.get_group("firstQelement")[0].source = "cAnswer.png"
if Value.correctButtonNumber ==  2: self.canvas.get_group("secondQelement")[0].source = "cAnswer.png"
if Value.correctButtonNumber ==  3: self.canvas.get_group("thirdQelement")[0].source = "cAnswer.png"
if Value.correctButtonNumber ==  4: self.canvas.get_group("fourthQelement")[0].source = "cAnswer.png"

Error:

if Value.correctButtonNumber ==  1: self.canvas.get_group("firstQelement")[0].source = "cAnswer.png"
 IndexError: list index out of range

Minimal Reproducible Example:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
import random

APP_KV = """
<CanvasTest>:
    BoxLayout:
        canvas.after:
            Color:
                rgba: 0, 1, 0, 1
            Rectangle:
                group: 'rectangle'
                size: 400, 200
                pos: self.pos
            Color:
                rgba: 1, 0, 0, 1
            Ellipse:
                group: 'ellipse'
                size: 200, 100
                pos: self.pos
"""

class CanvasTest(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        print(self.BoxLayout.canvas.after.get_group('rectangle'))
    
class MainApp(App):
    def build(self):
        self.root = Builder.load_string(APP_KV)
        return CanvasTest()

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

When BoxLayout: is removed, from both the KV script and the print statement, this code works correctly.


Solution

  • Here is a modified version of your code that changes the source attribute of the Rectangle:

    from kivy.app import App
    from kivy.clock import Clock
    from kivy.lang import Builder
    from kivy.uix.boxlayout import BoxLayout
    
    APP_KV = """
    <CanvasTest>:
        BoxLayout:
            id: box
            canvas.after:
                Color:
                    rgba: 0, 1, 0, 1
                Rectangle:
                    group: 'rectangle'
                    size: 400, 200
                    pos: self.pos
                Color:
                    rgba: 1, 0, 0, 1
                Ellipse:
                    group: 'ellipse'
                    size: 200, 100
                    pos: self.pos
    """
    
    
    class CanvasTest(BoxLayout):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            Clock.schedule_once(self.info, 2)
    
        def info(self, dt):
            rect = self.ids.box.canvas.after.get_group('rectangle')[0]
            rect.source = 'tester.png'
    
    
    class MainApp(App):
        def build(self):
            self.root = Builder.load_string(APP_KV)
            return CanvasTest()
    
    
    if __name__ == '__main__':
        MainApp().run()
    

    I added the id of box to the BoxLayout in the APP_KV to allow access to that BoxLayout. Note that you shouldn't try to access ids in the __init__() method because they typically have not been set yet at that point. That is why I used Clock.schedule_once().