Kivy: How to create a 'blocking' popup/modalview? - python

I did find a question on this on stackoverflow, here, but I find it just doesn't answer the question, as for me, neither the Popup nor the ModalView actually 'blocks'. What I mean is, execution is moving through a function, like:
def create_file(self):
modal = ModalView(title="Just a moment", size_hint=(0.5, 0.5))
btn_ok = Button(text="Save & continue", on_press=self.save_file)
btn_no = Button(text="Discard changes", on_press=modal.dismiss)
box = BoxLayout()
box.add_widget(btn_ok)
box.add_widget(btn_no)
modal.add_widget(box)
modal.open()
print "back now!"
self.editor_main.text = ""
new = CreateView()
new.open()
And the print statement prints "back now!" and the rest of the function is immediately executed, despite the fact the ModalView just opened. I also tried this using a Popup instead of a ModalView, with the same result. I would like execution in the function to pause while I interact with the Popup/ModalView. Is there a way for this to be done built into kivy? Must I use threads? Or will I need to just find some other workaround?

You can't block like that, as that would stop the event loop and you wouldn't be able to interact with your app anymore. The easiest fix is to split this into two functions, and use on_dismiss to continue:
def create_file(self):
modal = ModalView(title="Just a moment", size_hint=(0.5, 0.5))
btn_ok = Button(text="Save & continue", on_press=self.save_file)
btn_no = Button(text="Discard changes", on_press=modal.dismiss)
box = BoxLayout()
box.add_widget(btn_ok)
box.add_widget(btn_no)
modal.add_widget(box)
modal.open()
modal.bind(on_dismiss=self._continue_create_file)
def _continue_create_file(self, *args):
print "back now!"
self.editor_main.text = ""
new = CreateView()
new.open()
It's also possible to use Twisted to make the function asynchronous, though that's a little more complicated.

Related

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.
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.

Efficient multi window tkinter

So I am writing this code with multiple states where each state is represented with its own Tkinter window. the question is how to do this efficiently knowing that each window is defined as a class on a separate file. I'm doing this right now but I'm not sure this okay as the interface seems to be lagging compared to when I launch the class directly (not from the method call)
def StartExperimentButtonCallback(self):
ErrorMessage=''
#build error message
if(len(ErrorMessage)>0):
ErrorMessage += '\n'
messagebox.showerror("Error", ErrorMessage)
else:
self.parent.destroy()
factory = eego_sdk.factory()
v = factory.getVersion()
print('version: {}.{}.{}.{}'.format(v.major, v.minor, v.micro, v.build))
print('delaying to allow slow devices to attach...')
time.sleep(1)
amplifier=factory.getAmplifiers()
amplifier=amplifier[0]
root = tk.Tk()
# This is the section of code which creates the main window
root.geometry('1280x740')
root.configure(background='#FFFFFF')
root.title('Impedances')
timer = ImpedanceGUI(root, amplifier, self.SubjectID.get(), [float(self.RightSphere.get()),float(self.LeftSphere.get()),float(self.RightCylinder.get()), float(self.LeftCylinder.get())])
del self
root.mainloop()

Dearpygui status box not updating properly

I am using dearpygui as an interface for a webscraping tool. I have a "status box" which I want to use to communicate messages to the user. It is setup as a read-only, multi-line input text box and is designed to update with the old value and append the new message. If I run the program, it will work but the updates to the status box do not show up until the webscraping(download_reports function) is finished. The gui is not updating each time the update_status function is called. What is the best way to get this to update and display when called and not when the entire process has finished?
def window_month_end_reports(sender, data):
with window('Month End'):
for building in buildings:
add_checkbox(building)
add_spacing()
add_button("Done", callback=run_month_end_reports)
add_spacing()
add_separator()
add_text("Select reports")
for report in reports_list:
add_checkbox(report)
def run_month_end_reports(sender, data):
item_list = []
building_list = []
delete_item('Month End')
for item in reports_list: # LIST OF REPORTS SELECTED
if get_value(item):
item_list.append(item)
for building in buildings:
if get_value(building):
building_list.append(building)
update_status('Running month end reports')
download_reports(building_list, item_list)
def update_status(message):
set_value('##status box', get_value('##status box') + str(message) + '\n')
with window("Main Window"):
add_button("Month End Reports", callback=window_month_end_reports)
add_text("Status Box:")
add_input_text("##status box", readonly=True, multiline=True, height=500)
start_dearpygui(primary_window="Main Window")
Probably, the best way to solve this is to use an async task to complete the download reports function. You could use run_async_function to launch the download. You can find an example of the long task management in the documentation: async functions

