The TKinter 'after' method is executing immediately, then pausing for the 3 second time after execution. If I also use the 'after' method in my CheckStatus function, it goes onto a rapid loop and never gets to the mainloop().
What am I doing wrong? the documentation says the function would be called after the pause time, but its actually happening before. I want to call CheckStatus every second for a hardware input on Raspberry Pi, as well as have the normal mainloop responding to user events.
from tkinter import *
def DoClick(entries):
global ButCount
ButCount += 1
print("ButCount", ButCount, "TimeCount", TimeCount)
def newform(root):
L1 = Label(root, text = "test of 'after' method which seems to call before time")
L1.pack()
def CheckStatus():
global TimeCount
TimeCount += 1
print("In CheckStatus. ButCount", ButCount, "TimeCount", TimeCount)
# root.after(3000, DoTime())
root = Tk()
ButCount = 0
TimeCount = 0
if __name__ == '__main__':
FormData = newform(root)
root.bind('<Return>', (lambda event, e=FormData: fetch(e)))
b1 = Button(root, text='Click me', command=(lambda e=FormData: DoClick(e)))
b1.pack()
print("Before root.after(")
root.after(3000, CheckStatus())
print("Done root.after(")
root.mainloop()
You are using after incorrectly. Consider this line of code:
root.after(3000, CheckStatus())
It is exactly the same as this:
result = CheckStatus()
root.after(3000, result)
See the problem? after requires a callable -- a reference to the function.
The solution is to pass a reference to the function:
root.after(3000, CheckStatus)
And even though you didn't ask, for people who might be wondering how to pass arguments: you can include positional arguments as well:
def example(a,b):
...
root.after(3000, example, "this is a", "this is b")
You've got one bug in your code, with:
root.after(3000, CheckStatus())
which needs to be:
root.after(3000, CheckStatus)
# ^^ parens removed.
Passing in CheckStatus() actually calls the func rather than passing in its reference.
It also sounds like you want to call CheckStatus over and over again. You can do that with a recursive call in CheckStatus. You've already got:
# root.after(3000, DoTime())
in your code, in CheckStatus(). Perhaps you would want to change that to:
root.after(3000, CheckStatus)
to get you your async checking.
Also, depending on what you're actually trying to do, you may want that "recursive" call to be conditional.
Related
I am writing a function which should return two other functions in it until I decide to stop it. Maybe I even want the function to run 5 hours. I write my code, and it runs perfectly except for one problem: when I click on the starting button, the button stays pressed and I cannot close the infinite loop. I want a way to stop my program without doing key-interrupting or something else. I think a button which can stop my started process would be fine.
Here is my button:
self.dugme1 = Button(text="Start ", command=self.start, fg="black", bg="green", font="bold")
self.dugme1.place(relx=0.05, rely=0.65)
Here are my functions:
def greeting(self):
print("hello")
def byee (self):
print("bye")
def start(self):
while True:
self.greeting()
self.byee()
When I click the button these will be run in the terminal infinitely until I stop them using keyboard interrupting. Is there any way to stop it using an elegant way such as a stop button?
You can achieve this with threading. The following code demonstrates a start/stop button for an infinitely running process:
import tkinter as tk
import threading
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.stop_event = threading.Event()
self.thread = threading.Thread(target = self.start, args = (self.stop_event,))
self.btn = tk.Button(self, text = "Start", command = self.start_cmd)
self.btn.pack()
def start_cmd(self):
self.btn.config(text = "Stop", command = self.stop_cmd)
self.thread.start()
def stop_cmd(self):
self.stop_event.set()
self.destroy()
def start(self, stop_event):
while not stop_event.is_set():
self.greeting()
self.byee()
def greeting(self):
print("hello")
def byee(self):
print("bye")
app = App()
app.mainloop()
You can use threading on start function. I am sure you'll find more information about this here on over the internet.
Example below:
import threading
self.dugme1 = Button(text="Start ",
command=lambda: threading.Thread(name='start', target=self.start, daemon=True).start(),
fg="black",
bg="green",
font="bold"
)
then you can have your start function as:
def start(self):
if self.start_running == False:
self.start_running = True
while self.start_running:
self.greeting()
self.byee()
where self.start_running is a variable that is initially set to false at the beginning of your program (or you can set declare it first in start function with a try-except block)
the first if avoids multiple calls to the same button, calling the function more than once.
You can turn "false" the variable self.start_running to stop the execution of start function.
We want the thread to be daemon, so that the function is terminated whenever main terminates.
If you don't want those function to exit abruptly, turn daemon=False, but make sure to signal self.start_running=False, before your program exits.
I am trying to use a thread to run a function with multiple arguments. Whenever I tried to execute the code, it would say I was providing 1 too many arguments for the function. In my last attempt, I used 1 less argument than the function needed, and voila it works by using the class itself as an argument. Here is my code.
import threading
import sys
import tkinter
class Window():
'''Class to hold a tkinter window'''
def __init__(self, root):
'''Makes a button'''
button1 = tkinter.Button(root,
text = ' Print ',
command = self.Printer
)
button1.pack()
def Function(x,y,z):
'''function called by the thread'''
print(x)
print(y)
print(z)
def Printer(self):
'''function called by the button to start the thread'''
print('thread started')
x = threading.Thread(target=self.Function, args=('spam', 'eggs'))
x.daemon = True
x.start()
root = tkinter.Tk()
Screen = Window(root)
root.mainloop()
Here is the resulting output. Normally I would expect some kind of error from this; note that I only specified 2 arguments when the function calls for three!
thread started
<__main__.Window object at 0x000001A488CFF848>
spam
eggs
What is causing this to happen? Using python 3.7.5 in IDLE, if that is making a difference.
Function is a method, so call self.function implicitly provides self as a first argument. If that is not your intended behavior either consider switch to a static method or use a function.
I'm doing Tkinter. My functions run correctly (playing a bell sound once a minute), but it's not running on a thread. Whenever I click the Start button, the program window grays out and the top says "Not responding" (because my recursive calls to start() are locking it, I'm assuming.)
Why am I not threading correctly? Thanks.
def start():
now = tt1.time()
listOfTimes = []
for t in timeSlotEFs:
listOfTimes.append(t.get())
now = datetime.now()
timee = now.strftime('%H%M')
print('here')
for t in listOfTimes:
if t==timee:
winsound.PlaySound('newhourlychimebeg.wav',winsound.SND_FILENAME)
s.enterabs(now+60,1,start) #I want to call recursively every 60 seconds
s.run()
def start2():
t = threading.Thread(target=start)
t.run()
startBtn = Button(ssFrame, text='Start', command=start2)
startBtn.grid(row=0,column=0,padx=paddX,pady=paddY)
It feels like you mixed between the definitions that threading.Thread imports from.
You should create run function and then start the thread.
This way i see the result and it works:
from tkinter import *
import threading
root = Tk()
def run():
print('hello Thread')
def start2():
t = threading.Thread(target=run)
t.start()
print(t) # Output: <Thread(Thread-1, stopped 10580)>
startBtn = Button(root, text='Start', command=start2)
startBtn.grid(row=0,column=0)
root.mainloop()
In start2(), I changed t.run() to t.start(). That was it, and it now works. Thx Jim.
I have a button whose function is
def callback2():
callback()
The callback() function is
def callback():
usein = None
if inspect.stack()[1][3] == callback2:
global inputText
usein = inputText.get()
return None
while True: #freezes everything, because tkinter
if usein:
return usein
Now, the reason I have to do it like this is because other functions call callback() looking for the value inputted by the button, but I have to make them wait for the button to be pressed. But since I'm using tkinter, the while loop doesn't work - it just makes the GUI freeze. So what can I use instead? I've been working on this for days. I'd be glad to add any other parts of my code if needed.
isButtonClicked = false #a global variable
def callback2():
isButtonClicked = true
callback()
isButtonClicked = false
One idea may be to use a global variable called isButtonClicked and assign a false value, and modify the other methods which call callback method like this:
def othermethod():
if isButtonClicked:
callback()
But you've to make sure that the variables are thread-safe.
Not a tkinter expert, but if you want to get some text input on a button click, the following may work.
def callback():
usein = entry.get()
# do whatever with usein
master = Tk()
entry = Entry(master) # the text input
Button(master, text='Button', command=callback)
I wrote this code to be a version manager, but it doesn't execute the command changeDir(). Why?
https://pastebin.com/VSnhzRzF
You forgot to pass a 'name' argument to changeDir function. And there's no exception because your statement has no effect!
Snippet to represent the problem:
import sys
def exec_smth():
# execution without effect
exec('write_smth')
try:
# execution with exception because of missing argument
exec('write_smth()')
except TypeError as error:
# now we pass an argument
exec('write_smth("I failed because of %s" % error )')
def write_smth(smth):
sys.stdout.write(smth)
exec_smth()
Anyway, outside your __init__ function there're no StringVars at all thanks to garbage collector, so your code would fail anyway!
There're even more problems, because you never bind any of your sv{} to a widget and expect something in return! But ok, let's try to do things with exec:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.entries = []
for _ in range(5):
exec('self.sv{} = tk.StringVar()'.format(_))
exec('self.sv{}.trace("w", self.change_sv)'.format(_))
exec('self.entries.append(tk.Entry(self, text="", textvariable=self.sv{}))'.format(_))
for entry in self.entries:
entry.pack()
def change_sv(*args):
# get id of a variable (you can't rely on that (0-9)!)
idx = args[1][-1:]
# get new value
value = getattr(args[0], 'sv{}'.format(idx)).get()
# result
print('Value changed in self.sv%s to %s!' % (idx, value))
app = App()
app.mainloop()
Output:
As you see - we always need a reference to StringVars and I think that option with a list of them is far better!
Note: If you need to pass something to callback function - use a lambda function! All code tested with Python 3.
Links:
The Variable Classes
Tkinter Callbacks
Behavior of exec function in Python 2 and Python 3