Search code examples
pythonpython-2.7kivykivy-language

Remove all the button from a kivy dropdown


I'm new with Python and I tried to sort out this problem, but unlikely I wasn't able to do this. Dropdown is dinamically populated via mqtt (here for instance is mosquito) and after the selection of a value I need to change Screen and when I return in the screen with the dropdown it should be empty.

Someone have some idea for doing it? Thank you in advance.

main.py

#-*-coding:utf8;-*-
#qpy:2
#qpy:kivy
#!/usr/bin/python
#@@@@@@ -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.core.window import Window
#from kivy.properties import DictProperty

import xmlrpclib
import calendar
import datetime
from urllib3.util.timeout import current_time
from datetime import date, time, timedelta
import locale
from kivy.uix.togglebutton import ToggleButton

from kivy.properties import ListProperty
from threading import Thread
import paho.mqtt.client as mqtt
import time
from time import sleep
from ConfigParser import SafeConfigParser



class CustomDropDownChip(DropDown):
    def __init__(self, **kwargs):
        super(CustomDropDownChip, self).__init__(**kwargs)
        notes = chip_list
        for note in notes:
            btn = Button(text='%s' % note, size_hint_y=None, height=int(Window.height)/10)
            btn.bind(on_release=lambda btn: self.select('CHIP: ' + btn.text))
            self.add_widget(btn)

class StartScreen(Screen):
    pass

class InsertBox(Screen):
    global chip_list
    chip_list = []
    global chip_list_empty
    chip_list_empty = []
    global connected_flag
    consommables = ListProperty([])

    def buttons_down(self):
        app = App.get_running_app()
        app.chip_value = self.ids.id_chip.text
        app.InsertSelection = 'InsertBoxConfirm'

    def on_connect(self, mqttc, obj, flags, rc):
        if rc==0:
            mqttc.connected_flag=True
            connected_flag = True
            print("connected OK")
        else:
            print("Bad connection Returned code=",rc)
    def on_disconnect(self, mqttc, obj, rc):
        pass

    def on_message(self, mqttc, obj, msg):
        response = str(msg.payload)
        if response not in chip_list:
            sleep(1)
            chip_list.append(response)
        if mqttc.connected_flag == False:
            mqttc.on_disconnect()
    def on_publish(self, mqttc, obj, mid):
        print("mid: "+str(mid))
    def on_subscribe(self, mqttc, obj, mid, granted_qos):
        print("Subscribed: "+str(mid)+" "+str(granted_qos))
    def on_log(self, mqttc, obj, level, string):
        print(string)
    def __init__(self, **kwargs):
        super(InsertBox, self).__init__(**kwargs)
    def stop_mqtt(self):
        mqttc.disconnect()
    def start_mqtt(self):
        self.read_chip()
    def read_chip(self, *args):
        global t
        t = Thread(target=self.read_).start()
    def read_(self):

        mqtt.Client.connected_flag=False#create flag in class
        connected_flag = False

        global mqttc
        mqttc = mqtt.Client(transport="websockets")
        mqttc.on_message = self.on_message
        mqttc.on_connect = self.on_connect
        mqttc.on_publish = self.on_publish
        mqttc.on_subscribe = self.on_subscribe
        mqttc.on_log = self.on_log
        mqttc.connect("test.mosquitto.org", 8080, 60)
        mqttc.subscribe("temp/random", 0)
        mqttc.loop_forever()



    def consume(self, *args):
        while self.consommables and time() < (Clock.get_time() + MAX_TIME):
            item = self.consommables.pop(0)  # i want the first one
            label = Factory.MyLabel(text=item)
            self.root.ids.id_chip.text = label #add_widget(label)

#    @classmethod
    def reset_chip_list(self):
        chip_list = chip_list_empty


class InsertBoxConfirm(Screen):
    def buttons_set(self):
        InsertBox.buttons_set()
    def buttons_reset(self):
        InsertBox.buttons_reset()

class MyScreenManager(ScreenManager):

    def SetChip(self, value):
        pass

class testdropdownApp(App):
    title = "Kivy Drop-Down List Demo"
    chip_list = chip_list_empty
    chip_value = ''

    def build(self):
        return MyScreenManager()

if __name__ == '__main__':
    testdropdownApp().run()

testdropdown.kv

#:kivy 1.10.0
#:import Factory kivy.factory.Factory

