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)
对于这么长的帖子深表歉意,感谢您的时间和关注。
\n\n\nRun Code Online (Sandbox Code Playgroud)\nself.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
问题是self.ids空的。
ScreenTwo() 类共有三个实例。如果您应用id()函数,它将显示三个不同的内存地址/位置。self.ids仅当解析 kv 文件时才可用。因此,仅在one.kvself.ids文件中实例化的实例中可用。
class ScreenOne(Screen):,var = ScreenTwo()class SelectableButton(RecycleDataViewBehavior, Button):,var = ScreenTwo()ScreenTwo:\n\n\n当你的 kv 文件被解析时,kivy 会收集所有带有\n id\xe2\x80\x99s 标签的小部件,并将它们放置在self.ids字典类型属性中。
\n
在提供的示例中,我使用的是 SQLite3 数据库,其中包含表、Users列UserID和UserName. 详情请参阅示例。
var = ScreenTwo()从class ScreenOne(Screen):和 中删除,class SelectableButton(RecycleDataViewBehavior, Button):因为您不需要实例化另一个ScreenTwo()与 kv 文件中实例化的对象不同的对象,one.kv.populate_fields()方法中,替换self.ids.no.text为,self.user_no_text_input.text因为在kv文件(two.kv)中,已经有一个ObjectProperty,已user_no_text_input定义并挂钩到TextInput\的id,no即user_no_text_input: no。filechoser()方法中,删除image_path = self.image_pathandreturn image_path因为 self.image_path 是类的类属性ScreenTwo()。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。这将创建直接引用,提供更快的访问并且更加明确。*id: screen_manager,manager: screen_manager因为默认情况下每个屏幕都有一个属性,该属性为您提供ScreenManagermanager的实例。<SelectableButton>:替换root.var.populate_fields(self)为app.root.screen_two.populate_fields(self)\n\n\n\n\n默认情况下,每个屏幕都有一个属性管理器,它为您提供所使用的 ScreenManager 实例。
\n
\n\n\n虽然
\nself.ids方法很简洁,但一般被视为\xe2\x80\x98使用ObjectProperty的最佳实践\xe2\x80\ x99。这将创建一个直接引用,提供更快的访问并且更加明确。
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()\nRun Code Online (Sandbox Code Playgroud)\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\'\nRun Code Online (Sandbox Code Playgroud)\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\'\nRun Code Online (Sandbox Code Playgroud)\n\n