Retrieve data from db & set to textinput fields and image widget in Kivy for a multiscreen app! AttributeError

后端 未结 1 1018
太阳男子
太阳男子 2021-01-06 18:47

I\'m learning kivy by cobbling together a small application to understand the behavior of different widgets.

What works:

The app accepts te

相关标签:
1条回答
  • 2021-01-06 19:04

    Problem - AttributeError

         self.ids.no.text = self.data_items[columns[0]]['text']
       File "kivy/properties.pyx", line 841, in kivy.properties.ObservableDict.__getattr__
     AttributeError: 'super' object has no attribute '__getattr__'
    

    self.ids - Empty

    The problem was self.ids was empty.

    Root Cause

    There were three instances of class ScreenTwo(). If you apply id() function, it will show three different memory addresses/locations. self.ids is only available when the kv file is parsed. Therefore, self.ids is only available in the instance instantiated in one.kv file.

    1. At class ScreenOne(Screen):, var = ScreenTwo()
    2. At class SelectableButton(RecycleDataViewBehavior, Button):, var = ScreenTwo()
    3. In one.kv file, ScreenTwo:

    Kv language » self.ids

    When your kv file is parsed, kivy collects all the widgets tagged with id’s and places them in this self.ids dictionary type property.

    Solution

    In the example provided, I am using a SQLite3 database containing table, Users with columns, UserID and UserName. Please refer to the example for details.

    Python Code

    1. Remove var = ScreenTwo() from class ScreenOne(Screen): and class SelectableButton(RecycleDataViewBehavior, Button): because you don't need to instantiate another objects of ScreenTwo() which are different from the one instantiated in kv file, one.kv.
    2. In populate_fields() method, replace self.ids.no.text with self.user_no_text_input.text because in kv file (two.kv), there is already an ObjectProperty, user_no_text_input defined and hooked to TextInput's id, no i.e. user_no_text_input: no.
    3. In filechoser() method, remove image_path = self.image_path and return image_path because self.image_path is a class attributes of class ScreenTwo().
    4. In save() method, replace self.ids.no.text and self.ids.name.text with self.user_no_text_input.text and self.user_name_text_input.text respectively because they are defined and hooked-up to the TextInputs in kv file, two.kv *plus it is generally regarded as ‘best practice’ to use the ObjectProperty. This creates a direct reference, provides faster access and is more explicit.*

    kv file - one.kv

    1. Remove all references of id: screen_manager and manager: screen_manager because each screen has by default a property manager that gives you the instance of the ScreenManager used.

    kv file - two.kv

    1. In class rule, <SelectableButton>: replace root.var.populate_fields(self) with app.root.screen_two.populate_fields(self)

    Screen default property manager

    Each screen has by default a property manager that gives you the instance of the ScreenManager used.

    Accessing Widgets defined inside Kv lang in your python code

    Although the self.ids method is very concise, it is generally regarded as ‘best practice’ to use the ObjectProperty. This creates a direct reference, provides faster access and is more explicit.

    Example

    main.py

    import sqlite3
    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.recycleview.views import RecycleDataViewBehavior
    from kivy.uix.button import Button
    from kivy.properties import BooleanProperty, ListProperty, ObjectProperty
    from kivy.uix.recyclegridlayout import RecycleGridLayout
    from kivy.uix.behaviors import FocusBehavior
    from kivy.uix.recycleview.layout import LayoutSelectionBehavior
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.uix.accordion import Accordion
    
    from tkinter.filedialog import askopenfilename
    from tkinter import Tk
    
    
    class Manager(ScreenManager):
        screen_one = ObjectProperty(None)
        screen_two = ObjectProperty(None)
    
    
    class ScreenTwo(BoxLayout, Screen, Accordion):
        data_items = ListProperty([])
    
        def __init__(self, **kwargs):
            super(ScreenTwo, self).__init__(**kwargs)
            self.create_table()
            self.get_table_column_headings()
            self.get_users()
    
        def populate_fields(self, instance): # NEW
            columns = self.data_items[instance.index]['range']
            self.user_no_text_input.text = self.data_items[columns[0]]['text']
            self.user_name_text_input.text = self.data_items[columns[1]]['text']
    
        def get_table_column_headings(self):
            connection = sqlite3.connect("demo.db")
            with connection:
                cursor = connection.cursor()
                cursor.execute("PRAGMA table_info(Users)")
                col_headings = cursor.fetchall()
                self.total_col_headings = len(col_headings)
    
        def filechooser(self):
            Tk().withdraw()
            self.image_path = askopenfilename(initialdir = "/",title = "Select file",filetypes = (("jpeg files","*.jpg"),("all files","*.*")))
            self.image.source = self.image_path
    
        def create_table(self):
            connection = sqlite3.connect("demo.db")
            cursor = connection.cursor()
            sql = """CREATE TABLE IF NOT EXISTS Users(
            UserID integer PRIMARY KEY,
            UserName text NOT NULL)"""
            cursor.execute(sql)
            connection.close()
    
        def get_users(self):
            connection = sqlite3.connect("demo.db")
            cursor = connection.cursor()
    
            cursor.execute("SELECT * FROM Users ORDER BY UserID ASC")
            rows = cursor.fetchall()
    
            # create list with db column, db primary key, and db column range
            data = []
            low = 0
            high = self.total_col_headings - 1
            # Using database column range for populating the TextInput widgets with values from the row clicked/pressed.
            self.data_items = []
            for row in rows:
                for col in row:
                    data.append([col, row[0], [low, high]])
                low += self.total_col_headings
                high += self.total_col_headings
    
            # create data_items
            self.data_items = [{'text': str(x[0]), 'Index': str(x[1]), 'range': x[2]} for x in data]
    
        def save(self):
            connection = sqlite3.connect("demo.db")
            cursor = connection.cursor()
    
            UserID = self.user_no_text_input.text
            UserName = self.user_name_text_input.text
    
            EmpPhoto = open(self.image_path, "rb").read()
    
            try:
                save_sql = "INSERT INTO Users (UserID, UserName) VALUES (?,?)"
                connection.execute(save_sql, (UserID, UserName))
                connection.commit()
                connection.close()
            except sqlite3.IntegrityError as e:
                print("Error: ", e)
    
            self.get_users() #NEW
    
    
    class ScreenOne(Screen):
        pass
    
    
    class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
                                      RecycleGridLayout):
        ''' Adds selection and focus behaviour to the view. '''
    
    
    class SelectableButton(RecycleDataViewBehavior, Button):
        ''' Add selection support to the Button '''
    
        index = None
        selected = BooleanProperty(False)
        selectable = BooleanProperty(True)
    
        def refresh_view_attrs(self, rv, index, data):
            ''' Catch and handle the view changes '''
            self.index = index
            return super(SelectableButton, self).refresh_view_attrs(rv, index, data)
    
        def on_touch_down(self, touch):
            ''' Add selection on touch down '''
            if super(SelectableButton, self).on_touch_down(touch):
                return True
            if self.collide_point(*touch.pos) and self.selectable:
                return self.parent.select_with_touch(self.index, touch)
    
        def apply_selection(self, rv, index, is_selected):
            ''' Respond to the selection of items in the view. '''
            self.selected = is_selected
    
    
    class OneApp(App):
        def build(self):
            return Manager()
    
    
    if __name__ == "__main__":
        OneApp().run()
    

    one.kv

    #:kivy 1.11.0
    #:include two.kv
    
    <Manager>:
        screen_one: screen_one_id 
        screen_two: screen_two_id
    
        ScreenOne:
            id: screen_one_id
            name: 'screen1'
    
        ScreenTwo:
            id: screen_two_id
            name: 'screen2'
    
    <ScreenOne>:
        Button:
            text: "On Screen 1 >> Go to Screen 2"
            on_press: root.manager.current = 'screen2'
    

    two.kv

    #:kivy 1.11.0
    
    <SelectableButton>:
        # Draw a background to indicate selection
        canvas.before:
            Color:
                rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
            Rectangle:
                pos: self.pos
                size: self.size
        on_press:
            app.root.screen_two.populate_fields(self)
    
    <ScreenTwo>:
        user_no_text_input: no
        user_name_text_input: name
        image: image
    
        AccordionItem:
            title: "INPUT FIELDS"
            GridLayout:
                rows:3
                BoxLayout:
                    size_hint: .5, None
                    height: 600
                    pos_hint: {'center_x': 1}
                    padding: 10
                    spacing: 3
                    orientation: "vertical"
    
                    Label:
                        text: "Employee ID"
                        size_hint: (.5, None)
                        height: 30
                    TextInput:
                        id: no
                        size_hint: (.5, None)
                        height: 30
                        multiline: False
                    Label:
                        text: "Employee NAME"
                        size_hint: (.5, None)
                        height: 30
                    TextInput:
                        id: name
                        size_hint: (.5, None)
                        height: 30
                        multiline: False
                    Label:
                        text: "Employee PHOTO"
                        size_hint: (.5, None)
                        height: 30
                    Image:
                        id: image
                        allow_stretch: True
                        keep_ratio: True
                    Button:
                        text: "SELECT IMAGE"
                        size_hint_y: None
                        height: self.parent.height * 0.2
                        on_release: root.filechooser()
                    Button:
                        id: save_btn
                        text: "SAVE BUTTON"
                        height: 50
                        on_press: root.save()
    
        AccordionItem:
            title: "RECYCLE VIEW"
    
            BoxLayout:
                orientation: "vertical"
    
                GridLayout:
                    size_hint: 1, None
                    size_hint_y: None
                    height: 25
                    cols: 2
                    Label:
                        text: "Employee ID"
                    Label:
                        text: "Employee Name"
    
    # Display only the first two columns Employee ID and Employee Name NOT EmployeePhoto on the RecycleView
    
                BoxLayout:
                    RecycleView:
                        viewclass: 'SelectableButton'
                        data: root.data_items
                        SelectableRecycleGridLayout:
                            cols: 2
                            default_size: None, dp(26)
                            default_size_hint: 1, None
                            size_hint_y: None
                            height: self.minimum_height
                            orientation: 'vertical'
                            multiselect: True
                            touch_multiselect: True
                Button:
                    text: "On Screen 2 >> Go to Screen 1"
                    on_press: root.manager.current = 'screen1'
    

    Output

    0 讨论(0)
提交回复
热议问题