首页 > 解决方案 > 从 Kivy 应用程序获取用户输入并使用 Python 附加到谷歌表格

问题描述

我正在尝试制作一个简单的 Python Kivy 应用程序(使用 kv 文件进行布局),我将使用它来保存我的零件库存以供工作。我对 python 和 kivy 很陌生,所以我需要一些指导。

基本功能之一是允许用户在“将零件添加到库存”屏幕(四个屏幕之一)上输入新零件。用户输入包括四个项目(零件名称、序列号、现有数量和所需的最低数量)。

一旦用户输入此信息并按下“提交”按钮,我希望将该数据附加到我使用 Google Drive/Sheets API(我是能够开始工作)。

我在网上找到的大多数示例应用程序只有一个屏幕,并且只处理一种类型的用户输入(通常是文本,而我的输入是文本和整数)。

我很困惑我需要将我的 id 放在 kv 文件中的哪个位置(我假设在 AddPartWindow 布局下),当按下不在根类中的按钮时如何调用函数,如何存储用户输入到列表中,因为我不确定 ObjectProperty 函数到底在做什么,最后,如何将该列表附加到我的谷歌表中的新行。

我不是在寻找剪切和粘贴的答案,只是一些方向会很好,因为我无法为我正在尝试做的事情找到一个好的参考。我的主要困惑在于拥有多个屏幕,以及如何在不同屏幕及其各自的类之间传输数据。数据是否应该只通过单个根类汇集?或者每个类都可以用来处理来自应用程序中各自屏幕(窗口)的数据吗?ObjectProperty 函数到底在做什么?我发现 kivy 网站在描述属性类时有点复杂。

任何帮助/方向将不胜感激。

这是 main.py 代码:

import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
import gspread
from oauth2client.service_account import ServiceAccountCredentials

scope = ['https://www.googleapis.com/auth/drive']
creds = ServiceAccountCredentials.from_json_keyfile_name('My_First_Project-3d753d98320e.json', scope)
client = gspread.authorize(creds)
iQue_sheet = client.open("InventoryBackend").sheet1

#root layout
class InventoryWindow(Screen):
    pass

#Layout in question
class AddPartWindow(Screen):

    #Is there anything else I need to do with these before saving into a list or dictionary?
    
    part_name = ObjectProperty(None)
    serial_number = ObjectProperty(None)
    on_hand_cnt = ObjectProperty(None)
    min_needed = ObjectProperty(None)

    #This function should save the user input into a list, np, and then append to the google sheet iQue_sheet
    #Wasn't sure if it should be a list or a dictionary.
    #I'm assuming .text is type-casting each object to a string.  Can this be used for numerical inputs?
    
    def new_part(self):
        np = [self.part_name.text, self.serial_number.text, self.on_hand_cnt.text, self.min_needed.text]
        iQue_sheet.append(np)

class OnOrderWindow(Screen):
    pass

class OrderFormWindow(Screen):
    pass

class WindowManager(ScreenManager):
    pass

class InventoryApp(App):
    def build(self):
        #These are used to enable going back and forth between screens using buttons
        sm = ScreenManager()
        sm.add_widget(InventoryWindow(name='inv_window'))
        sm.add_widget(OnOrderWindow(name='on_order_window'))
        sm.add_widget(AddPartWindow(name='add_part_window'))
        sm.add_widget(OrderFormWindow(name='order_form_window'))
        return sm

if __name__ == "__main__":
    InventoryApp().run()

这是我的 .kv 布局文件,Add Part 窗口是最后一个布局:

WindowManager:
    InventoryWindow:
    OnOrderWindow:
    OrderFormWindow:
    AddPartWindow:

<ItemLabel@Label>
    font_size: '15sp'
    halign: 'left'
    valign: 'middle'
    text_size: self.size

<ItemButton@Button>
    pos_hint: {'center_x':0.5, 'center_y':0.5}
    size_hint: 0.65, 0.2


<ItemButton2@Button>
    pos_hint: {'center_x':0.5, 'center_y':0.5}
    size_hint: 0.65, 0.2
    on_release:
        app.root.current = "order_form_window"


