从数据库检索数据并设置为 Kivy 中的文本输入字段和图像小部件,以实现多屏应用程序!属性错误

Ber*_*rch 4 python sqlite python-3.x kivy

我正在通过拼凑一个小应用程序来了解不同小部件的行为来学习 kivy。

什么有效:

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

问题:

按下 RecycleView 上的按钮时,应用程序崩溃并出现错误: AttributeError: 'super' object has no attribute ' getattr '

我尝试过的:

我从这篇文章中了解到,初始化可能不完整,并尝试使用 kivy 时钟进行调度,但这会引发一个新错误 AttributeError:'float' 对象没有属性 'index'。

预期行为:

单击按钮后,在各自的小部件中设置选定的按钮数据(文本和图像值)。我无法理解为什么这在多屏幕环境中不起作用。

完整代码如下。

主要.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()
Run Code Online (Sandbox Code Playgroud)

一千伏

#: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'
Run Code Online (Sandbox Code Playgroud)

两千伏

#: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'
Run Code Online (Sandbox Code Playgroud)

对于这么长的帖子深表歉意,感谢您的时间和关注。

iko*_*lim 5

问题 - 属性错误

\n\n
\n
     self.ids.no.text = self.data_items[columns[0]][\'text\']\n   File "kivy/properties.pyx", line 841, in kivy.properties.ObservableDict.__getattr__\n AttributeError: \'super\' object has no attribute \'__getattr__\'\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

self.ids - 空

\n\n

问题是self.ids空的。

\n\n

根本原因

\n\n

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

\n\n
    \n
  1. class ScreenOne(Screen):var = ScreenTwo()
  2. \n
  3. class SelectableButton(RecycleDataViewBehavior, Button):var = ScreenTwo()
  4. \n
  5. 一个.kv文件中,ScreenTwo:
  6. \n
\n\n

Kv语言\xc2\xbb self.ids

\n\n
\n

当你的 kv 文件被解析时,kivy 会收集所有带有\n id\xe2\x80\x99s 标签的小部件,并将它们放置在self.ids字典类型属性中。

\n
\n\n

解决方案

\n\n

在提供的示例中,我使用的是 SQLite3 数据库,其中包含表、UsersUserIDUserName. 详情请参阅示例。

\n\n

Python代码

\n\n
    \n
  1. var = ScreenTwo()class ScreenOne(Screen):和 中删除,class SelectableButton(RecycleDataViewBehavior, Button):因为您不需要实例化另一个ScreenTwo()与 kv 文件中实例化的对象不同的对象,one.kv.
  2. \n
  3. populate_fields()方法中,替换self.ids.no.text为,self.user_no_text_input.text因为在kv文件(two.kv)中,已经有一个ObjectProperty,已user_no_text_input定义并挂钩到TextInput\的id,nouser_no_text_input: no
  4. \n
  5. filechoser()方法中,删除image_path = self.image_pathandreturn image_path因为 self.image_path 是类的类属性ScreenTwo()
  6. \n
  7. save()方法中,分别将self.ids.no.text和替换为 和self.ids.name.text,因为它们是在 kv 文件中定义并连接到 TextInputs 的,*加上它通常被视为 \xe2\x80\x98 最佳实践\xe2\x80\x99 使用ObjectPropertyself.user_no_text_input.textself.user_name_text_input.texttwo.kv。这将创建直接引用,提供更快的访问并且更加明确。*
  8. \n
\n\n

kv 文件 - one.kv

\n\n
    \n
  1. 删除 和 的所有引用id: screen_managermanager: screen_manager因为默认情况下每个屏幕都有一个属性,该属性为您提供ScreenManagermanager的实例
  2. \n
\n\n

kv 文件 - 二.kv

\n\n
    \n
  1. 在班级规则中,<SelectableButton>:替换root.var.populate_fields(self)app.root.screen_two.populate_fields(self)
  2. \n
\n\n

屏幕默认属性管理器

\n\n
\n

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

\n
\n\n

访问 python 代码中 Kv lang 中定义的小部件

\n\n
\n

虽然self.ids方法很简洁,但一般被视为\xe2\x80\x98使用ObjectProperty的最佳实践\xe2\x80\ x99。这将创建一个直接引用,提供更快的访问并且更加明确。

\n
\n\n

例子

\n\n

主要.py

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

一千伏

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

两千伏

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

输出

\n\n

图像01\n图像02

\n