Получите данные из базы данных и установите для полей ввода текста и виджета изображения в Kivy для многоэкранного приложения! AttributeError

1

Я изучаю киви, объединяя небольшое приложение, чтобы понять поведение разных виджетов.

Что работает:

Приложение принимает текст и изображения в качестве входных и хранилищ в базу данных, сохраненные данные корректно отображаются на кнопках с помощью RecycleView.

Проблема:

При нажатии кнопок на RecycleView приложение выходит из строя с ошибкой: AttributeError: объект 'super' не имеет атрибута ' getattr '

Что я пробовал:

Я понимаю из этого сообщения, что инициализация может быть неполной и попробоваться при расписании с часами kivy, но это порождает новую ошибку 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'

Извиняюсь за действительно длинный пост, я благодарю вас за ваше время и внимание.

Теги:
python-3.x
sqlite3
kivy

1 ответ

1
Лучший ответ

Проблема - 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 доступен только при self.ids файла kv. Поэтому self.ids доступен только в экземпляре, созданном в файле one.kv.

  1. В class ScreenOne(Screen): var = ScreenTwo()
  2. В class SelectableButton(RecycleDataViewBehavior, Button): var = ScreenTwo()
  3. В файле ScreenTwo:

Kv язык "self.ids

Когда ваш файл kv разбирается, kivy собирает все виджеты с тегами с идентификаторами и помещает их в это свойство типа слова self.ids.

Решение

В приведенном примере я использую базу данных SQLite3, содержащую таблицу, Users с столбцами, UserID и UserName. Подробные сведения см. В этом примере.

Код Python

  1. Удалить var = ScreenTwo() из class ScreenOne(Screen): и class SelectableButton(RecycleDataViewBehavior, Button): потому что вам не нужно создавать экземпляры других объектов ScreenTwo() которые отличаются от экземпляра, one.kv в файле kv, one.kv,
  2. В методе self.ids.no.text populate_fields() замените self.ids.no.text на self.user_no_text_input.text потому что в файле kv (two.kv) уже существует ObjectProperty, user_no_text_input определенный и подключенный к идентификатору TextInput, no ie user_no_text_input: no.
  3. В filechoser() удалите image_path = self.image_path и return image_path потому что self.image_path - это атрибуты класса класса ScreenTwo().
  4. В методе save() замените self.ids.no.text и self.ids.name.text с self.user_no_text_input.text и self.user_name_text_input.text соответственно, потому что они определены и подключены к TextInputs в файле kv, two.kv * plus обычно рассматривается как "наилучшая практика использования ObjectProperty. Это создает прямую ссылку, обеспечивает более быстрый доступ и более явственно. *

kv файл - one.kv

  1. Удалите все ссылки id: screen_manager и manager: screen_manager потому что каждый экран по умолчанию имеет manager свойств, который дает вам экземпляр используемого ScreenManager.

файл kv - two.kv

  1. В правиле класса <SelectableButton>: замените root.var.populate_fields(self) на app.root.screen_two.populate_fields(self)

Диспетчер свойств по умолчанию

Каждый экран по умолчанию имеет менеджер свойств, который дает вам экземпляр используемого ScreenManager.

Доступ к виджетам, определенным внутри Kv lang, в вашем коде на Python

Хотя метод self.ids очень краток, он обычно считается "лучшей практикой использования ObjectProperty. Это создает прямую ссылку, обеспечивает более быстрый доступ и более явственно.

пример

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'

Выход

Изображение 174551 Изображение 174551

  • 0
    Большое спасибо за ваш ценный вклад с разбивкой кода, пояснения и ссылки на лучшие практики .... Не могли бы вы предложить, как получить и отобразить изображения в виджете изображений для нажатия кнопки, как это делается для полей ввода текста, чтобы это полностью отвечает на мой вопрос.
  • 0
    Поскольку EmpID доступен, когда нажата кнопка RV, выполните fetchone() для EmpPhoto для demo.db и конвертируйте BLOB с помощью Kivy CoreImage .
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню