Interaction and Deletion among widgets on Python + KivyMD and Code Optimization

北城余情 提交于 2021-01-29 14:58:56

问题


I am currently building an app that serves as a travel expenses manager. I have a system in which the user writes the desired amount of the expense on an MDTextField, such expense will be recorded in an MDList and the total amount of requested expenses will be added to an MDTextField. For clarity I will post a screen shot:

All this system works correctly so far. However, there can be mistakes made by the user. I want the user to be able to delete an item from the MDList when the trash-can icon is pressed. At the same time, the amount of the deleted expenditure should be substracted from the total amount requested. (i.e. If the user deletes the element of Alimentación which had $1,000.00, the total amount requested after deletion should be $2000.0).

In my code I've been able to bind the on_pressed event of the icon to a remove_item function. Which succesfully does the desired substraction, and afterwards will show a success toast. However this is done without actually deleting the item from the MDList. As shown on the second screenshot, the total expenses amount is 0, nevertheless the MDList item has not been deleted (remove_widget function inside my remove_item function is not doing the desired action).

CODE FOR MINIMAL REPRODUCIBLE EXAMPLE:

Python code:

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 File:

<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

Lastly, I would really appreciate if you could give me suggestions for code optimization on my Python file. I am repeating basically the same code in my Classes, only referring to different widgets. Can I make a list of the widgets I want to interact with and make a loop, getting the same result? I'm fairly new to Python so I guess I need to learn better ways to code.

Thanks lot in advance.


回答1:


The problem is that your remove_item() is trying to remove the wrong object. The instance passed to remove_item() is the icon, not the ViaticosIconList. In order to get the ViaticosIconList passed to the remove_item() you can change your bind call to:

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

The partial essentially creates a new function that will have add_item as an argument. See the documentation.

Then, you must adjust your remove_item() method definition to handle the additional argument:

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()

The icon argument is the argument that used to be instance and the new argument is the instance (the add_item).

You can simplify your classes by creating a base class for your expense types that contains all the common code, then each different expense type can just extend that base class.



来源:https://stackoverflow.com/questions/65583656/interaction-and-deletion-among-widgets-on-python-kivymd-and-code-optimization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!