Python + KivyMD 和代码优化上的小部件之间的交互和删除

Posted

技术标签:

【中文标题】Python + KivyMD 和代码优化上的小部件之间的交互和删除【英文标题】:Interaction and Deletion among widgets on Python + KivyMD and Code Optimization 【发布时间】:2021-04-11 11:37:33 【问题描述】:

我目前正在构建一个用作差旅费用管理器的应用。我有一个系统,用户在 MDTextField 上写入所需的费用金额,此类费用将记录在 MDList 中,并且请求的费用总额将添加到 MDTextField 中。为了清楚起见,我将发布一个屏幕截图:

到目前为止,所有这个系统都可以正常工作。但是,用户可能会犯错误。我希望用户能够在按下垃圾桶图标时从 MDList 中删除一个项目。同时,应从申请的总金额中减去已删除的支出金额。 (即,如果用户删除了 Alimentación 中价值 1,000.00 美元的元素,则删除后请求的总金额应为 2000.0 美元。

在我的代码中,我已经能够将图标的 on_pressed 事件绑定到 remove_item 函数。哪个成功地进行了所需的减法,然后将显示成功 toast。然而,这是在没有实际从 MDList 中删除项目的情况下完成的。如第二个屏幕截图所示,总费用金额为 0,但 MDList 项尚未删除(我的 remove_item 函数中的 remove_widget 函数没有执行所需的操作)。

最小可重复示例的代码:

Python 代码:

from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.app import MDApp
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelOneLine
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.list import TwoLineIconListItem, IconLeftWidget
from kivymd.toast import toast


class ViaticosIconList(TwoLineIconListItem):
    pass


class MyContentAliment(BoxLayout):
    def apply_currency_format(self):
        # if len <= 3
        if len(self.ids.monto_aliment_viaje.text) <= 3 and self.ids.monto_aliment_viaje.text.isnumeric():
            self.ids.monto_aliment_viaje.text = "$" + self.ids.monto_aliment_viaje.text + '.00'
        # n,nnn
        elif len(self.ids.monto_aliment_viaje.text) == 4 and self.ids.monto_aliment_viaje.text.isnumeric():
            self.ids.monto_aliment_viaje.text = "$" + self.ids.monto_aliment_viaje.text[0] + "," + \
                                            self.ids.monto_aliment_viaje.text[1:] + '.00'
        # nn,nnn
        elif len(self.ids.monto_aliment_viaje.text) == 5 and self.ids.monto_aliment_viaje.text.isnumeric():
            self.ids.monto_aliment_viaje.text = "$" + self.ids.monto_aliment_viaje.text[:2] + "," + \
                                            self.ids.monto_aliment_viaje.text[2:] + '.00'

    def limit_currency(self):
        if len(self.ids.monto_aliment_viaje.text) > 5 and self.ids.monto_aliment_viaje.text.startswith('$') == False:
            self.ids.monto_aliment_viaje.text = self.ids.monto_aliment_viaje.text[:-1]

    def sumar_gasto(self):
        if self.ids.monto_aliment_viaje.text == "":
            pass
        elif self.ids.monto_aliment_viaje.text.startswith('$'):
            pass
        else:
            travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
            monto_total = float(travel_manager.ids.suma_solic_viaje.text[2:])
            monto_total += float(self.ids.monto_aliment_viaje.text)
            travel_manager.ids.suma_solic_viaje.text = "$ " + str(monto_total)
            self.apply_currency_format()

            screen_list_view = MDApp.get_running_app().root.get_screen('travelManager').ids.viaticos_list
            icon = IconLeftWidget(icon='delete')
            add_item = ViaticosIconList(text=f"Alimentación Personal", secondary_text=self.ids.monto_aliment_viaje.text)
            add_item.add_widget(icon)
            icon.bind(on_press=self.remove_item)
            screen_list_view.add_widget(add_item)

    def remove_item(self, instance):
        # Remove item once the trash icon is clicked (pendiente)
        travel = MDApp.get_running_app().root.get_screen('travelManager')
        travel.ids.viaticos_list.remove_widget(instance)
        self.substract_expense()
        self.show_toast()

    def substract_expense(self):
        travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
        monto_total = float(travel_manager.ids.suma_solic_viaje.text[2:])
        substract_amount = self.ids.monto_aliment_viaje.text[1:-3].replace(',', '')
        monto_total -= float(substract_amount)
        travel_manager.ids.suma_solic_viaje.text = "$ " + str(monto_total)
        self.ids.monto_aliment_viaje.text = ''

    def show_toast(self):
        toast("Monto de Alimentación Personal eliminado de la solicitud")


class MyContentCasetas(BoxLayout):
    def apply_currency_format(self):
        # if len <= 3
        if len(self.ids.monto_casetas_viaje.text) <= 3 and self.ids.monto_casetas_viaje.text.isnumeric():
            self.ids.monto_casetas_viaje.text = "$" + self.ids.monto_casetas_viaje.text + '.00'
        # n,nnn
        elif len(self.ids.monto_casetas_viaje.text) == 4 and self.ids.monto_casetas_viaje.text.isnumeric():
            self.ids.monto_casetas_viaje.text = "$" + self.ids.monto_casetas_viaje.text[0] + "," + \
                                            self.ids.monto_casetas_viaje.text[1:] + '.00'
        # nn,nnn
        elif len(self.ids.monto_casetas_viaje.text) == 5 and self.ids.monto_casetas_viaje.text.isnumeric():
            self.ids.monto_casetas_viaje.text = "$" + self.ids.monto_casetas_viaje.text[:2] + "," + \
                                            self.ids.monto_casetas_viaje.text[2:] + '.00'

    def limit_currency(self):
        if len(self.ids.monto_casetas_viaje.text) > 5 and self.ids.monto_casetas_viaje.text.startswith('$') == False:
            self.ids.monto_casetas_viaje.text = self.ids.monto_casetas_viaje.text[:-1]

    def sumar_gasto(self):
        if self.ids.monto_casetas_viaje.text == "":
            pass
        elif self.ids.monto_casetas_viaje.text.startswith('$'):
            pass
        else:
            travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
            monto_total = float(travel_manager.ids.suma_solic_viaje.text[2:])
            monto_total += float(self.ids.monto_casetas_viaje.text)
            travel_manager.ids.suma_solic_viaje.text = "$ " + str(monto_total)
            self.apply_currency_format()

            screen_list_view = MDApp.get_running_app().root.get_screen('travelManager').ids.viaticos_list
            icon = IconLeftWidget(icon='delete')
            add_item = ViaticosIconList(text=f"Casetas", secondary_text=self.ids.monto_casetas_viaje.text)
            add_item.add_widget(icon)
            icon.bind(on_press=self.remove_item)
            screen_list_view.add_widget(add_item)

    def remove_item(self, instance):
        # Remove item once the trash icon is clicked (pendiente)
        travel = MDApp.get_running_app().root.get_screen('travelManager')
        travel.ids.viaticos_list.remove_widget(instance)
        self.substract_expense()
        self.show_toast()

    def substract_expense(self):
        travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
        monto_total = float(travel_manager.ids.suma_solic_viaje.text[2:])
        substract_amount = self.ids.monto_casetas_viaje.text[1:-3].replace(',', '')
        monto_total -= float(substract_amount)
        travel_manager.ids.suma_solic_viaje.text = "$ " + str(monto_total)
        self.ids.monto_casetas_viaje.text = ''

    def show_toast(self):
        toast("Monto de Casetas eliminado de la solicitud")


class MyContentGasolina(BoxLayout):
    def apply_currency_format(self):
        # if len <= 3
        if len(self.ids.monto_gas_viaje.text) <= 3 and self.ids.monto_gas_viaje.text.isnumeric():
            self.ids.monto_gas_viaje.text = "$" + self.ids.monto_gas_viaje.text + '.00'
        # n,nnn
        elif len(self.ids.monto_gas_viaje.text) == 4 and self.ids.monto_gas_viaje.text.isnumeric():
            self.ids.monto_gas_viaje.text = "$" + self.ids.monto_gas_viaje.text[0] + "," + \
                                        self.ids.monto_gas_viaje.text[1:] + '.00'
        # nn,nnn
        elif len(self.ids.monto_gas_viaje.text) == 5 and self.ids.monto_gas_viaje.text.isnumeric():
            self.ids.monto_gas_viaje.text = "$" + self.ids.monto_gas_viaje.text[:2] + "," + \
                                        self.ids.monto_gas_viaje.text[2:] + '.00'

    def limit_currency(self):
        if len(self.ids.monto_gas_viaje.text) > 5 and self.ids.monto_gas_viaje.text.startswith('$') == False:
            self.ids.monto_gas_viaje.text = self.ids.monto_gas_viaje.text[:-1]

    def sumar_gasto(self):
        if self.ids.monto_gas_viaje.text == "":
            pass
        elif self.ids.monto_gas_viaje.text.startswith('$'):
            pass
        else:
            travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
            monto_total = float(travel_manager.ids.suma_solic_viaje.text[2:])
            monto_total += float(self.ids.monto_gas_viaje.text)
            travel_manager.ids.suma_solic_viaje.text = "$ " + str(monto_total)
            self.apply_currency_format()

            screen_list_view = MDApp.get_running_app().root.get_screen('travelManager').ids.viaticos_list
            icon = IconLeftWidget(icon='delete')
            add_item = ViaticosIconList(text=f"Gasolina", secondary_text=self.ids.monto_gas_viaje.text)
            add_item.add_widget(icon)
            icon.bind(on_press=self.remove_item)
            screen_list_view.add_widget(add_item)

    def remove_item(self, instance):
        # Remove item once the trash icon is clicked (pendiente)
        travel = MDApp.get_running_app().root.get_screen('travelManager')
        travel.ids.viaticos_list.remove_widget(instance)
        self.substract_expense()
        self.show_toast()

    def substract_expense(self):
        travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
        monto_total = float(travel_manager.ids.suma_solic_viaje.text[2:])
        substract_amount = self.ids.monto_gas_viaje.text[1:-3].replace(',', '')
        monto_total -= float(substract_amount)
        travel_manager.ids.suma_solic_viaje.text = "$ " + str(monto_total)
        self.ids.monto_gas_viaje.text = ''

    def show_toast(self):
        toast("Monto de Gasolina eliminado de la solicitud")


class LoginWindow(Screen):
    pass


class TravelManagerWindow(Screen):
    panel_container = ObjectProperty(None)

    # EXPANSION PANEL PARA SOLICITAR GV
    def set_expansion_panel(self):
        self.ids.panel_container.clear_widgets()
        # FOOD PANEL
        self.ids.panel_container.add_widget(MDExpansionPanel(icon="food", content=MyContentAliment(),
                                                         panel_cls=MDExpansionPanelOneLine(text="Alimentacion")))
        # CASETAS PANEL
        self.ids.panel_container.add_widget(MDExpansionPanel(icon="food", content=MyContentCasetas(),
                                                         panel_cls=MDExpansionPanelOneLine(text="Casetas")))
        # GAS PANEL
        self.ids.panel_container.add_widget(MDExpansionPanel(icon="food", content=MyContentGasolina(),
                                                         panel_cls=MDExpansionPanelOneLine(text="Gasolina")))


### WINDOW MANAGER ################################

class WindowManager(ScreenManager):
    pass


class ReprodExample(MDApp):
    def build(self):
        self.theme_cls.primary_palette = "Teal"
        return WindowManager()


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

KV 文件:

<WindowManager>:
    LoginWindow:
    TravelManagerWindow:

<LoginWindow>:
    name: 'login'
    MDRaisedButton:
        text: 'Enter'
        pos_hint: 'center_x': 0.5, 'center_y': 0.5
        size_hint: None, None
        on_release:
            root.manager.transition.direction = 'up'
            root.manager.current = 'travelManager'

<TravelManagerWindow>:
    name:'travelManager'
    on_pre_enter: root.set_expansion_panel()

    MDRaisedButton:
        text: 'Back'
        pos_hint: 'center_x': 0.5, 'center_y': 0.85
        size_hint: None, None
        on_release:
            root.manager.transition.direction = 'down'
            root.manager.current = 'login'

    BoxLayout:
        orientation: 'vertical'
        size_hint:1,0.85
        pos_hint: "center_x": 0.5, "center_y":0.37
        adaptive_height:True
        height: self.minimum_height

        ScrollView:
            adaptive_height:True

            GridLayout:
                size_hint_y: None
                cols: 1
                row_default_height: root.height*0.10
                height: self.minimum_height

                BoxLayout:
                    adaptive_height: True
                    orientation: 'horizontal'

                    GridLayout:
                        id: panel_container
                        size_hint_x: 0.6
                        cols: 1
                        adaptive_height: True

                    BoxLayout:
                        size_hint_x: 0.05
                    MDCard:
                        id: resumen_solicitud
                        size_hint: None, None
                        size: "250dp", "350dp"
                        pos_hint: "top": 0.9, "center_x": .5
                        elevation: 0.1

                        BoxLayout:
                            orientation: 'vertical'
                            canvas.before:
                                Color:
                                    rgba: 0.8, 0.8, 0.8, 1
                                Rectangle:
                                    pos: self.pos
                                    size: self.size
                            MDLabel:
                                text: 'Monto Total Solicitado'
                                font_style: 'Button'
                                halign: 'center'
                                font_size: (root.width**2 + root.height**2) / 15.5**4
                                size_hint_y: 0.2
                            MDSeparator:
                                height: "1dp"
                            MDTextField:
                                id: suma_solic_viaje
                                text: "$ 0.00"
                                bold: True
                                line_color_normal: app.theme_cls.primary_color
                                halign: "center"
                                size_hint_x: 0.8
                                pos_hint: 'center_x': 0.5, 'center_y': 0.5
                            MDSeparator:
                                height: "1dp"

                            MDBoxLayout:
                                padding: '5dp', '10dp', '5dp', '10dp'
                                MDList:
                                    pos_hint: 'x': 0, 'top': 1
                                    id: viaticos_list
                                    padding: 0


<MyContentAliment>:
    adaptive_height: True
    MDBoxLayout:
        orientation:'horizontal'
        adaptive_height:True
        size_hint_x:self.width
        pos_hint: "center_x":0.5, "center_y":0.5
        spacing: dp(10)
        padding_horizontal: dp(10)
        MDLabel:
            text: 'Monto:'
            multiline: 'True'
            halign: 'center'
            pos_hint: "x":0, "top":0.5
            size_hint_x: 0.15
            font_style: 'Button'
            font_size: 19

        MDTextField:
            id: monto_aliment_viaje
            hint_text: 'Monto a solicitar'
            pos_hint: "x":0, "top":0.5
            halign: 'left'
            size_hint_x: 0.3
            helper_text: 'Ingresar el monto a solicitar'
            helper_text_mode: 'on_focus'
            write_tab: False
            required: True
            on_text: root.limit_currency()

        MDRaisedButton:
            id: boton_aliment_viaje
            pos_hint: "x":0, "top":0.5
            text:'Ingresar Gasto'
            on_release: root.sumar_gasto()

### CASETAS
<MyContentCasetas>:
    adaptive_height: True
    MDBoxLayout:
        orientation:'horizontal'
        adaptive_height:True
        size_hint_x:self.width
        pos_hint: "center_x":0.5, "center_y":0.5
        spacing: dp(10)
        padding_horizontal: dp(10)
        MDLabel:
            text: 'Monto:'
            multiline: 'True'
            halign: 'center'
            pos_hint: "x":0, "top":0.5
            size_hint_x: 0.15
            font_style: 'Button'
            font_size: 19

        MDTextField:
            id: monto_casetas_viaje
            hint_text: 'Monto a solicitar'
            pos_hint: "x":0, "top":0.5
            halign: 'left'
            size_hint_x: 0.3
            helper_text: 'Ingresar el monto a solicitar'
            helper_text_mode: 'on_focus'
            write_tab: False
            required: True
            on_text: root.limit_currency()

        MDRaisedButton:
            id: boton_casetas_viaje
            pos_hint: "x":0, "top":0.5
            text:'Ingresar Gasto'
            on_release: root.sumar_gasto()

        BoxLayout:
            size_hint_x: 0.05

### GASOLINA
<MyContentGasolina>:
    adaptive_height: True
    MDBoxLayout:
        orientation:'horizontal'
        adaptive_height:True
        size_hint_x:self.width
        pos_hint: "center_x":0.5, "center_y":0.5
        spacing: dp(10)
        padding_horizontal: dp(10)
        MDLabel:
            text: 'Monto:'
            multiline: 'True'
            halign: 'center'
            pos_hint: "x":0, "top":0.5
            size_hint_x: 0.15
            font_style: 'Button'
            font_size: 19

        MDTextField:
            id: monto_gas_viaje
            hint_text: 'Monto a solicitar'
            pos_hint: "x":0, "top":0.5
            halign: 'left'
            size_hint_x: 0.3
            helper_text: 'Ingresar el monto a solicitar'
            helper_text_mode: 'on_focus'
            write_tab: False
            required: True
            on_text: root.limit_currency()

        MDRaisedButton:
            id: boton_gas_viaje
            pos_hint: "x":0, "top":0.5
            text:'Ingresar Gasto'
            on_release: root.sumar_gasto()

        BoxLayout:
            size_hint_x: 0.05

最后,如果您能给我的 Python 文件代码优化建议,我将不胜感激。我在我的类中重复基本相同的代码,只是指不同的小部件。我可以列出我想与之交互的小部件并进行循环,得到相同的结果吗?我对 Python 还很陌生,所以我想我需要学习更好的编码方法。

非常感谢。

【问题讨论】:

【参考方案1】:

问题是您的remove_item() 试图删除错误的对象。传递给remove_item()instance 是图标,而不是ViaticosIconList。为了将ViaticosIconList 传递给remove_item(),您可以将bind 调用更改为:

icon.bind(on_press=partial(self.remove_item, add_item))

partial 本质上创建了一个以add_item 作为参数的新函数。请参阅documentation。

然后,您必须调整您的 remove_item() 方法定义以处理附加参数:

def remove_item(self, instance, icon):
    # Remove item once the trash icon is clicked (pendiente)
    travel = MDApp.get_running_app().root.get_screen('travelManager')
    travel.ids.viaticos_list.remove_widget(instance)
    self.substract_expense()
    self.show_toast()

icon 参数是以前的 instance 参数,而新参数是 instanceadd_item)。

您可以通过为包含所有公共代码的费用类型创建一个基类来简化您的类,然后每个不同的费用类型都可以扩展该基类。

【讨论】:

一如既往,你对约翰很有帮助。非常感谢,祝您有美好的一天。 您好@John Anderson,很抱歉再次打扰您。几天前我发布了一个新问题,但没有任何回应。我尝试了几种解决方案,但无法成功运行我的代码。如果您有时间,我将非常感谢您的帮助。再次对由此可能造成的任何不便深表歉意。提前感谢lof。网址如下:***.com/questions/65603075/…

以上是关于Python + KivyMD 和代码优化上的小部件之间的交互和删除的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 上的 kivymd 中使用 python 3.8 exchangelib 时出错

Python透明KivyMD导航抽屉

KivyMD RecycleView 的 FPS 和延迟较低

Mac 上的 KivyMD:运行 kitchen_sink 会在 kivytoast.py 中引发错误

如何在 KivyMD (Python) 中结合导航抽屉和多个屏幕?

无法连接到 android KivyMD 上的 mysql 数据库