从数据库中检索数据并在Kivy中将其设置为textinput字段和图像小部件,以用于多屏幕应用程序! AttributeError

时间:2018-07-31 14:09:46

标签: python python-3.x sqlite kivy

我正在学习如何通过组合一个小应用程序来了解不同的小部件的行为。

有效的方法:

该应用程序接受文本和图像作为输入并存储到数据库,使用RecycleView在按钮上正确显示存储的数据。

问题:

按下RecycleView上的按钮时,应用程序崩溃并显示以下错误:AttributeError:'super'对象没有属性' getattr '

我尝试过的事情:

我从post中了解到,初始化可能无法完成,并尝试使用奇异时钟进行调度,但这会引发新的错误AttributeError:“ float”对象没有属性“ index”。

预期的行为:

在按钮上单击可在其各自的小部件中设置选定的按钮数据(文本和图像值)。我无法理解为什么在多屏环境中无法正常工作。

完整代码如下。

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, StringProperty, 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 kivy.clock import Clock

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)
        # Clock.schedule_once(self.populate_fields)
        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.ids.no.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
        image_path = self.image_path
        return image_path

    def create_table(self):
        connection = sqlite3.connect("demo.db")
        cursor = connection.cursor()
        sql = """CREATE TABLE IF NOT EXISTS Employees(
        EmpID integer PRIMARY KEY,
        EmpName text NOT NULL,
        EmpPhoto blob NOT NULL)"""
        cursor.execute(sql)
        connection.close()

    def get_users(self):
        connection = sqlite3.connect("demo.db")
        cursor = connection.cursor()

        cursor.execute("SELECT * FROM Employees ORDER BY EmpID 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()

        EmpID = self.ids.no.text
        EmpName = self.ids.name.text
        image_path = self.image_path # -- > return value from fielchooser

        EmpPhoto = open(image_path, "rb").read()

        try:
            save_sql="INSERT INTO Employees (EmpID, EmpName, EmpPhoto) VALUES (?,?,?)"
            connection.execute(save_sql,(EmpID, EmpName, EmpPhoto))
            connection.commit()
            connection.close()
        except sqlite3.IntegrityError as e:
            print("Error: ",e)

        self.get_users() #NEW

class ScreenOne(Screen):
    var = ScreenTwo()

class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
                                  RecycleGridLayout):
    ''' Adds selection and focus behaviour to the view. '''

class SelectableButton(RecycleDataViewBehavior, Button):
    ''' Add selection support to the Button '''

    var = ScreenTwo()
    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.10.0
#:include two.kv

<Manager>:
    id: screen_manager
    screen_one: screen_one_id # original name: our set name
    screen_two: screen_two_id

    ScreenOne:
        id: screen_one_id # our set name
        name: 'screen1'
        manager: screen_manager # telling each screen who its manager is.

    ScreenTwo:
        id: screen_two_id # our set name
        name: 'screen2'
        manager: screen_manager

<ScreenOne>:
    Button:
        text: "On Screen 1 >> Go to Screen 2"
        on_press: root.manager.current = 'screen2'

two.kv

#:kivy 1.10.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:
        root.var.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'

很长的帖子很抱歉,谢谢您的时间和关注。

1 个答案:

答案 0 :(得分:2)

问题-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-空

问题是self.ids为空。

根本原因

ScreenTwo() 类的三个实例。如果应用id()函数,它将显示三个不同的内存地址/位置。 self.ids仅在解析kv文件时可用。因此,self.ids仅在 one.kv 文件中实例化的实例中可用。

  1. class ScreenOne(Screen):var = ScreenTwo()
  2. class SelectableButton(RecycleDataViewBehavior, Button):var = ScreenTwo()
  3. one.kv 文件中,ScreenTwo:

Kv language » self.ids

  

当解析您的kv文件时,kivy会收集所有标有的小部件   id,并将其放在此 self.ids 字典类型属性中。

解决方案

在提供的示例中,我正在使用一个包含表Users和列UserIDUserName的SQLite3数据库。有关详细信息,请参阅示例。

Python代码

  1. var = ScreenTwo()class ScreenOne(Screen):中删除class SelectableButton(RecycleDataViewBehavior, Button):是因为您不需要实例化ScreenTwo()的另一个对象,该对象不同于kv文件中实例化的对象{{ 1}}。
  2. one.kv方法中,将populate_fields()替换为self.ids.no.text,因为在kv文件(self.user_no_text_input.text)中,已经定义了一个ObjectProperty,two.kv并将其挂钩TextInput的ID user_no_text_input,即no
  3. user_no_text_input: no方法中,删除filechoser()image_path = self.image_path,因为self.image_path是类return image_path的类属性。
  4. ScreenTwo()方法中,用save()self.ids.no.text分别替换self.ids.name.textself.user_no_text_input.text,因为它们是定义的并且已连接到kv文件中的TextInputs,self.user_name_text_input.text *加使用 ObjectProperty 通常被认为是“最佳实践”。这将创建直接引用,提供更快的访问速度,并且更加明确。*

kv文件-one.kv

  1. 删除所有two.kvid: screen_manager的引用,因为每个屏幕默认都有一个属性manager: screen_manager,该属性为您提供所用 ScreenManager 的实例。

kv文件-two.kv

  1. 在课堂规则中,manager<SelectableButton>:替换root.var.populate_fields(self)

Screen default property manager

  

每个屏幕默认都有一个属性管理器,可为您提供   使用的ScreenManager实例。

Accessing Widgets defined inside Kv lang in your python code

  

尽管app.root.screen_two.populate_fields(self)方法非常简洁,但通常   被视为使用 ObjectProperty 的“最佳做法”。这个   创建直接引用,提供更快的访问权限以及更多   明确的。

示例

main.py

self.ids

one.kv

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()

two.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'

输出

Img01 Img02