Python GPIO raspberry pi

So bassicaly the user will press the picture_pin buton first, then this calls the picture_taking() function which then it should stop and wait for either (Accept_Pin or Decline_Pin) buttons to be pressed, it should not let the user to continue unless a selection is made. so when the user makes his/her selection then it go back and wait for the picture_pin button. at this stage the Accept_Pin and Decline_Pin should have no affect at all.
I have a python program that waits for a button press from the user then it runs a function to do its thing. what I would like to accomplish is, in that function I would like to also wait for another button press.
(Main.py)
----etc
## PICTURE FUNCTION ##
def picture_taking():
.....etc
returnvalue = subprocess.check_output("sudo ./" + config.layout + ".sh", shell=True)
GPIO.wait_for_edge(Accept_pin, GPIO.FALLING)
GPIO.wait_for_edge(Decline_Pin, GPIO.FALLING)
pad1alreadyPressed = False
pad4alreadyPressed = False
while True:
pad1pressed = not GPIO.input(Accept_pin)
pad4pressed = not GPIO.input(Decline_Pin)
if pad1pressed and not pad1alreadyPressed:
print "Accepted photo:" + str(returnvalue)
pad1alreadyPressed = pad1pressed
if pad4pressed and not pad4alreadyPressed:
print "Declined photo:" + str(returnvalue)
pad4alreadyPressed = pad4pressed
(This here is my Main Program)
#Main section
while True:
time.sleep(0.2)
#camera.hflip = false
camera.shutter_speed = 2000000000
camera.exposure_mode = 'off'
#camera.iso = 1000
camera.preview_fullscreen = True
camera.preview_alpha = 150
camera.start_preview()
GPIO.wait_for_edge(picture_pin, GPIO.FALLING)
picture_taking()
So in the picture_taking() function I would like to ask the user if they accept this picture or not
if they press button (GPIO 19) then they accept or (GPIO 6) as Decline
after they do there selection the program should go back and wait for the main button to select below. and these two buttons should only be selectable inside the function.
I tried this in the Picture_taking() function
when I make the selection it only accepts the "Decline_pin" button, but after I press the "Decline button" then it reads "Accept button".
The other issue is that it does not go back and wait for the Main button "picture_pin" to be pressed. it seems to be stuck here and not exiting.
I am not sure if this is something to do with Indent stuff in Python.
Thank you.
(This should be a comment, not an answer but I don't have reputation 50 yet).
Is this your actual code? I can't understand how it ever returns from the while True: clause in picture_taking(). I'd think you'd need to have something like
while (!(pad1alreadyPressed or pad4alreadyPressed)):

Python: cannot find the handle with win32gui.FindWindowEx()

I'm trying to ge the handle for "Yes" button in a dialog, so I can send the message to click it.
I get the dialog and then I try to find the button, but I always get 0 back.
import win32gui
hwnd = win32gui.FindWindow("#32770", "Programs and Features")
# got back the correct handle to the dialog
win32gui.SetForegroundWindow(hwnd)
btnhdl = win32gui.FindWindowEx(hwnd, 0, "Button", "&Yes")
# returns 0
The button is there and the class and title seem to be ok. I verified it by this:
def printClasses(childHwnd, lparam):
if win32gui.GetWindowText(childHwnd) == "&Yes":
print win32gui.GetClassName(childHwnd), win32gui.GetWindowText(childHwnd)
return 1
win32gui.EnumChildWindows(hwnd, printClasses, None)
# output: Button &Yes
Looks like everything should be fine, but why it doesn't return the handle with FindWindowEx?
Thanks
[From the comments in the OP] Maybe the button is a child of a child, ie a grandchild? IIRC EnumChildWindow enumerates recursively while FindWindowEx does not.

Categories