<InventoryWindow>:
    name: "inv_window"
        add_probe: add_probe

    FloatLayout:
        Label:
            text: 'Inventory'
            font_size: '25sp'
            size_hint: (1, 0.17)
            pos_hint: {'x': 0, 'y': 0.87}

    GridLayout:
        cols: 4

        padding:[10, 65, 10, 10]
        spacing: 5

        BoxLayout:
            orientation: 'vertical'
            ItemLabel:
                text:'iQue3: Probe/Tubing'

        BoxLayout:
            orientation: 'vertical'

            ItemButton:
                text:'Add'
                on_release:
                    root.new_part()

        BoxLayout:
            orientation: 'vertical'

            ItemButton:
                text:'Sub'

        BoxLayout:
            orientation: 'vertical'
            ItemButton2:
                text:'Order'

        BoxLayout:
            orientation: 'vertical'
            ItemLabel:
                text:'Gen2: Probe/Tubing'

        BoxLayout:
            orientation: 'vertical'
            ItemButton:
                text:'Add'

        BoxLayout:
            orientation: 'vertical'
            ItemButton:
                text:'Sub'

        BoxLayout:
            orientation: 'vertical'
            ItemButton2:
                text:'Order'

        BoxLayout:
            orientation: 'vertical'
            ItemLabel:
                text:'Beads'

        BoxLayout:
            orientation: 'vertical'
            ItemButton:
                text:'Add'

        BoxLayout:
            orientation: 'vertical'
            ItemButton:
                text:'Sub'

        BoxLayout:
            orientation: 'vertical'
            ItemButton2:
                text:'Order'

        BoxLayout:
            orientation: 'vertical'
            ItemLabel:
                text:'iQue3 Fluid Maint. Kit'

        BoxLayout:
            orientation: 'vertical'
            ItemButton:
                text:'Add'

        BoxLayout:
            orientation: 'vertical'
            ItemButton:
                text:'Sub'

        BoxLayout:
            orientation: 'vertical'
            ItemButton2:
                text:'Order'

        BoxLayout:
            orientation: 'vertical'
            ItemLabel:
                text:'Screener Fluid Maint. Kit'

        BoxLayout:
            orientation: 'vertical'
            ItemButton:
                text:'Add'

        BoxLayout:
            orientation: 'vertical'
            ItemButton:
                text:'Sub'

        BoxLayout:
            orientation: 'vertical'
            ItemButton2:
                text:'Order'

        BoxLayout:
            orientation: 'vertical'


    GridLayout:
        cols: 2
        size_hint: 1, 0.15
        padding: [10, 0, 10, 10]


        BoxLayout:

            Button:
                text: 'Add Part to Inventory'
                font_size: '18sp'
                size_hint: (1, 0.75)
                on_release:
                    app.root.current = "add_part_window"
                    root.manager.transition.direction = "right"

            Button:
                text: 'On Order'
                font_size: '18sp'
                size_hint: (1, 0.75)
                on_release:
                    app.root.current = "on_order_window"
                    root.manager.transition.direction = "left"


<OnOrderWindow>:

    name: "on_order_window"

    FloatLayout:
        Label:
            text: 'On Order'
            font_size: '25sp'
            size_hint: (1, 0.17)
            pos_hint: {'x': 0, 'y': 0.87}

        GridLayout:
            cols: 2
            size_int: 1, .15
            padding:[10, 510, 10, 10]

            Button:
                text: "Inventory"
                font_size: '18sp'
                size_hint: (1, 0.6)
                on_release:
                    app.root.current = "inv_window"
                    root.manager.transition.direction = "right"
            Button:
                text:"Add Part to Inventory"
                size_hint: (1, 0.6)
                font_size: '18sp'
                on_release:
                    app.root.current = "add_part_window"
                    root.manager.transition.direction = "left"


<OrderFormWindow>
    name: "order_form_window"

    FloatLayout:
        Label:
            text: 'Order Form'
            font_size: '25sp'
            size_hint: (1, 0.17)
            pos_hint: {'x': 0, 'y': 0.87}

        GridLayout:
            cols:2
            padding: [50, 50, 50, 120]

            ItemLabel:
                text: "Part: "

            ItemLabel:
                text: "Populate Part Here"

            ItemLabel:
                text: "Serial Number: "

            ItemLabel:
                text: "Populate SN Here"

            ItemLabel:
                text: "On Hand: "

            ItemLabel:
                text: "Populate On Hand Count Here"

            ItemLabel:
                text: "Minimum Needed: "

            ItemLabel:
                text: "Populate Min Needed Here"

            ItemLabel:
                text: "Order Quantity"

            TextInput:
                halign: 'left'
                valign: 'middle'
                input_type: 'number'
                input_filter: 'int'
                multiline:False

        GridLayout:
            cols:2
            size_hint: 1, 0.15
            padding: [10, 0, 10, 10]

            Button:
                text:"Cancel"
                on_release: app.root.current = "inv_window"

            Button:
                text:"Submit Order"
                on_release: app.root.current = "on_order_window"


