after cancel method tkinter - python

I have been through many examples about after cancel method in Tkinterbut I am not clear about that.In my code I am using the after method to enter the value of s in entry box after a certain time delay.Now i need to stop that after the cycle is completed when a button is pressed.
def read_pressure():
global s
s+=1
E3.delete(0,'end')
E3.insert(0,s)
top.after(1000, lambda:read_pressure())
Now i need to stop these loop using a button.How to do that??
I am using python 3.5
With the help of Dan i can able to stop after method.But it freezes my gui and I can't able to recall the after method.
How to do that?!

You need to keep the return value around:
handle = top.after(1000, lambda:read_pressure())
And then when the button is clicked do:
if handle:
top.after_cancel(handle)
handle = None
Both with global handle. I would prefer to make these methods and use self to store state than mutate globals.

Related

Pause function execution while awaiting input in entry (tkinter)

Let's say I've got a function that requests input. How can I pause the function that calls this function while I am waiting for user input in an entry widget. I tried it with a while loop and time.sleep(sec). Furthermore I executed the function within another thread (so the main thread should not be interrupted), but the problem that always occurs is that the whole program freezes (typing in entry impossible)!
Because I do not have that much experience with Python I am truly stuck.
PS: I am coding on a mac.
The code I used:
import time
_input = []
def get_input()
try:
return _input[0]
except IndexError:
return None
def req():
while get_input() == None:
time.sleep(1)
return get_input()
The function req() is always called within a function which is called via 'getattr()' in a function which parses the input in the entry widget. The variable '_input' automatically gets the user input from the entry. The input I then successfully got from the '_input' variable is then discarded.
Maybe the problem is that the function is running and that is why another function cannot be executed... but shouldn't that be irrelevant if I was using a distinct thread? Why didn't that work...?
Here's a function that creates a simple Tkinter GUI that allows the user to input data into an Entry widget. When the user hits Enter the function gets the current value from the Entry, closes the GUI and returns the value to the calling code. The calling code will block until tkinter_input returns. If the user closes the GUI window with the close button the contents of the Entry are ignored, and None is returned.
import tkinter as tk
def tkinter_input(prompt=""):
root = tk.Tk()
tk.Label(root, text=prompt).pack()
entry = tk.Entry(root)
entry.pack()
result = None
def callback(event):
nonlocal result
result = entry.get()
root.destroy()
entry.bind("<Return>", callback)
root.mainloop()
return result
result = tkinter_input("Enter data")
print(result)
The way to wait for user input is to open up a dialog. A modal dialog will force the user to dismiss the dialog, a non-modal will allow the user to continue to use the main application.
In your case, you can create a dialog using a Toplevel and fill it with any widgets that you want, then use the wait_window function to wait for that window to be destroyed. To make it modal you can create a "grab" on the toplevel. To keep this simple, I have not done that in the following example.
Here is a basic example. The key is the call to wait_window which will not return until the dialog is destroyed.
import tkinter as tk
class CustomDialog(object):
def __init__(self, parent, prompt="", default=""):
self.popup = tk.Toplevel(parent)
self.popup.title(prompt)
self.popup.transient(parent)
self.var = tk.StringVar(value=default)
label = tk.Label(self.popup, text=prompt)
entry = tk.Entry(self.popup, textvariable=self.var)
buttons = tk.Frame(self.popup)
buttons.pack(side="bottom", fill="x")
label.pack(side="top", fill="x", padx=20, pady=10)
entry.pack(side="top", fill="x", padx=20, pady=10)
ok = tk.Button(buttons, text="Ok", command=self.popup.destroy)
ok.pack(side="top")
self.entry = entry
def show(self):
self.entry.focus_force()
root.wait_window(self.popup)
return self.var.get()
To use it, call the show method:
dialog = CustomDialog(root, prompt="Enter your name:")
result = dialog.show()
With the above, result will have the string that you entered.
For more information about creating dialogs, see Dialog Windows on the effbot site,
GUI programming is quite different from normal python scripts.
When you see the GUI pop up, it is already running in the mainloop. That means that your code is only invoked from the mainloop as a callback attached to some event or as a timeout function. Your code actually interrupts the flow of events in the mainloop.
So to keep the GUI responsive, callbacks and timeouts have to finish quickly (say in 0.1 second max). This is why you should not run long loops in a callback; the GUI will freeze.
So the canonical way to do a long calculation in a GUI program is to split it up into small pieces. Instead of e.g. looping over a long list of items in a for loop, you create a global variable that holds the current position in the list. You then create a timeout function (scheduled for running by the after method) that takes e.g. the next 10 items from the list, processes them, updates the current position and reschedules itself using after.
The proper way to get input for a function is to get the necessary input before starting the function. Alternatively, you could use a messagebox in the function to get the input. But in general it is considered good design to keep the "guts" of your program separate from the GUI. (Consider that you might want to switch from Tkinter to the GTK+ or QT toolkits in the future.)
Now onto threads. You might think that using threads can make long-running tasks easier. But that is not necessarily the case. For one thing, the standard Python implementation (we shall call it CPython) has a Global Interpreter Lock that ensures that only one thread at a time can be running Python bytecode. So every time your long-running calculation thread is running, the other thread containing the mainloop is halted. In Python 3 the thread scheduling is improved w.r.t. Python 2 as to try and not starve threads. But there is no guarantee that the GUI thread gets enough runtime when the other thread is doing a ton of work.
Another restriction is that the Tkinter GUI toolkit is not thread safe. So the second thread should not use Tkinter calls. It will have to communicate with the GUI thread by e.g. setting variables or using semaphores. Furthermore, data structures that are used by both threads might have to be protected by Locks, especially if both threads try to modify them.
In short, using threads is not as simple as it seems. Also multithreaded programs are notoriously difficult to debug.