<CustomDropDownChip>:
    on_select:
        app.root.ids.InsertBox.ids.id_chip.text = '{}'.format(args[1])
        app.root.SetChip(args[1])


<StartScreen>:
    name: 'StartScreen'
    Button:
        text: 'Start'
        on_release:
            root.manager.transition.direction = 'left'
            root.manager.transition.duration = 0
            root.manager.current = 'InsertBox'


<InsertBox>:
    name: 'InsertBox'
    id: test00 
    on_enter:
        root.reset_chip_list()
        root.start_mqtt()
    on_leave:
        root.stop_mqtt()

    GridLayout:
        rows: 2
        id: test01
        GridLayout:
            rows: 1
            size_hint_y: .27
            padding: 5
            Button:
                id: id_chip
                text: 'Select chip'
#                    size_hint_y: .5
                spacing: 5
                on_release: Factory.CustomDropDownChip().open(self)

        GridLayout:
            size_hint_y: .2
            height: .15
            cols: 2
            BoxLayout:
                padding: 5
                orientation: 'horizontal'
                size: root.size
                pos: root.pos
                spacing: 5
#                height: .15
                Button:
                    text: "Back"
                    on_press:
                        root.reset_chip_list()
                        root.manager.transition.direction = 'left'
                        root.manager.transition.duration = 0
                        root.manager.current = 'StartScreen'
                Button:
                    text: 'OK'
                    on_press:
                        root.buttons_down()
                        root.manager.transition.direction = 'left'
                        root.manager.transition.duration = 0
                        root.manager.current = 'InsertBoxConfirm'

<InsertBoxConfirm>:
    name: 'InsertBoxConfirm'
    on_pre_enter:
        id_chip_label.text = app.chip_value
    GridLayout:
        rows: 2
        id: id_InsertBoxConfirm
        GridLayout:
            rows: 1
            size_hint_y: .27
            padding: 5
            Label:
                id: id_chip_label
                text: 'valore CHIP'

        GridLayout:
            size_hint_y: .2
            height: .15
            cols: 1
            BoxLayout:
                padding: 5
                orientation: 'horizontal'
                size: root.size
                pos: root.pos
                spacing: 5
                Button:
                    text: 'OK'
                    id: button_ok
                    on_press:
                        root.manager.transition.direction = 'left'
                        root.manager.transition.duration = 0
                        root.manager.current = 'InsertBox'


<MyScreenManager>:
    StartScreen:
        id: 'StartScreen'
        name: 'StartScreen'
    InsertBox:
        id: InsertBox
        name: 'InsertBox'
    InsertBoxConfirm:
        id: InsertBoxConfirm
        name: 'InsertBoxConfirm'