#This is the add part screen layout I'm referring to
<AddPartWindow>
    name: "add_part_window"

        #These are the id's I was referring to:
        
        part_name: part_name
        serial_number: serial_number
        on_hand_cnt: on_hand_cnt
        min_needed: min_needed

    FloatLayout:
        Label:
            text: 'Add Part to Inventory'
            font_size: '25sp'
            size_hint: (1, 0.17)
            pos_hint: {'x': 0, 'y': 0.86}

        GridLayout:
            cols:2
            padding: [50, 100, 50, 120]
            spacing: 5

            ItemLabel:
                text: "Name of Part: "

            TextInput:
                id: part_name
                halign: 'left'
                valign: 'middle'
                multinline:False

            ItemLabel:
                text: "Serial Number: "

            TextInput:
                id: serial_number
                halign: 'left'
                valign: 'middle'
                multiline:False

            ItemLabel:
                text: "How Many On Hand?: "

            TextInput:
                id: on_hand_cnt
                halign: 'left'
                valign: 'middle'
                multinline:False

            ItemLabel:
                text: "Minimum Needed?: "

            TextInput:
                id: min_needed
                halign: 'left'
                valign: 'middle'
                multiline:False

        GridLayout:
            cols:2
            size_hint: 1, 0.15
            padding: [10, 0, 10, 10]

            Button:
                text:"Cancel"
                on_release:
                    app.root.current = "inv_window"
                    root.manager.transition.direction = "left"

            #Here is the button I'm referring to, I realize that putting "root." in front of new_part() is not 
            #the correct thing to put, so it's a placeholder for now:
            
            Button:
                text:"Submit"
                on_release:
                    root.new_part()
                    app.root.current = "inv_window"
                    root.manager.transition.direction = "left"

这是添加零件屏幕的屏幕截图

当我在四个字段中输入输入并单击提交按钮时,我得到的当前错误是:

File "C:\Users\edr27\kivy_venv\lib\site-packages\kivy\uix\behaviors\button.py", line 179, in on_touch_up
     self.dispatch('on_release')
   File "kivy\_event.pyx", line 705, in kivy._event.EventDispatcher.dispatch
   File "kivy\_event.pyx", line 1248, in kivy._event.EventObservers.dispatch
   File "kivy\_event.pyx", line 1132, in kivy._event.EventObservers._dispatch
   File "C:\Users\edr27\kivy_venv\lib\site-packages\kivy\lang\builder.py", line 57, in custom_callback
     exec(__kvlang__.co_value, idmap)
   File "C:\Users\edr27\PycharmProjects\pythonProject\inventory.kv", line 345, in <module>
     root.new_part()
   File "C:\Users\edr27\PycharmProjects\pythonProject\main.py", line 35, in new_part
     np = [self.part_name.text, self.serial_number.text, self.on_hand_cnt.text, self.min_needed.text]
 AttributeError: 'NoneType' object has no attribute 'text'

编辑:

以下是我如何实现 Oussama 的答案以使其正常工作,我所要做的就是更新 AddPartWindow 类:

class AddPartWindow(Screen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.np = []

    def submit(self):
        self.part_name = self.ids.part_name.text
        self.serial_number = self.ids.serial_number.text
        self.on_hand_cnt = self.ids.on_hand_cnt.text
        self.min_needed = self.ids.min_needed.text

        self.np = [self.part_name, self.serial_number, self.on_hand_cnt, self.min_needed]
        iQue_sheet.append_row(self.np)

我还进行了此更改,以便附加到工作簿的第 3 页而不是第 1 页:

iQue_sheet = client.open("InventoryBackend").get_worksheet(2)

标签: pythongoogle-sheetsgoogle-apikivyuser-input

解决方案


这个 main.py 可以随意导入你需要的东西。这是你想做的一个简单的例子

from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.app import App
Builder.load_file('the.kv')

class fscreen(widget):
    def __init__(self, **kwargs):
        supper().__init__(**kwargs)
        self.np = []        

    def submit(self):
            
        self.part_name = self.ids.Pname.text
        self.serial_number = self.ids.Snumber.text
        self.on_hand_cnt = self.ids.onhabdcnt.text
        self.min_needed = self.ids.minneeded.text

        self.np = [self.part_name, self.serial_number, self.on_hand_cnt, self.min_needed]
        iQue_sheet.append(self.np)


class secscreen(widget):
    def __init__(self, **kwargs):
        supper().__init__(**kwargs)
        pass

class thscreen(widget):
    def __init__(self, **kwargs):
        supper().__init__(**kwargs)
        pass

class theapp(App):
    def build(self):
        self.screenm = ScreenManager()
        self.screen = Screen(name = "first screen")
        screen.add_widget(self.fscreen)
        self.screenm.add_widget(screen)
if __name__ = "__main__":
    theapp.run()

这是.kv文件

<fscreen>
    TextInput:
        id: Pname
        hint_text: 'partname'

    TextInput:
        id: Snumber
        hint_text: 'serialnumber'

    TextInput:
        id: onhandcnt
        hint_text: 'on hand cnt'

    TextInput:
        id: minneeded
        hint_text: 'min nedded' 

    Button:
        text: 'Submit'
        on_press: root.submit()

推荐阅读