Kivy – popup A shows over popup B
PROBLEM
I have a loop that in every passage displays a popup. I will call it the popup_A. In the loop there is a condition that when is met it triggers together another popup and a method in a thread. This second popup I call popup_B. The problem is that that the popup_B it does show but right after that the popup_A shows over the popup_B, covering it fully. To picture better the get the idea of the flow:
def myFun: if condition1: method1 if condition2: method2 if condition3: show popup_B thread method3 thread popup_A def popup_A: do something display message_A call myFun def popup_B: display message_B
CODE The method involved in the looping:
def goForward(self): if self.header == "someTask": if "tasks" in self.data: # check if the request has "tasks" in the body if self.counter < len(self.data["tasks"]): # check the counter self.code = self.data["tasks"][self.counter].get("code") action = self.data["tasks"][self.counter].get("actionDescription") if "myCondition" in str(action): #set the popup structure self.popup_B = ActivityBox(self) self.popup_B.open() # run the method in a thread t1 = threading.Thread(target = timeConsumingMethod) t1.start() # dismiss the popup ActivityBox self.popup_B.dismiss() # call popup_A in thread t3 = threading.Thread(target = self.popup_A) t3.start() def do(self): self.counter = self.counter + 1 self.popup.dismiss() self.goForward() def cancel(self): self.counter = self.counter + 1 self.popup.dismiss() self.goForward() def popup_A(self): self.popup = MessageBox(self) self.popup.open()
The ActivityBox and MessageBox popups structure in the Builder.load_string()
:
<MessageBox>: size_hint: 1, .7 auto_dismiss: False title: "MessageBoxTitle" title_align: "center" title_size: 30 BoxLayout: orientation: "vertical" Label: font_size: '30sp' text: "MessageBoxLabel" BoxLayout: orientation: "horizontal" spacing: 10 size_hint: 1, .5 Button: font_size: 50 background_color: 0,204,0,1 text: "CONFIRM" on_press: self.disabled = True self.background_color = 0,255,0,1 app.do() root.dismiss() Button: font_size: 50 background_color: 204,0,0,1 text: "CANCEL" on_press: self.background_color = 255,0,0,1 app.cancel() root.dismiss() <ActivityBox>: size_hint: 1, .7 auto_dismiss: False title: "ActivityBoxTitle" title_align: "center" title_size: 30 BoxLayout: orientation: "vertical" Label: font_size: '30sp' text: "ActivityBoxLabel" BoxLayout: orientation: "horizontal" spacing: 10 size_hint: 1, .5
EXPLANATION OF THE CODE The components of the main loop are goForward
and the popup_A. With every passage of the loop the popup_A appears, called in a thread. Then the popup_A calls back goForward
. If the condition "work" in goForward
is met, the "work in progress" popup_B shows up. The popup_B runs together with a method in a thread otherwise Kivy does not show the popup (locking GUI).
RESULTS
So far I have tried with:
- Run the popup_B in a thread
t1 = threading.Thread(target = self.popup.open)
: the popup_A covers the popup_B. - Using the thread
.join()
: the popup_A appears but the popup_B does not, .join() ignores it. - Run the popup_B and the
timeConsumingMethod
together in a thread: the popup_A appears but the popup_B does not. - Run the
timeConsumingMethod
as a process: the popup_A appears but the popup_B does not, the program hangs. - Using
mutex = threading.Lock()
to lock the thread with the popup_B: the popup_A appears over the popup_B. Also the GUI gets garbled. - Run the popup_A not in a thread and run popup_B and the
timeConsumingMethod
together in a thread: the popup_A appears over the popup_B.
QUESTION
The popup_A can show up only after the method in the thread has finished and the popup_B has been dismissed. How can I prevent the popup_A to cover the popup_B?
I went over the posts below but I did not find a solution.
— UPDATE 20200715 ————————————————
In the code I renamed the popup "work in progress" in popup_B and the popup2 in popup_A for a better understanding.
— UPDATE 20200716 ————————————————-
I modified the code using Clock.schedule_once
for step2
(thread for popup_A
and step3
(thread for the timeConsumingMethod
and popup_B
). The popup_B
goes up but the popup_A
covers it untill the last popup_A
is dismissed with the button. To wait for the timeConsumingMethod
to finish without the popup_A
to fire up, I use a while loop
. The code is below:
def goForward(self): if self.header == "someTask": if "tasks" in self.data: # check if the request has "tasks" in the body if self.counter < len(self.data["tasks"]): # check the counter self.code = self.data["tasks"][self.counter].get("code") action = self.data["tasks"][self.counter].get("actionDescription") if "myCondition" in str(action): self.returnStatus = 1 # set status #the two rows below give the same result #self.popup_B = ActivityBox(self) #self.popup_B.open() Clock.schedule_once(self.step3) # run the method in a thread t1 = threading.Thread(target = self.step1) t1.start() while self.returnStatus != 0: time.sleep(1) Cloch.schedule_once(self.step2) def step1(self): ts1 = threading.Thread(target = self.timeConsumingMethod) ts1.start() ts1.join() self.returnStatus = 0 # change status when over return(self.returnStatus) def step2(self, *args): ts2 = threading.Thread(target = self.popup_A) ts2.start() def step3(self, *args): #set the popup structure self.popup = ActivityBox(self) self.popup.open() def popup_A(self): self.popup = MessageBox(self) t3 = threading.Thread(target = self.popup.open) t3.start() def do(self): self.counter = self.counter + 1 self.popup.dismiss() self.goForward() def cancel(self): self.counter = self.counter + 1 self.popup.dismiss() exit()
A way to get the popup_A to not popup until the time consuming thread is finished is to call popup_A at the end of the time consuming thread. Try separating the goForward()
method into two parts. The first part remains almost unchanged:
def goForward(self): if self.header == "someTask": if "tasks" in self.data: # check if the request has "tasks" in the body if self.counter < len(self.data["tasks"]): # check the counter self.code = self.data["tasks"][self.counter].get("code") action = self.data["tasks"][self.counter].get("actionDescription") if "myCondition" in str(action): #set the popup structure self.popup_B = ActivityBox(self) self.popup_B.open() # run method in a thread t1 = threading.Thread(target = self.timeConsumingMethod) t1.start()
I have moved the timeConsumingMethod()
into the class, so it needs a call as self.timeConsumingMethod
in the Thread
.
Then place the rest of the old goForward()
method in a separate method (I call it step2()
):
def step2(self, *args): # dismiss the popup ActivityBox self.popup_B.dismiss() # call popup_A in thread t3 = threading.Thread(target=self.popup_A) t3.start()
Then in the timeConsumingMethod()
, call step2()
when it finishes:
def timeConsumingMethod(self): time.sleep(5) Clock.schedule_once(self.step2)