Search code examples
pythonpython-3.xkivykivy-languagekivymd

how do I select directories and files in KivyMD


I am trying to select a file (picture), through a simple app I made using file manager, but I keep getting this error message: kivy.uix.widget.WidgetException: Cannot add <kivymd.uix.filemanager.filemanager.MDFileManager object at 0x000001D98DC5F890>, it already has a parent <kivy.uix.modalview.ModalView object at 0x000001D98DC5F4A0>, whenever I click on the button to access files (i.e to open directories). I don't know want is wrong, I don't even know what I am doing wrong.

I would appreciate any help thanks.

Here's my .py file

from kivy.factory import Factory
from kivy.uix.modalview import ModalView

from kivymd.uix.filemanager import MDFileManager
from kivymd.theming import ThemeManager
from kivymd.toast import toast


from kivymd.app import MDApp
from kivy.core.window import Window


Window.size = (300, 530)


class MainApp(MDApp):
    title = "File Manage"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Window.bind(on_keyboard=self.events)
        self.manager_open = False
        self.manager = None

    def build(self):
        self.theme_cls.primary_palette = 'Gray'
        self.theme_cls.primary_hue = '200'
        self.theme_cls.theme_style = 'Dark'

        return Factory.ExampleFileManager()

    def file_manager_open(self):
        if not self.manager:
            self.manager = ModalView(size_hint=(1, 1), auto_dismiss=False)
            self.file_manager = MDFileManager(
                exit_manager=self.exit_manager, select_path=self.select_path)
            self.manager.add_widget(self.file_manager)
            self.file_manager.show('/')  # output manager to the screen
        self.manager_open = True
        self.manager.open()

    def select_path(self, path):
        '''It will be called when you click on the file name
        or the catalog selection button.

        :type path: str;
        :param path: path to the selected directory or file;
        '''

        self.exit_manager()
        toast(path)

    def exit_manager(self, *args):
        '''Called when the user reaches the root of the directory tree.'''

        self.manager.dismiss()
        self.manager_open = False

    def events(self, instance, keyboard, keycode, text, modifiers):
        '''Called when buttons are pressed on the mobile device..'''

        if keyboard in (1001, 27):
            if self.manager_open:
                self.file_manager.back()
        return True


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

and here's my .kv file:

<ExampleFileManager@BoxLayout>
    orientation: 'vertical'

    MDToolbar:
        id: progress_toolbar
        title: 'Progress'

    ScrollView:
        MDGridLayout:
            cols: 2
            adaptive_height: True
            spacing: (10, 15)
            padding: [25, 25]

            MDLabel:
                halign: 'center'
                text: 'Before'

            MDLabel:
                halign: 'center'
                text: 'Now'

            MDCard:
                ripple_behavior: True
                orientation: 'vertical'
                size_hint_y: None
                size: 120, 220
                elevation: 15
                radius: 8
                MDIconButton:
                    icon: "camera-outline"
                    user_font_size: "64sp"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_release: app.file_manager_open()

            MDCard:
                ripple_behavior: True
                orientation: 'vertical'
                size_hint_y: None
                size: 120, 220
                elevation: 15
                radius: 8
                MDIconButton:
                    icon: "camera-outline"
                    user_font_size: "64sp"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_release: app.file_manager_open()


            MDTextField:
                hint_text: 'Date'
                width: 100

            MDTextField:
                hint_text: 'Date'
                width: 100

            MDTextField:
                hint_text: 'Weight'
                width: 80

            MDTextField:
                hint_text: 'Weight'
                width: 80

Edit: New code:

from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.core.window import Window
from plyer import filechooser

Window.size = (300, 530)

KV = """
MDBoxLayout:
    orientation: 'vertical'

    MDToolbar:
        id: progress_toolbar
        title: 'Progress'

    ScrollView:
        MDGridLayout:
            cols: 2
            adaptive_height: True
            spacing: (10, 15)
            padding: [25, 25]

            MDLabel:
                halign: 'center'
                text: 'Before'

            MDLabel:
                halign: 'center'
                text: 'Now'

            MDCard:
                ripple_behavior: True
                orientation: 'vertical'
                size_hint_y: None
                size: 120, 220
                elevation: 15
                radius: 8
                MDIconButton:
                    icon: "camera-outline"
                    user_font_size: "24sp"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_release: app.file_chooser1()
                Image:
                    id: img1
                    allow_stretch: True
                    keep_ratio: False
                    # size_hint_y: .5

            MDCard:
                ripple_behavior: True
                orientation: 'vertical'
                size_hint_y: None
                size: 120, 220
                elevation: 15
                radius: 8
                MDIconButton:
                    icon: "camera-outline"
                    user_font_size: "24sp"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_release: app.file_chooser2()
                Image:
                    id: img2
                    allow_stretch: True
                    keep_ratio: False
                    # size_hint_y: .5


            MDTextField:
                hint_text: 'Date'
                width: 100

            MDTextField:
                hint_text: 'Date'
                width: 100

"""


class Example(MDApp):

    def build(self):
        return Builder.load_string(KV)

    def file_chooser1(self):
        filechooser.open_file(on_selection=self.selected1)

    def file_chooser2(self):
        filechooser.open_file(on_selection=self.selected2)

    def selected1(self, selection1):
        self.root.ids.img1.source = selection1[0]

    def selected2(self, selection2):
        self.root.ids.img2.source = selection2[0]


Example().run()

Solution

  • Instead of using the FileManager widget, there is actually a better alternative which is from the plyer module, specifically the filechooser API. What it does is to open up the default file manager app of your device to either choose folder, file or save file. Here is an example:

    def file_manager_open(self):
        from plyer import filechooser
        path = filechooser.open_file()[0] 
        # this method returns a list with the first index
        # being the path of the file selected
        toast(path)
    

    More information regarding the APIs of this library here.

    One tip I can give you is that because this file chooser is very weak on the Windows platform, I would recommend using tkinter's filedialog if you want.