Search code examples
pythonkivy

Unknown class <classname> error when using Screenmanager in .kv but screenmanager works fine in .py


I'm trying to make this app which has multiple screens. I was creating the screenmanager in my .py file and everything was working as expected but i figured that i need to write the screenmanager in my .kv file. But it's just not working. I get this error kivy.factory.FactoryException: Unknown class <Home> My home class is properly defined in my .kv file so i have no clue about why this is happening. i'm guessing that maybe i need to define the current screen somehow but idk how to. only the screenmanager and home class is worth looking at i think. Any help or guidance is appreciated.

this is my kv file

#:import dp kivy.metrics.dp
#:import ScreenManager kivy.uix.screenmanager.ScreenManager
#:import Screen kivy.uix.screenmanager.ScreenManager
#:set navbar_button_color (59/255, 68/255, 75/255, 1)

ScreenManager:
    id: screen_manager
    Home:
        id: home
        name: "home_screen"
        manager: "screen_manager"
    Daily:
        id: daily
        name: "daily_screen"
        manager: "screen_manager"
    Weekly:
        id: weekly
        name: "weekly_screen"
        manager: "screen_manager"
    Monthly:
        id: monthly
        name: "monthly_screen"
        manager: "screen_manager"
    Yearly:
        id: yearly
        name: "yearly_screen"
        manager: "screen_manager"                

<MyLayout>:
    my_grid: my_grid
    expense_button: expense_button
    orientation: "vertical"
    BoxLayout:
        size_hint: 1,0.1
        Button:
            text: "Back"
            size_hint: 0.1,1
            # on_press:
            #     app.root.manager.current = "home_screen"
            #     app.root.manager.transition.direction = "right" 
        Button:
            text: "Home"
            size_hint: 0.1,1
        Button:
            text: "Daily"
            size_hint: 0.1,1
        Button:
            text: "Weekly"
            size_hint: 0.1,1
        Button:
            text: "Monthly" 
            size_hint: 0.1,1  
        Button:
            text: "Load" 
            size_hint: 0.1,1 
            on_press: my_grid.load()               
        Button:
            text: "Save" 
            size_hint: 0.1,1 
            on_press: my_grid.save() 
        Label:
            text: dt.today().strftime("%d %B %Y")
            size_hint: 0.3,1
            canvas.before:
                Color:
                    rgb: 59/255, 78/255, 85/255,1
                Rectangle:
                    size: self.size
                    pos: self.pos                  
    BoxLayout:
        size_hint: 1,0.15
        Button:
            text: "EXPENSES"            
        Button:
            text: "REVENUE"    
    HeaderBox:
    MyScroll:    
        MyGrid:
            id: my_grid
            size_hint: 1, None
            height: self.minimum_height 
    BoxLayout:
        orientation:"horizontal"
        size_hint: 1,0.2       
        BoxLayout:
            orientation: "vertical"
            Button:
                id: expense_button
                text: "Expense Total:"
                font_size: dp(20)
                on_press:
                    self.text = "Expense Total: " + str(my_grid.expense_total)
                    revenue_button.text = "Revenue Total: " + str(my_grid.revenue_total)
                    profit_button.text = "Profit: " + str(my_grid.profit)
            Button:
                id: revenue_button
                text: "Revenue Total: " 
                font_size: dp(20)
                on_press:
                    expense_button.text = "Expense Total: " + str(my_grid.expense_total)
                    self.text = "Revenue Total: " + str(my_grid.revenue_total)
                    profit_button.text = "Profit: " + str(my_grid.profit)
        Button:
            id: profit_button
            text: "Profit:"
            font_size: dp(40)       
            on_press:
                expense_button.text = "Expense Total: " + str(my_grid.expense_total)
                revenue_button.text = "Revenue Total: " + str(my_grid.revenue_total)
                self.text = "Profit: " + str(my_grid.profit)            

<Home>:
    BoxLayout:
        orientation: "horizontal"
        BoxLayout:
            orientation: "vertical"
            size_hint: 0.65, 1
            spacing: 5
            padding: 5
            Button:
                text: "Daily"
                font_size: 40
                size_hint: 1, 0.75
                background_color: 59/255, 68/255, 75/255, 1
                on_press:
                    root.manager.current = "daily_screen"
                    root.manager.transition.direction = "left" 
                    root.manager.transition.duration = 0.8
            Button:
                text: "Yearly"
                font_size: 40  
                size_hint: 1, 0.25
                background_color: 101/255, 115/255, 131/255, 1
        BoxLayout:
            orientation: "vertical"
            size_hint: 0.35,1
            spacing: 5
            padding: [0,5,5,5]
            Button:
                text: "Weekly"
                font_size: 40
                size_hint: 1, 0.50
                background_color: 84/255, 98/255, 111/255, 1
            Button:
                text: "Monthly"
                font_size: 40  
                size_hint: 1, 0.50
                background_color: 152/255, 175/255, 199/255, 1

<Daily>:
    MyLayout:    
<Weekly>:
    Label:
        text:"Weekly"
<Monthly>:
    Label: 
        text:"Monthly"
<Yearly>:        ```


#for @ApuCoder #i saw a similar question and changed the return statement of my App class. now it throws a different error. Also i don't think the home class is causing the issue, coz i removed that class n then it said the next class was undefined. so it's something else. Anyway, i have a new error to deal with now.

root.manager.current = "daily_screen" AttributeError: 'str' object has no attribute 'current'```

class Home(Screen):
    pass

class MyApp(App):
    def build(self):
        return Builder.load_file("sample1.kv")

if __name__ == "__main__":
    MyApp().run()        

Solution

  • There are some issues with your code, let's try to address those one by one.

    1. In kvlang you either design a predefined class from .py or a class defined within itself. Thus either you'll do in .py

    class MyButton(Button):
        # Some prop. and/or methods if needed.
        # Otherwise,
        pass
    

    Then in kvlang for this class,

    <MyButton>:
        # Whatever you want...
    

    Or equivalently you can define and design that class (dynamic class ) in kvlang as,

    <MyButton@Button>:
        # Here is your design rule.
    

    Now without inheriting a new class (not a default one) from a Widget (or to be precise from EventDispatcher) if you try to design it within kvlang a FactoryException will be raised.

    2. The Screen widgets has a read-only property manager that is essentially a ScreenManager widget. Thus when you do something like this,

    ScreenManager:
        id: screen_manager
        Home:
            id: home
            name: "home_screen"
            manager: "screen_manager"
    

    i.e. assigning a string to it, you just overwrite its default manager by a string, that's why you got the error message AttributeError: 'str' object has no attribute 'current' when you tried something like this

    root.manager.current = "daily_screen"
    

    from that screen.

    What you need to do is,

    ScreenManager:
        id: screen_manager # Pass this id to the screen.
        Home:
            id: home
            name: "home_screen"
            manager: screen_manager # Although its not necessary to pass it over.
    

    Also I think you can't assign it in python as it is a read-only property. In kvlang, perhaps this is different.