Search code examples
pythonkivypyserial

How can I open multiple popups in succession?


I'm currently working on an application written in Kivy in python. I have 2 multiprocessing.Processes running:

  • One is a process for the RFID-reader, I open a popup that lets me choose a screen to go to.
  • The second process is for Serial communication.

I use queues to communicate with my main thread, and that works great. The problem I'm facing at the moment is I need to introduce a learning process to my Kivy program. I have 4 screens which I call

  • mainscreen
  • adminscreen
  • managementscreen
  • initialscreen <- This screen runs only once, once it's set, the screen won't be accessable anymore.

This is the function that gets called when I push the button inside the initialscreen:

def startLearningProcess(self, lockeramount):
        self.lockeramount = int(lockeramount)
        with Database(self.databasepath) as db:
            db.truncate_table('LOCKERS')
            # resetting primary key sequence to make the ids start from 1
            db.reset_primary_key_sequence('LOCKERS')
        for locker in range(int(lockeramount)):
            # use a with statement to automatically close the db after the operations
            with Database(self.databasepath) as db:
                # inserting the given amount lockers to the database as rows. 
                db.insertLockerRow(locker+1,'Pieter')

if I add the following to the function, 5 popups get opened all at once:

while self.lockeramount != 0:
    popup = Popup(title='Test popup', auto_dismiss=True, content=Label(text='Hello world'), size_hint=(None, None), size=(400, 400))
    popup.open()
    self.lockeramount -= 1

When I input the number 5 into my interface, I want to have 5 popups to open up for me one by one. How can I make it so when I push a button I open up 1 popup, instead of all 5 at once? I apologize for my grammar, english is not my first language.

EDIT: while John's answer worked perfectly, I was looking for another solution that did not use threading. I solved it by doing the following:

In my class InitialScreen(Screen): I added 2 variables, a bool that starts out with False (booleanUp) and a int variable that starts at 0 (lockeramount). When I enter my def startLearningProcess I set the lockeramount variable to the number I input into my screen. I added an interval to the startLearningProcess function: Clock.schedule_interval(lambda dt: self.schedule_popups(), 1). I then added the following functions:

def close_popup(self, instance):
    self.booleanUp = False

def schedule_popups(self):
    if self.lockeramount > 0 and not self.booleanUp:
        print(f'opening up popup {self.lockeramount}')
        popup = Popup(title='MyPopup', content=Label(text='Abba ' + str(self.lockeramount)), size_hint=(0.5, 0.5))
        popup.bind(on_dismiss=self.close_popup)
        self.lockeramount -= 1
        self.booleanUp = True
        popup.open()
    else:
        print('not opening another popup')

When I open a new popup, I set the boolean to true, so that with the next interval it won't open another interval. I made an on_dismiss event that resets the variable back to False and bound it to my popup.


Solution

  • You can use a Queue to make the Popups wait. Define a custom Popup that accepts a Queue in its __init__() method, and sends something (could even be None) to the Queue when it is dismissed. And your loop can use the Queue to wait for the Popups to be dismissed.

    Here is a custom Popup that uses a Queue:

    class MyPopup(Popup):
        queue = ObjectProperty(None)
    
        def dismiss(self, *_args, **kwargs):
            super(MyPopup, self).dismiss(*_args, **kwargs)
            if self.queue:
                self.queue.put(None)
    

    For this to work, you must run it in another thread. Otherwise, waiting for the Queue on the main thread will never end, because holding the main thread will prevent the Popup from dismissing. Here is some code that shows 5 Popups in succession, one at a time:

    def doit(self):
        threading.Thread(target=self.popup_thread).start()
    
    def popup_thread(self):
        self.queue = Queue()
        for i in range(5):
            Clock.schedule_once(self.show_popup)
            self.queue.get()
    
    def show_popup(self, dt):
        popup = MyPopup(title='MyPopup', content=Label(text='Abba ' + str(dt)), size_hint=(0.5, 0.5), queue=self.queue)
        popup.open()
    

    To start the Popups, just call the doit() method, probably as an action associated with a Button.