Solution

  • Please refer to the overview, snippets, and example for details.

    Overview

    kv file

    1. Remove import statement, #:import Factory kivy.factory.Factory
    2. Move root.reset_chip_list() from on_enter: to on_leave:
    3. Replace Factory.CustomDropDownChip().open(self) with a new callbackmethod, root.create_open_customdropdown(self)
    4. Remove root.reset_chip_list() from on_press: Back Button because when we leave the screen, it will automatically fired on_leave: event.
    5. Replace app.chip_value with root.manager.chip_value because we will be moving variable, chip_value from App class to root class.

    Snippet - kv file

                Button:
                    id: id_chip
                    text: 'Select chip'
                    spacing: 5
                    on_release:
                        root.create_open_customdropdown(self)
    

    Python Code

    1. Add import statement, from kivy.properties import BooleanProperty, ObjectProperty
    2. Remove variables (global chip_list; global chip_list_empty; global connected_flag) from class InsertBox(Screen):
    3. Replace chip_list = [] with chip_list = ListProperty([]); chip_list_empty = [] with chip_list_empty = ListProperty([]); and add connected_flag = BooleanProperty(False)
    4. Add ObjectProperty, dropdown = ObjectProperty(None) at class level of class InsertBox(Screen): so that we can use this to hook-up to the CustomDropDownChip object and remove it.
    5. Implement a new method, create_open_customdropdown(self, instance): inside class InsertBox(Screen):
    6. In methods, replace chip_list with self.chip_list; chip_list_empty with self.chip_list_empty; connected_flag with self.connected_flag
    7. Remove chip_list = chip_list_empty from class testdropdownApp():
    8. Move chip_value = '' from class testdropdownApp() to class MyScreenManager():
    9. In SetChip() method, replace pass with self.chip_value = value
    10. In CustomDropDownChip's constructor, add chip_list as arguments; remove notes = chip_list; and replace notes with chip_list

    Snippet - Python code

    class CustomDropDownChip(DropDown):
        def __init__(self, chip_list, **kwargs):
            super(CustomDropDownChip, self).__init__(**kwargs)
            for note in chip_list:
                btn = Button(text='%s' % note, size_hint_y=None, height=int(Window.height)/10)
                btn.bind(on_release=lambda btn: self.select('CHIP: ' + btn.text))
                self.add_widget(btn)
    ...
    class InsertBox(Screen):
        consommables = ListProperty([])
        chip_list = ListProperty([])
        chip_list_empty = ListProperty([])
        connected_flag = BooleanProperty(False)
        dropdown = ObjectProperty(None)
    
        def create_open_customdropdown(self, instance):
            self.dropdown = CustomDropDownChip(self.chip_list)
            self.dropdown.open(instance)
    ...
        def reset_chip_list(self):
            print("\tlen(chip_list)=", len(self.chip_list))
            self.chip_list = self.chip_list_empty
            print("\tlen(chip_list)=", len(self.chip_list))
    
            if self.dropdown is not None:
                self.remove_widget(self.dropdown)
    

    Example

    main.py

    from kivy.app import App
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.uix.dropdown import DropDown
    from kivy.uix.button import Button
    from kivy.core.window import Window
    from datetime import time
    from threading import Thread
    import paho.mqtt.client as mqtt
    import time
    from time import sleep
    from kivy.factory import Factory
    from kivy.clock import Clock
    from kivy.properties import BooleanProperty, ListProperty, ObjectProperty
    
    MAX_TIME = 1/60.
    
    
    class CustomDropDownChip(DropDown):
        def __init__(self, chip_list, **kwargs):
            super(CustomDropDownChip, self).__init__(**kwargs)
            for note in chip_list:
                btn = Button(text='%s' % note, size_hint_y=None, height=int(Window.height)/10)
                btn.bind(on_release=lambda btn: self.select('CHIP: ' + btn.text))
                self.add_widget(btn)
    
    
    class StartScreen(Screen):
        pass
    
    
    class InsertBox(Screen):
        consommables = ListProperty([])
        chip_list = ListProperty([])
        chip_list_empty = ListProperty([])
        connected_flag = BooleanProperty(False)
        dropdown = ObjectProperty(None)
    
        def create_open_customdropdown(self, instance):
            self.dropdown = CustomDropDownChip(self.chip_list)
            self.dropdown.open(instance)
    
        def buttons_down(self):
            app = App.get_running_app()
            app.InsertSelection = 'InsertBoxConfirm'
    
        def on_connect(self, mqttc, obj, flags, rc):
            if rc==0:
                mqttc.connected_flag = True
                self.connected_flag = True
                print("connected OK")
            else:
                print("Bad connection Returned code=",rc)
    
        def on_disconnect(self, mqttc, obj, rc):
            pass
    
        def on_message(self, mqttc, obj, msg):
    
            response = str(msg.payload)
            if response not in self.chip_list:
                sleep(1)
                self.chip_list.append(response)
    
            if mqttc.connected_flag == False:
                mqttc.on_disconnect()
    
        def on_publish(self, mqttc, obj, mid):
            print("mid: "+str(mid))
    
        def on_subscribe(self, mqttc, obj, mid, granted_qos):
            print("Subscribed: "+str(mid)+" "+str(granted_qos))
    
        def on_log(self, mqttc, obj, level, string):
            print(string)
    
        def __init__(self, **kwargs):
            super(InsertBox, self).__init__(**kwargs)
    
        def stop_mqtt(self):
            mqttc.disconnect()
    
        def start_mqtt(self):
            self.read_chip()
    
        def read_chip(self, *args):
            global t
            t = Thread(target=self.read_).start()
    
        def read_(self):
    
            mqtt.Client.connected_flag = False  # create flag in class
            self.connected_flag = False
    
            global mqttc
            mqttc = mqtt.Client(transport="websockets")
            mqttc.on_message = self.on_message
            mqttc.on_connect = self.on_connect
            mqttc.on_publish = self.on_publish
            mqttc.on_subscribe = self.on_subscribe
            mqttc.on_log = self.on_log
            mqttc.connect("test.mosquitto.org", 8080, 60)
            mqttc.subscribe("temp/random", 0)
            mqttc.loop_forever()
    
        def consume(self, *args):
            while self.consommables and time() < (Clock.get_time() + MAX_TIME):
                item = self.consommables.pop(0)  # i want the first one
                label = Factory.MyLabel(text=item)
                self.root.ids.id_chip.text = label #add_widget(label)
    
    #    @classmethod
        def reset_chip_list(self):
            print("\tlen(chip_list)=", len(self.chip_list))
            self.chip_list = self.chip_list_empty
            print("\tlen(chip_list)=", len(self.chip_list))
    
            if self.dropdown is not None:
                self.remove_widget(self.dropdown)
    
    
    class InsertBoxConfirm(Screen):
        def buttons_set(self):
            InsertBox.buttons_set()
    
        def buttons_reset(self):
            InsertBox.buttons_reset()
    
    
    class MyScreenManager(ScreenManager):
        chip_value = ''
    
        def SetChip(self, value):
            self.chip_value = value
    
    
    class testdropdownApp(App):
        title = "Kivy Drop-Down List Demo"
    
        def build(self):
            return MyScreenManager()
    
        def on_stop(self):
            self.root.ids.InsertBox.stop_mqtt()
    
    
    if __name__ == '__main__':
        testdropdownApp().run()
    

    testdropdown.kv

    #:kivy 1.11.0
    
    <CustomDropDownChip>:
        on_select:
            app.root.ids.InsertBox.ids.id_chip.text = '{}'.format(args[1])
            app.root.SetChip(args[1])
    
    
    <StartScreen>:
        name: 'StartScreen'
        Button:
            text: 'Start'
            on_release:
                root.manager.transition.direction = 'left'
                root.manager.transition.duration = 0
                root.manager.current = 'InsertBox'
    
    
    <InsertBox>:
        name: 'InsertBox'
        id: test00
        on_enter:
            root.start_mqtt()
        on_leave:
            root.stop_mqtt()
            root.reset_chip_list()
    
        GridLayout:
            rows: 2
            id: test01
            GridLayout:
                rows: 1
                size_hint_y: .27
                padding: 5
                Button:
                    id: id_chip
                    text: 'Select chip'
    #                    size_hint_y: .5
                    spacing: 5
                    on_release:
                        root.create_open_customdropdown(self)
    
            GridLayout:
                size_hint_y: .2
                height: .15
                cols: 2
                BoxLayout:
                    padding: 5
                    orientation: 'horizontal'
                    size: root.size
                    pos: root.pos
                    spacing: 5
    #                height: .15
                    Button:
                        text: "Back"
                        on_press:
                            root.manager.transition.direction = 'left'
                            root.manager.transition.duration = 0
                            root.manager.current = 'StartScreen'
                    Button:
                        text: 'OK'
                        on_press:
                            root.buttons_down()
                            root.manager.transition.direction = 'left'
                            root.manager.transition.duration = 0
                            root.manager.current = 'InsertBoxConfirm'
    
    <InsertBoxConfirm>:
        name: 'InsertBoxConfirm'
        on_pre_enter:
            id_chip_label.text = root.manager.chip_value
    
        GridLayout:
            rows: 2
            id: id_InsertBoxConfirm
            GridLayout:
                rows: 1
                size_hint_y: .27
                padding: 5
                Label:
                    id: id_chip_label
                    text: 'valore CHIP'
    
            GridLayout:
                size_hint_y: .2
                height: .15
                cols: 1
                BoxLayout:
                    padding: 5
                    orientation: 'horizontal'
                    size: root.size
                    pos: root.pos
                    spacing: 5
                    Button:
                        text: 'OK'
                        id: button_ok
                        on_press:
                            root.manager.transition.direction = 'left'
                            root.manager.transition.duration = 0
                            root.manager.current = 'InsertBox'
    
    
    <MyScreenManager>:
        StartScreen:
            id: 'StartScreen'
            name: 'StartScreen'
        InsertBox:
            id: InsertBox
            name: 'InsertBox'
        InsertBoxConfirm:
            id: InsertBoxConfirm
            name: 'InsertBoxConfirm'
    

    Output

    Img01 Img02 Img03 Img04