Matthew Kelley
Matthew Kelley

Reputation: 1

KivyMD: AttributeError: 'NoneType' object has no attribute 'theme_cls' upon Adding MDToolbar

I'm using Kivy and KivyMD, I'm trying to implement MDToolbar. After running my program I get this error: AttributeError: 'NoneType' object has no attribute 'theme_cls'. I included only the relevant code from my program. Everything works perfectly up until adding the MDToolbar. How can I get MDToolbar to work? Please help!

My Full Error:

Parser: File "<inline>", line 22:
...
     20:<MDToolbar>
     21:    size_hint_y: None
>>   22:    height: root.theme_cls.standard_increment
     23:    padding: [root.theme_cls.horizontal_margins - dp(12), 0]
     24:    opposite_colors: True
...
AttributeError: 'NoneType' object has no attribute 'theme_cls'
  File "C:\Users\kelleym\AppData\Local\Continuum\anaconda3\lib\site-packages\kivy\lang\builder.py", line 696, in _apply_rule
    setattr(widget_set, key, value)
  File "kivy\weakproxy.pyx", line 35, in kivy.weakproxy.WeakProxy.__setattr__
  File "kivy\properties.pyx", line 497, in kivy.properties.Property.__set__
  File "kivy\properties.pyx", line 544, in kivy.properties.Property.set
  File "kivy\properties.pyx", line 599, in kivy.properties.Property.dispatch
  File "kivy\_event.pyx", line 1214, in kivy._event.EventObservers.dispatch
  File "kivy\_event.pyx", line 1120, in kivy._event.EventObservers._dispatch
  File "kivy\properties.pyx", line 1318, in kivy.properties.ReferenceListProperty.trigger_change
  File "kivy\properties.pyx", line 1333, in kivy.properties.ReferenceListProperty.trigger_change
  File "kivy\properties.pyx", line 599, in kivy.properties.Property.dispatch
  File "kivy\_event.pyx", line 1214, in kivy._event.EventObservers.dispatch
  File "kivy\_event.pyx", line 1120, in kivy._event.EventObservers._dispatch
  File "C:\Users\kelleym\AppData\Local\Continuum\anaconda3\lib\site-packages\kivymd\uix\behaviors\elevation.py", line 105, in _update_shadow
    self._shadow = App.get_running_app().theme_cls.quad_shadow
  File "C:\Users\kelleym\Desktop\Actual Inventory App\main.py", line 313, in <module>
    sm.add_widget(inventory(name='inventory'))

My Code:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.theming import ThemeManager

Builder.load_string("""
<inventory>:
    NavigationLayout:
        id: "nav_layout"
        MDToolbar:
            title: "test"
        MDNavigationDrawer:
            drawer_logo: "test.png"
            id: "nav_drawer"
            NavigationDrawerSubheader:
                text: "[color=#black]Categories[/color]"
            NavigationDrawerIconButton:
                icon: 'printer'
                text: "Printers"
                on_release: root.manager.current = 'printers'
            NavigationDrawerIconButton:
                text: "PCs"
                icon: "laptop"
            NavigationDrawerIconButton:
                text: "Monitors"
                icon: "monitor"
            NavigationDrawerIconButton:
                text: "Tablets"
                icon: "tablet"
            NavigationDrawerIconButton:
                text: "Non-HP"
                icon: "close-circle-outline"
            NavigationDrawerIconButton:
                text: "Supplies"
                icon: "water"
            NavigationDrawerIconButton:
                text: "Misc."
                icon: "paperclip"
        Button:
            text: "test"
            on_release: app.nav_drawer.toggle()
""")


class inventory(BoxLayout, Screen):
    pass


sm = ScreenManager()
sm.add_widget(inventory(name='inventory'))


class TestApp(App):
    theme_cls = ThemeManager()

    def build(self):
        return sm


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

Upvotes: 0

Views: 1776

Answers (2)

Oras
Oras

Reputation: 124

Another example that works


from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ListProperty

from kivymd.app import MDApp
from kivymd.theming import ThemableBehavior
from kivymd.uix.list import OneLineIconListItem, MDList

KV = '''
# Menu item in the DrawerList list.
<ItemDrawer>:
    theme_text_color: "Custom"
    on_release: self.parent.set_color_item(self)

    IconLeftWidget:
        id: icon
        icon: root.icon
        theme_text_color: "Custom"
        text_color: root.text_color


<ContentNavigationDrawer>:
    orientation: "vertical"
    padding: "8dp"
    spacing: "8dp"

    AnchorLayout:
        anchor_x: "left"
        size_hint_y: None
        height: avatar.height

        Image:
            id: avatar
            size_hint: None, None
            size: "56dp", "56dp"
            source: "data/logo/kivy-icon-256.png"

    MDLabel:
        text: "KivyMD library"
        font_style: "Button"
        size_hint_y: None
        height: self.texture_size[1]

    MDLabel:
        text: "[email protected]"
        font_style: "Caption"
        size_hint_y: None
        height: self.texture_size[1]

    ScrollView:

        DrawerList:
            id: md_list



Screen:

    MDNavigationLayout:

        ScreenManager:

            Screen:

                BoxLayout:
                    orientation: 'vertical'

                    MDToolbar:
                        title: "Navigation Drawer"
                        elevation: 10
                        left_action_items: [['menu', lambda x: nav_drawer.set_state("open")]]

                    Widget:


        MDNavigationDrawer:
            id: nav_drawer
            
            ContentNavigationDrawer:
                id: content_drawer
                nav_drawer: nav_drawer
'''


