I'm attempting to transition a program from tkinter to Kivy. When I got to a section where I used messagebox.askyesno, I figured that I could just create a popup with a couple of buttons, and I'd be done. The issue I've encountered is that, while Kivy's popup and tkinter's messagebox are both modal, when I call messagebox.askyesno in a function, messagebox will halt all execution of the current function until the messagebox is destroyed, while the popup will allow the function to finish. My original program had
flag = messagebox.askyesno(message='...',parent=self)
if flag:
#Stuff if flag is true
else:
#Stuff if flag is false
However, this will not work with a Kivy popup since the popup will open, and the program will continue to execute. Is there a way to halt execution until the popup has been destroyed, or another way to solve the problem?
The basic idea is a quasi-dialog for a two player game. Here, the program asks one player if he wants to perform an action, such as move a piece. If the player says "yes," then the second player is given a messagebox.askyesno for a counter-move. A simple analogy is advancing a runner from first base to third base on a single in baseball. You would have to ask the offensive team if he wants to advance the runner, or have the runner remain at second. If the answer is yes, then the program would have to ask the defensive team if he wants to throw to third. It would definitely be possible to create a function to handle each instance of askyesno, with appropriate bindings, but it seems excessive.
I'm not very familiar with how tkinter does things, but kivy requires a slightly different mental model here. You don't want to stop and start the eventloop in between bits of python code, but instead probably want to start the popup, pass any state you need into it or store it somewhere else, then bind the result of the popup (e.g. when the user presses a 'done' button) to some new function that does the rest of your calculation.
I can provide an example if you like, especially if you give more information about what you're trying to do.
Related
I've got an interface where there is a 'Start' button. It runs a 'main' command where it starts a loop to run some measurements on a powermeter. I want to be able to click on an 'OK' button every time the measure is ready to be done. This button would replace the 'Start' button.
But when I try to destroy the button (buttonStart.destroy()) and then run the command main()
, the command executes but doesn't delete the button until the very end.
I've tried using threads from threading package, but it doesn't really work.
If there is a way to both destroy the button and run a command, I would be very interested !
Thanks a lot
The event loop must be given the opportunity to process events in order for the window to be removed from the screen.
There are a couple of ways to make that happen. The first is to call the update_idletasks method on any widget. That is usually enough to process the event related to the destruction of the widget, though it may be necessary to call update instead. Calling update should be avoided if at all possible. My rule of thumb is to start with update_idletasks. If that doesn't work, switch to update or switch to using after.
def my_custom_function():
startButton.destroy()
root.upddate_idletasks()
main()
The second method is to run your main function via after or after_idle. This will let tkinter naturally process all pending events before starting main. I would argue that this is the best approach. I recommend trying after_idle first, and if that doesn't work, switch to after with a small delay.
def my_custom_function():
startButton.destroy()
root.after_idle(main)
I just want the equivalent of closing and reopening my main program. I want to invoke it when a "new"-like option from a drop-down menu is clicked on. Something like calling root.destroy() and then re-initiating the mainloop.
How can I get this done?
There are at least three ways you can solve this.
Method one: the head fake. When you create your app, don't put all the widgets in the root window. Instead, hide the root window and create a new toplevel that represents your application. When you restart it's just a matter of destroying that new toplevel and re-running all your start-up logic.
Method two: nuke and pave. Similar in concept but slightly different in execution. In this model, when you want to restart you simply delete all the widgets in the main window, reset the geometry to null (so the window will once again resize itself based on its contents) and then run the logic that draws all the other widgets.
Method three: if it worked the first time... As suggested by Martin v. Löwis, simply have your program exec a new instance of the program, then exit.
The first two methods are potentially faster and have the (dis?)advantage of preserving the current environment. For example you could save the copy of the clipboard, column widths, etc. The third method absolutely guarantees a blank slate.
If you are on Unix, restart the entire application with os.execv. Make sure you pass all command line arguments etc.
You could take all your GUI building logic and initial state code out of the mainloop and put it into functions. Call these functions from the mainloop (something like: buildgui() & initstate()) and then, when the user clicks your menu icon, just call initstate() to set it back like it was when the application first started.
Is it possible to use tkinter to stop a user from clicking off my application. In other words making my program the top level program at all times?
The concept you are looking for is called a "grab". Tkinter supports grabs with several methods.
To do what you want requires a global grab, which effectively freezes your entire display except for your specific window. This is very dangerous since you can easily lock yourself out of your own computer if you have a bug in your code.
For more information, read about grab_set and other grab commands here: http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.grab_set-method
I am building an app that, when the user hits a 'run' button, generates a table of buttons.
Because this process takes a while, I want to add a popup or progress bar to alert the user that the function is running and not frozen. To do this I decided to create a popup and call my function using threading so that the screen will be updated when the function starts (as opposed to once it is done).
mythread = threading.Thread(target=run_function)
mythread.start()
The trouble is that when I call my function from the above code it works very strangely: the columns of my table are the wrong width, some of my buttons are arbitrarily empty, and others have the wrong fill color. To fix this, all I need to do is to remove the threading operation and simply call run_function()
Any idea why this is happening?
I am new to Python, so it is likely some dumb mistake, but I have no idea. What is different between a process running as a thread and its default operation?
Disclaimer: I haven't worked with Kivy.
Not every framework works well with multithreading.
But most of the GUI frameworks have an event loop which is responsible for managing user events (mouse clicks, keyboard) and queued drawing operations (widgets).
In your case if don't want the UI to be freezed, you should regularly give control to your framework's event loop.
I guess kivy.base.EventLoopBase.dispatch_input is what you need to call to show an added widget or to handle user events.
I'm in the process of creating a filling program with Python 3.3/Tkinter and have run into an issue that's really stumping me. Basically, I want to prevent button presses from registering while certain processes are taking place. I tried changing the button's state to state='disabled' but this only delays the click from registering until after the function is finished running. In other words, despite the button being "disabled", if it's clicked, the button press will register as soon as it's re-enabled. Please see the example code below.
def Button_Action:
Button.config(state='disabled')
#Activate some hardware that takes a few seconds.
Button.config(state='normal')
So, the question is: How can one selectively ignore button presses in Tkinter/Python 3?
I'm really new to Python and have tried searching for relevant questions to no avail, so please forgive me if this is a stupid question, or has been asked before. Also, I have tested this with both Radiobuttons as well as, standard Buttons (in case that helps).
You can use update method.
def button_action():
b.config(state='disabled')
b.update() # <----------
# Activate some hardware that takes a few seconds.
b.update() # <----------
b.config(state='normal')
The first call to update is to make the button displayed as disabled state.
The second call to update is to handle all pending event (clicks in your case) before enable the button.
BTW, it's normal state, not enabled that make the button back to normal state.