I would like the code to run in the background and to update my GUI periodically. How can I accomplish this?
For example, suppose I want to execute something like this in the background of the GUI code you can see below:
x = 0
while True:
print(x)
x = x + 1
time.sleep(1)
This is the GUI code:
class GUIFramework(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
self.master.title("Volume Monitor")
self.grid(padx=10, pady=10,sticky=N+S+E+W)
self.CreateWidgets()
def CreateWidgets(self):
textOne = Entry(self, width=2)
textOne.grid(row=1, column=0)
listbox = Listbox(self,relief=SUNKEN)
listbox.grid(row=5,rowspan=2,column=0,columnspan=4,sticky=N+W+S+E,pady=5)
listbox.insert(END,"This is an alert message.")
if __name__ == "__main__":
guiFrame = GUIFramework()
guiFrame.mainloop()
It is a little unclear what your code at the top is supposed to do, however, if you just want to call a function every second (or every the amount of seconds you want), you can use the after method.
So, if you just want to do something with textOne, you'd probably do something like:
...
textOne = Entry(self, width=2)
textOne.x = 0
def increment_textOne():
textOne.x += 1
# register "increment_textOne" to be called every 1 sec
self.after(1000, increment_textOne)
You could make this function a method of your class (in this case I called it callback), and your code would look like this:
class Foo(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.x = 0
self.id = self.after(1000, self.callback)
def callback(self):
self.x += 1
print(self.x)
#You can cancel the call by doing "self.after_cancel(self.id)"
self.id = self.after(1000, self.callback)
gui = Foo()
gui.mainloop()
If you truly want to run a distinct infinite loop you have no choice but to use a separate thread, and communicate via a thread safe queue. However, except under fairly unusual circumstances you should never need to run an infinite loop. Afte all, you already have an infinite loop running: the event loop. So, when you say you want an infinite loop you are really asking how to do an infinite loop inside an infinite loop.
#mgilson has given a good example on how to do that using after, which you should consider trying before trying to use threads. Threading makes what you want possible, but it also makes your code considerably more complex.
Related
I have a Tkinter program which I want to pause for 3 seconds.
time.sleep doesn't work and the after method doesn't do exactly what I want to.
here is an example code:
from Tkinter import *
def waithere():
print "waiting..."
root = Tk()
print "1"
root.after(3000,waithere)
print "2"
root.mainloop()
output:
1
2
*3 seconds*
waiting...
the output i want to have:
1
waiting...
*3 seconds*
2
thanks.
Normally it's a very bad idea to have a GUI wait for something. That's imply not how event-based programs work. Or more accurately, GUIs are already in a perpetual wait state, and you don't want to block that with your own waiting.
That being said, tkinter has a way to wait until certain things happen. For example, you can use one of the "wait" functions, such as wait_variable, wait_window, or wait_visibility.
Assuming that you wanted waithere to do the waiting, you could use wait_variable to do the waiting, and after to set the variable after a given amount of time.
Here's the solution based on your original code:
from Tkinter import *
def waithere():
var = IntVar()
root.after(3000, var.set, 1)
print("waiting...")
root.wait_variable(var)
root = Tk()
print "1"
waithere()
print "2"
root.mainloop()
The advantage to using these methods is that your code is still able to respond to events while it is waiting.
I found a way like that, i hope it helps you:
from tkinter import *
def waitToShow():
index = 1
while index < 11:
l1.config(text=index)
l1.after(1000)
l1.update()
index += 1
win = Tk()
l1 = Label(win)
l1.pack()
waitToShow()
win.mainloop()
Just for future reference, refrain from using long or infinite loops in Tkinter; they will prevent the UI from responding to user events (AKA freezing). The method I was taught was to periodically update the field using the after() function.
The after() function creates an alarm-callback meaning when called (with the right parameters) it will queue a call to the target method (in the example below def update(self) with our entered delay. You can use a boolean in the class to exit the loop. Create on on __init__ and then when set to False don't call after() anymore.
Here is an example creating a class inheriting Tkinter.Frame to inherit the functionality.
try:
import tkinter as tk
except:
import Tkinter as tk
import datetime
class DelayedUpdateUI(tk.Frame):
def __init__(self, master=None, **kw):
# Create widgets, if any.
tk.Frame.__init__(self, master=master, **kw)
self.timeStr = tk.StringVar()
self.lblTime = tk.Label(self, textvariable=self.timeStr)
self.lblTime.grid()
# Call update to begin our recursive loop.
self.update()
def update(self):
self.timeStr.set(datetime.datetime.now())
# We use after( milliseconds, method_target ) to call our update
# method again after our entered delay. :)
self.after(1000, self.update)
if __name__ == '__main__':
root = tk.Tk()
DelayedUpdateUI(root).grid()
root.mainloop()
A suggestion based on Bryan's answer:
I understand the recommended way from an event-based perspective, but it does not feel very intuitive to me. I have to look up the trick every time I need it. Therefore, I have created a small mixin class that makes usage a bit more intuitive:
import tkinter as tk
class TkWaitMixin:
"""Simple wait timer that makes Tk waiting functionality
more intiutive. Applies the recommended way according to
https://stackoverflow.com/a/51770561/12646289.
"""
def start_wait_timer(self, milliseconds):
self.resume = tk.BooleanVar(value=False)
self.master.after(milliseconds, self.resume.set, True)
# Assume master attribute is available:
# https://stackoverflow.com/a/53595036/12646289
def wait_on_timer(self):
self.master.wait_variable(self.resume)
Example usage:
import tkinter as tk
class MyWindow(tk.Tk, TkWaitMixin):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
self.start_wait_timer(milliseconds)
self.message_label['text'] = message
self.wait_on_timer()
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
Obviously, this will only be of use if you use classes in your tkinter code. Also note that the master attribute should be available in the main class to which the mixin is added.
Edit
Alternatively, usage can be made even easier with a context manager:
import tkinter as tk
class TkWait:
def __init__(self, master, milliseconds):
self.duration = milliseconds
self.master = master
def __enter__(self):
self.resume = tk.BooleanVar(value=False)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.master.after(self.duration, self.resume.set, True)
self.master.wait_variable(self.resume)
Note that the waiting starts when the context manager is exited.
Example usage:
import tkinter as tk
class MyWindow(tk.Tk):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
with TkWait(self.master, milliseconds):
self.message_label['text'] = message
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
You forgot to do the () at root.after(3000,waithere <<<<-)
from tkinter import *
def waithere():
print("waiting...")
root = Tk()
print("1")
root.after(3000,waithere())
print("2")
root.mainloop()
My Requirement
I am developing a program in which I handle space and KeyPress-space Events to handle time based event.
What I am doing is that on the start of program a window gets open to display a time interval, say "5" which depicts 5 Second. Once the time lapses(5Sec) the text "5" gets disappear.
After that the user is required to press space key, when user press space key the same number will display until user don't release space key.
I have given 2 second gap to show next interval(once user releases the space) and in that interval the number should not appear which was appearing in previous set time interval space Event.
Problem Statement:
The issue that I am facing is that I have managed to display number(text) on key press but when I key-release, time interval is not disappearing for 2 second.
Here is my Code:
from tkinter import Label, Tk
import time
times = [3, 4, 5]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
print('called', loop)
self.lbl.pack()
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
Here you go..Please let me know if you have any further query -
from tkinter import Label, Tk
import time
times = [3, 4, 5]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
print('called', loop)
self.lbl.pack()
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
self.parent.update()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
You appear to have found a "crevice" in the way Python interacts with the Tk main loop. At least on my system (Python 2.7, Fedora 22, x86_64) none of the operations in showLabel() have any effect on the screen until the function (including time.sleep(2)) completes.
If you modify the code as follows for troubleshooting, I think you can see what I mean.
def hideLabel(self, event):
global loop, times
# self.lbl.config(text=times[loop])
self.lbl.config(bg = "red")
self.lbl.pack()
def showLabel(self, event):
global loop
# self.lbl.pack_forget()
self.lbl.config(fg = "blue")
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
# self.lbl.config(text= '')
self.lbl.config(bg = "green")
The second label now shows up below the first. The first label gets a blue foreground, but not on the keypress, as we would expect. It apparently only gets it when the function returns, after time.sleep().
I don't pretend to completely understand this - someone who has a deep understanding of Python and Tk might explain. One workaround for this particular program is to add update_idletasks(). This (warning: sloppy terminology ahead) forces the Tk main loop to immediately process pending events.
def showLabel(self, event):
global loop
# self.lbl.pack_forget()
self.lbl.config(fg = "blue")
master.update_idletasks()
time.sleep(2)
loop += 1
Demo(self.parent)
On my system, that made the first label turn blue before time.sleep(). I didn't experiment beyond that.
p.s showLabel() hides the label and hideLabel() shows it?
EDIT: The following slight mod of the original posted code works on my system almost as I would expect. I'm not 100% sure what it is intended to do. But the current label does disappear when I tap the space bar, and the next one shows up 2 seconds later, only to be blanked a variable time after that.
The only puzzle I ran into was that my system basically went crazy when I tried to hold down the space bar for more than 2 seconds. It turned out that the key auto-repeat was kicking in on my system, giving me dozens of keystrokes and causing dozens of subscript out-of-range errors. I gave up at that point so I still don't know how the program behaves if given a single space-down followed 3 seconds later by a single space-up.
from Tkinter import Label, Tk
import time
times = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
master.bind("<h>", self.showLabel)
print('called', loop)
self.lbl.pack()
for child in master.children: print child
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
master.update_idletasks()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
self.lbl.config(bg = "green")
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
If the pack_forget has no effect on your system even with the update_idletasks(), I'd have to guess that the Tk main loop is more platform dependent than is widely known.
I am having a problem with a fairly simple app.
It performs properly, but I would like it to perform a little slower.
The idea is to randomly generate a name from a list, display it, then remove it fromthe list every time a button is clicked.
To make it a little more interesting, I want the program to display several names before
picking the last one. I use a simple for loop for this. However, the code executes so quickly, the only name that winds up displaying is the last one.
using time.sleep() merely delays the display of the last name. no other names are shown.
here is my code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tkinter import *
import random
import time
class Application(Frame):
def __init__(self, master):
""" Initialize the frame. """
super(Application, self).__init__(master)
self.grid()
self.name_list = ["Thorin","Tyler","Jose","Bryson","Joe"]
self.create_widget()
def create_widget(self):
self.lbl = Label(self)
self.lbl["text"] = "Click to spin"
self.lbl["font"] = ("Arial", 24)
self.lbl.grid()
self.bttn = Button(self)
self.bttn["text"]= "Spin"
self.bttn["command"] = self.spin
self.bttn.grid()
def spin(self):
if self.name_list:
for i in range(5):
index = random.randrange(len(self.name_list))
self.lbl["text"] = self.name_list[index]
self.lbl.grid()
self.name_list.pop(index)
else:
self.lbl["text"] = "No more names"
self.lbl.grid()
def main():
root = Tk()
root.title("Click Counter")
root.geometry("600x600")
app = Application(root)
root.mainloop()
if __name__ == '__main__':
main()
This is a pretty common class of problems related to GUI programming. The heart of the issue is the window drawing manager. As long as your function is executing, the drawing manager is frozen; updating the label's text will have no apparent effect until your function ends. So if you have a for loop with a sleep(1) command inside, all it will do is freeze everything for five seconds before updating with your final value when the function finally ends.
The solution is to use the after method, which tells Tkinter to call the specified function at some point in the future. Unlike sleep, this gives the drawing manager the breathing room it requires to update your window.
One possible way to do this is to register six events with after: five for the intermediate name label updates, and one for the final name change and pop.
def spin(self):
def change_name():
index = random.randrange(len(self.name_list))
self.lbl["text"] = self.name_list[index]
self.lbl.grid()
def finish_spinning():
index = random.randrange(len(self.name_list))
self.lbl["text"] = self.name_list[index]
self.lbl.grid()
self.name_list.pop(index)
if self.name_list:
name_changes = 5
for i in range(name_changes):
self.after(100*i, change_name)
self.after(100*name_changes, finish_spinning)
else:
self.lbl["text"] = "No more names"
self.lbl.grid()
(disclaimer: this is only a simple example of how you might use after, and may not be suitable for actual use. In particular, it may behave badly if you press the "spin" button repeatedly while the names are already spinning. Also, the code duplication between change_name and finish_spinning is rather ugly)
The code as it is can show the same item twice since it chooses a new random number each time and so will choose the same number part of the time. Note that you do not pop until after the loop which means that each time you run the program you will have one less name which may or may not be what you want. You can use a copy of the list if you want to keep it the same size, and/or random.shuffle on the list and display the shuffled list in order. Also you only have to grid() the label once,
class Application():
def __init__(self, master):
""" Initialize the frame. """
self.master=master
self.fr=Frame(master)
self.fr.grid()
self.name_list = ["Thorin","Tyler","Jose","Bryson","Joe"]
self.ctr=0
self.create_widget()
def create_widget(self):
self.lbl = Label(self.master width=30)
self.lbl["text"] = "Click to spin"
self.lbl["font"] = ("Arial", 24)
self.lbl.grid()
self.bttn = Button(self.master)
self.bttn["text"]= "Spin"
self.bttn["command"] = self.spin
self.bttn.grid()
def change_label(self):
self.lbl["text"] = self.name_list[self.ctr]
self.ctr += 1
if self.ctr < 5:
self.master.after(1000, self.change_label)
else:
self.ctr=0
def spin(self):
if self.name_list and 0==self.ctr: # not already running
random.shuffle(self.name_list)
self.change_label()
else:
self.lbl["text"] = "No more names"
if __name__ == '__main__':
root = Tk()
root.title("Click Counter")
root.geometry("600x600")
app = Application(root)
root.mainloop()
I'm writing a basic war-driving program. I have gotten it to loop the command to pull all the wireless access points near by. The problem is my stop button doesn't work and I am unable to update the label(I'm not even sure if I can update the label).
import sys, os, subprocess, re
from Tkinter import *
missionGO = 0
count = 0
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.start = Button(frame, text="Start", fg="green",
command=self.startButtonClick)
self.start.grid(row=3)
self.stop = Button(frame, text="Stop", fg="red",
command=self.stopButtonClick)
self.stop.grid(row=3, column=1)
self.totalSSIDLabel = Label(frame, text="Current Access Points: ")
self.totalSSIDLabel.grid(row=0)
self.totalSSID = Label(frame, text=count)
self.totalSSID.grid(row=0, column=1)
def startButtonClick(self):
missionGO = 1
while (missionGO == 1):
wlan = getAccessPoints()
x = numberOfAccessPoints(wlan)
print x
return
def stopButtonClick(self):
missionGO = 0
return
def stop(event):
missionGO = 0
# Finds all wireless AP
def getAccessPoints():
X = subprocess.check_output("netsh wlan show network mode=Bssid",
shell=True)
return X
def numberOfAccessPoints(file):
count = 0
words = file.split()
for line in words:
if re.match('SSID', line):
count = count + 1
return count
#Main
root = Tk()
app = App(root)
root.mainloop()
Tkinter is single threaded. That means that while you are in the while loop inside startButtonClick, no other events are processed. The stop button won't call its command until the startButtonClick function finishes
You need to remember that your program is already running a global infinite loop: the event loop. There's no reason to put another infinite loop inside it. When you want something to run forever, the trick is to put one iteration on the event loop, then when it runs it puts another iteration on the event loop.
The other key to this is to make sure that one iteration of the loop is fast -- it needs to be well under a second (more like under 100ms) or the UI will become laggy.
The logic looks something like this:
def startButtonClick(self):
self.missionGO = 1
self._do_one_iteration()
def _do_one_iteration(self):
if self.missionGO == 1:
wlan = getAccessPoints()
x = numberOfAccessPoints(wlan)
print x
# this adds another iteration to the event loop
self.after(10, self._do_one_iteration)
def stopButtonClick(self):
self.missionGO = 0
I think the main thread is hanging in the while loop of the start button click. Since it's busy it won't even notice the stop button has been pressed.
I can't tell you exactly why your stop button doesn't work, but I think I got the idea of your programm. My suggestion is to establish two threads. The first thread for the UI, and the second for constantly checking wireless networks with given interval (your current code checks ASAP - bad practice, you should pause within the loop.
Since I have not dealt with multithreading in Tkinter, I can only provide you with entry points:
threading Module
time.sleep for updating the nearby networks every second or similar
Is there a way to request a function to be called on Tkinter mainloop from a thread which is not the mainloop?
Tkinter: invoke event in main loop
Good luck!
How can I listen for a button press in Tkinter? I need to listen for a button and not run a function but, I want to run a function that listens for a button.
Update
Here is the code:
#!/usr/bin/env python
import time
import thread
from Tkinter import *
class at(Frame):
def __init__(self, *args, **params):
## Standard heading: initialization
apply(Frame.__init__, (self,) + args, params)
self._parent = None
if len(args) != 0: self._parent = args[0]
self._init_before()
self.v = StringVar()
## Widget creation
self._widgets = {}
self._widgets['button#1'] = Button(self, name='button#1', text='up',)
self._widgets['button#1'].grid(column=1, row=1)
self._widgets['entry#1'] = Entry(self, name='entry#1', textvariable=self.v)
self._widgets['entry#1'].grid(column=2, row=1)
self._widgets['button#2'] = Button(self, name='button#2', text='down',)
self._widgets['button#2'].grid(column=1, row=2)
## Scroll commands
## Resize behavior(s)
self.grid_rowconfigure(1, weight=0, minsize=30)
self.grid_rowconfigure(2, weight=0, minsize=31)
self.grid_columnconfigure(1, weight=0, minsize=30)
self.grid_columnconfigure(2, weight=0, minsize=65)
self.pack(side=TOP, fill=BOTH, expand=1)
## Call to post-init method
self._init_after()
def _init_before(self):
self._init_specificBefore()
def _init_after(self):
self._init_specificAfter()
def u(self):
if self.listening==True:
self.u=True
self.listening=False
def d(self):
if self.listening==True:
self.d=True
self.listening=False
def listen(self):
#listen for self.u and self.d
def _init_specificBefore(self):
pass
def _init_specificAfter(self):
range=[0,100]
current_guess=range[1]/2
n=1
x=""
while 1:
self.v.set(str(current_guess)+"?")
x=self.listen()
if x=="u":
range[0]=current_guess
if range[0]==0:
current_guess=round(current_guess+round(range[1]/2))
else:
current_guess=round(current_guess+round((range[1]-range[0])/2))
elif x=="d":
range[1]=current_guess
if range[0]==0:
current_guess=round(round(range[1]/2))
else:
current_guess=range[0]+(round((range[1]-range[0])/2))
elif x=="y":
print "It took me "+str(n)+" guesses."
break
n=n+1
if __name__ == '__main__':
root = Tk()
o = at(root)
o.pack(side=TOP, fill=BOTH, expand=1)
root.mainloop()
What you are trying to accomplish is unclear, but it sounds like you misunderstand how GUI programming works in general and are just asking the wrong question. Generally speaking, If you think you need to poll for button presses rather than take advantage of the event loop you are doing it wrong.
So, the best answer to "how can I listen for a button and not run a function" is "you can't". It's a bit like asking "how can I put this screw in the wall with a hammer?". You can, but it's not the right way to use the tool so for all practical purposes you can't and shouldn't be taught how to.
It seems to me you are making a guessing game, and I think you want the user to press the up or down button after each guess. is that correct? If so, why not just have the up and down buttons call a function that makes a single guess?
You'll want to create a loop along the lines of while not exiting where exiting is declared False at the start and is toggled when quitting the application. Then in that loop you can put functions that check certain state and update other state. Each time round the loop it will run each of these functions. The magic is to make each of these functions fast enough that it seems like they're running constantly.