class ContentNavigationDrawer(BoxLayout):
    pass


class ItemDrawer(OneLineIconListItem):
    icon = StringProperty()
    text_color = ListProperty((0, 0, 0, 1))


class DrawerList(ThemableBehavior, MDList):
    def set_color_item(self, instance_item):
        """Called when tap on a menu item."""

        # Set the color of the icon and text for the menu item.
        for item in self.children:
            if item.text_color == self.theme_cls.primary_color:
                item.text_color = self.theme_cls.text_color
                break
        instance_item.text_color = self.theme_cls.primary_color


class TestNavigationDrawer(MDApp):
    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        icons_item = {
            "folder": "My files",
            "account-multiple": "Shared with me",
            "star": "Starred",
            "history": "Recent",
            "checkbox-marked": "Shared with me",
            "upload": "Upload",
        }
        for icon_name in icons_item.keys():
            self.root.ids.content_drawer.ids.md_list.add_widget(
                ItemDrawer(icon=icon_name, text=icons_item[icon_name])
            )


TestNavigationDrawer().run()

enter image description here

Upvotes: 0

ArtemSBulgakov
ArtemSBulgakov

Reputation: 1090

All code here works with Kivy 1.11.1 and KivyMD 0.102.0. KivyMD is in alpha status and API can be changed in future.

  1. Create all widgets inside build function. You should move creating of ScreenManager:
    def build(self):
        sm = ScreenManager()
        sm.add_widget(inventory(name='inventory'))
        return sm
  1. NavigationLayout cannot take more than two widgets, one of them is MDNavigationDrawer and second is layout. Create BoxLayout and put in it MDToolbar and Button:
    NavigationLayout:
        MDNavigationDrawer:
            # ...
            NavigationDrawerSubheader:
                # ...
        BoxLayout:
            orientation: "vertical"
            MDToolbar:
                title: "test"
            Button:
                text: "test"
                on_release: app.nav_drawer.toggle()
  1. Id property in kv code should be without "":
    NavigationLayout:
        id: nav_layout
        MDNavigationDrawer:
            id: nav_drawer
  1. To call widget by id you should use app.root.ids.nav_drawer.

  2. To toggle NavigationDrawer you should call NavigationLayout: app.root.ids.nav_layout.toggle_nav_drawer().

  3. Usually NavigationLayout used as root widget. Add ScreenManager to BoxLayout.

  4. You can add screens to ScreenManager using kv code:

        ScreenManager:
            id: screen_manager

            Inventory:
                name: "inventory"
  1. Do not combine layouts. Do not write class Inventory(BoxLayout, Screen):. Create BoxLayout inside your screen:
<Inventory>:
    BoxLayout:
        orientation: "vertical"

Final code will be:

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import Screen
from kivymd.theming import ThemeManager

root_kv = """
NavigationLayout:
    id: nav_layout

    MDNavigationDrawer:
        id: nav_drawer
        drawer_logo: "test.png"

        NavigationDrawerSubheader:
            text: "Categories"
        NavigationDrawerIconButton:
            text: "Printers"
            icon: "printer"
            on_release: app.screen_manager.current = "printers"
        NavigationDrawerIconButton:
            text: "PCs"
            icon: "laptop"
        NavigationDrawerIconButton:
            text: "Monitors"
            icon: "monitor"
        NavigationDrawerIconButton:
            text: "Tablets"
            icon: "tablet"
        NavigationDrawerIconButton:
            text: "Non-HP"
            icon: "close-circle-outline"
        NavigationDrawerIconButton:
            text: "Supplies"
            icon: "water"
        NavigationDrawerIconButton:
            text: "Misc."
            icon: "paperclip"

    BoxLayout:
        orientation: "vertical"

        ScreenManager:
            id: screen_manager

            Inventory:
                name: "inventory"


<Inventory>:
    BoxLayout:
        orientation: "vertical"

        MDToolbar:
            title: "test"

        Button:
            text: "test"
            on_release: app.root.toggle_nav_drawer()
"""


class Inventory(Screen):
    pass


class MainApp(App):
    theme_cls = ThemeManager()
    screen_manager = ObjectProperty()

    def build(self):
        self.root = Builder.load_string(root_kv)
        self.screen_manager = self.root.ids.screen_manager


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

Upvotes: 1

Related Questions