Tkinter Text: calling a custom function with the word selected by double-click

When the user double click a word in a Text widget, some internal callback is called, which results in the selection of a word. I want to have some additionnal task to be done on that word. How can I do that? The naive idea was to bind my own callback to the double click. It did not worked, because of a wrong priority: my callback was executed first, before the word is selected, and the Text callback wascalled after.
How can I solve that?
An idea would be to retreive a handle to the existing callback and reuse it after:
actual_callback = **<how can I get this handle?>**
text.bind("<Double-Button-1>", my_callback)
then my callback would straightforwardly write as follows:
def my_callback(event):
actual_callback(event) # this will select the word
w = text.selection_get()
do_something(w)
Please help to finalize it this way, or feel free to propose a better way of acheiving my original goal.
The easy answer is to simply wait a beat before running your callback.
text.bind("<Double-Button-1>", lambda e: text.after(2, my_callback, e)) # wait 2 ms before running callback

Tkinter Frame Not Recognizing Keypresses

This question is NOT a duplicate of this question: Why doesn't the .bind() method work with a frame widget in Tkinter?
As you can see, I set the focus to the current frame in my game_frame() method.
I'm writing a Chip-8 emulator in Python and using Tkinter for my GUI. The emulator is running, but I can't get Tkinter to recognize keypresses. Here is my code:
def game_frame(self):
self.screen = Frame(self.emulator_frame, width=640, height=320)
self.screen.focus_set()
self.canvas = Canvas(self.screen, width=640, height=320, bg="black")
self._root.bind("<KeyPress-A>", self.hello)
for key in self.CPU.KEY_MAP.keys():
print(key)
self.screen.bind(key, self.hello)
self.screen.pack()
self.canvas.pack()
def hello(self, event):
if event.keysym in self.CPU.KEY_MAP.keys():
self.CPU.keypad[self.CPU.KEY_MAP[event.keysym]] = 1
self.CPU.key_pressed = True
self.CPU.pc += 2
sys.exit()
def run_game(self, event):
self.game_frame()
self.CPU.load_rom("TANK")
while True:
self._root.update()
self.after(0, self.CPU.emulate_cycle)
Could you please help me figure out what's going wrong? I think it might have something to do with my game loop interfering with the key bindings, but I'm not sure. The hello method never gets called when I run the game because the program continues to run in an infinite loop and never exits, regardless of what key is pressed. Thank you!
The problem could be due to two things. Without seeing all your code it's impossible to say for sure.
For one, you are binding to a capital "A" rather than a lowercase "a" -- have you testing that the binding works or not when you press a capital A?
Also, you are using after and update incorrectly. You may be starving the event loop, preventing it from processing key presses. The right way to run a function periodically is to have a function that (re)schedules itself.
class CPU_Class():
...
def run_cycle(self):
self.emulate_cycle()
self._root.after(1, self.run_cycle)
Two things to note:
don't use after(0, ...) -- you need to give tkinter at least a ms or so to process other events.
the run_cycle function is responsible for running one cycle, and then scheduling the next cycle to run in the future.
Once you do that, you no longer need your while loop. You can simply call run_cycle once, and that will start the CPU running.

Is there an instant-updating function for texts or patterns as button.config() in tkinter?

I wrote a modified program of the 'mines' game, and I hope it shows every step/click graphically. I use time.sleep(0.5) to make a pause. So, in general the main program is like:
check_block():
if mine == 0:
buttons[current].config(image = tile_clicked)
elif mine == 1:
buttons[current].config(image = tile[1])
...
while(1):
time.sleep(0.5)
check_block()
get_next()
if check_fail():
break
However, the buttons don't update every 0.5 second: they are all updated together when the game(loop) finishes.
I guess it's just like 'cout' in C++: if you don't flush they will get stacked. So, is there a method to get them updated step by step, or say, instantly?
Thanks!
In all GUI systems you have to allow the message loop to run so that Windowing events occur promptly. So do not use a while loop like this. Instead, create a method that calls check_block() and get_next() and use after to call that function after a delay. At the end of that function, you use after again to call the same function again so that this function is called every 0.5 second forever. The after function queues a timer event and then lets the message queue be processed. Once your timer event fires, the callback function is run which allows you to do things and keep the UI responsive.
You should never call sleep in a GUI program. This is because the GUI must be "awake" at all times so that it can service events (including internal events that cause the screen to update). Instead, leverage the already-running eventloop by using the after method to put events on the queue at regular intervals.
In your case, you would replace the while loop with something like:
def do_check():
check_block()
if not check_fail():
root.after(500, do_check)
# in your initialization code, start the loop by calling it directly:
do_check()
I don't know what your get_next function does, so I don't know if you need to call it periodically too. Probably not. I'm guessing it waits for the next button press, which you don't need to do with tkinter or most other GUI toolkits. Instead, you configure the button to call a function when clicked.
Regardless, the way to do the type of looping you want is to place events on the event queue at a regular interval.

Creating A UI Window For My Previous Script

folks! So, thanks to you guys I was able to figure out what it was I was doing wrong in my previous script of staggering animation for selected objects in a scene. I am now on part two of this little exercise: Creating a UI for it.
This involves creating a window with a button and user input of how much the animation will be staggered by. So, instead of me putting how much the stagger should increment by (which was two in my previous script), I'd now allow the user to decide.
The script I have so far created the window, button, and input correctly, though I am having some trouble with getting the UI to properly execute, meaning when I click on the button, no error pops up; in fact, nothing happens at all to change the scene. I get the feeling it's due to my not having my increment variable in the correct spot, or not utilizing it the right way, but I'm not sure where/how exactly to address it. Any help would be greatly appreciated.
The code I have (with suggested edits) is as follows:
import maya.cmds as cmds
spheres = cmds.ls(selection=True)
stagWin = cmds.window(title="Stagger Tool", wh=(300,100))
cmds.columnLayout()
button = cmds.button(label="My Life For Aiur!")
count = cmds.floatFieldGrp(fieldgroup, query=True, value=True)
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
cmds.showWindow(stagWin)
def stagger(fieldgroup):
for i in spheres:
cmds.selectKey(i)
cmds.keyframe(edit=True, relative=True, timeChange=count)
print "BLAH"
Moving the comments into an answer because I think I've got it all figured out finally:
First of all, the better practice is to pass the stagger object to the button command rather than the string. so that would be:
cmds.button(label="My Life For Aiur!", command=stagger)
Secondly, the count isn't getting updated, so it stays 0 as per your third line. To update that:
count = cmds.floatFieldGrp(fieldgroup, query=True, value=True)
But wait, where did fieldgroup come from? We need to pass it into the function. So go back to your button code and take out the command entirely, also saving the object to a variable:
button = cmds.button(label="My Life For Aiur!")
Now store the object for the fieldgroup when you make it:
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
Now that you have fieldgroup, you can pass that in the function for the button, like this:
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
I had to wrap the function in a lambda because we're passing fieldgroup, but if I just put stagger(fieldgroup) it would call that and pass the result of that into the command for the button
Also update stagger def with fieldgroup argument:
def stagger(fieldgroup):
One final note that won't actually affect this, but good to know:
when you shift the keyframes inside stagger you're using a DIFFERENT count variable than the one you declared as 0 up above. The outer one is global, and the inner is local scope. Generally it's best to avoid global in the first place, which fortunately for you means just taking out count = 0
Putting that all together:
import maya.cmds as cmds
spheres = cmds.ls(selection=True)
stagWin = cmds.window(title="Stagger Tool", wh=(300,100))
cmds.columnLayout()
button = cmds.button(label="My Life For Aiur!")
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
cmds.showWindow(stagWin)
def stagger(fieldgroup):
count = 0
increment = cmds.floatFieldGrp(fieldgroup, query=True, value=True)[0]
print count
for i in spheres:
cmds.selectKey(i)
cmds.keyframe(edit=True, relative=True, timeChange=count)
count += increment
print "BLAH"

Categories

Resources