As title mentions, I'm trying to update the values in my labels in a tkinter gui. The values are taken from OpenWeatherMap API using pyown and at my subscription level, I can only make 60 calls/minute. Since I plan to make many calls, I would like to have my gui update every minute or 5-minutes. I've spent the last few days reading the similar questions and I have figured out that I need the sleep function to delay the update. Some have suggested that I put what I want to repeat in a while True infinite loop, but wheN I tried that, the gui only updated when I closed out the window, and I was unable to control the time in between updates. Others have suggested that I use the .after function, but when I do this, my program compiles but the gui never pops up. I'm looking for someone to show me how either of these solutions work in my code specifically, or if there is a third solution that lends itself better to my code that would be better, please let me see how it would look, because I am stumped.
import tkinter as tk
import pyowm
from datetime import datetime, timedelta
class WeatherInfo(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.wm_title('Forecast')
self.currentTime = tk.StringVar(self, value='')
self.d2temp_7 = tk.StringVar(self,value='')
self.owm = pyowm.OWM('*INSERT YOUR OWM KEY HERE*')
self.headLabel = tk.Label(self, text='5-Day Forecast of Cayce, US.')
self.headLabel.pack()
self.footLabel = tk.Label(self, textvariable=self.currentTime)
self.footLabel.pack(side=tk.BOTTOM)
self.day2Frame = tk.LabelFrame(self, text='D2')
self.day2Frame.pack(fill='both', expand='yes', side=tk.LEFT)
tk.Label(self.day2Frame, text="Temperature:").pack()
tk.Label(self.day2Frame, textvariable=self.d2temp_7).pack()
self.search()
def search(self):
fc = self.owm.three_hours_forecast_at_id(4573888)
try:
self.currentTime.set(datetime.today())
self.d2temp_7.set("7am: " + str(fc.get_weather_at((datetime.today().replace(hour=13, minute=00) + timedelta(days=1))
.strftime ('%Y-%m-%d %H:%M:%S+00')).get_temperature('fahrenheit')['temp']))
except:
self.temp.set('Pick a city to display weather.')
def _quit(self):
self.quit()
self.destroy()
if __name__== "__main__":
app = WeatherInfo()
app.mainloop()
More on what I have tried:
while True:
def __init__
def search
But as this answer points out, other answer , I won't see any changes I make in my while True preceding the root.mainloop()
This question came close to my answer using root.after(milliseconds,results), but when I implimented this answer my gui never showed. infinitely update
Thank you to anyone who tries to answer this.
Edit: I have made my code shorter as per recommendation.
Based on this you can have a function,forecast_update, like the following:
import tkinter as tk
#these two needed only for API update simulation
import random
import string
root = tk.Tk()
forecast = tk.Label(text="Forecast will be updated in 60 seconds...")
forecast.pack()
# returns a string with 7 random characters, each time it is called, in order to simulate API
def update_request_from_api():
return ''.join(random.choice(string.ascii_lowercase) for x in range(7))
# Your function to update the label
def forecast_update():
forecast.configure(text=update_request_from_api())
forecast.after(60000, forecast_update) # 60000 ms = 1 minute
# calling the update function once
forecast_update()
root.mainloop()
Related
Currently I'm working on a project of mine involving sensors, and showing that sensory data on a display via TKinter. Everythings written in Python 3.7.3.
The issue im currently handling, is to update the label in the window, while the mainloop is running.
What i mean by this, is that if i execute the script, first the window options get defined, then the update function gets defined with a while true loop. Then its supposed to start the window. Now because of the while true loop it does not reach the window.mainloop() point (obviously, the while loop doesn't break...). My interest was peaked and i tried to put the window.mainloop() function inside the while loop of the update (please don't blame me, i know my script is a spaghetti mess.) I figured out that i could run the whole thing in threads, and so i decided to thread the whole window process, and add queues for the sensor data. Now the while loop was still in the way and didnt work properly, and after a bit of googling i found a code snippet that might help me. After trying to implement it in my script, i got an exception "function init expects 3 arguments, but 4 were given.." (code below) and I'm kinda running out of ideas on this.
Bear in mind that im not raelly a developer, i just need a script that can handle sensor data, dispaly it in a window, and export the current data to a database. So go easy on the blame please.
Current Script:
import time
import board
import adafruit_dht
import threading
import queue
from tkinter import *
dhtDevice = adafruit_dht.DHT22(board.D4, use_pulseio=False)
tempQ = queue.Queue(maxsize=0)
humQ = queue.Queue(maxsize=0)
class windowMain:
def __init__(self):
self.tempC_label = Label(fenster, text="Placeholder TempC")
self.humidity_label = Label(fenster, text="Placeholder Humidity")
self.tempC_label.pack()
self.humidity_label.pack()
self.tempC_label.after(2000, self.labelUpdate)
self.humidity_label.after(2000, self.labelUpdate)
def labelUpdate(self, tempQ, humQ):
self.tempC_label.configure(text= tempQ.get() + "°C")
#this is just to confirm if the function called or not, to see if the label updated or not.
#if the label didnt update, and the function called, there is something wrong with the function
#if the label didnt update, and the function didnt call, there is a problem somwhere else
print("Current Temp: " +tempQ.get() + "°C")
self.label.after(2000, self.labelUpdate)
if __name__ == "__main__":
windowName = Tk()
windowName.title = ("Climatemonitor")
windowMain(windowName)
windowName.mainloop()
try:
windowThread = threading.Thread(target=windowMain, args=(tempQ, humQ, ))
windowThread.start()
except:
print("Unable to start thread")
while True:
try:
temperature_c= dhtDevice.temperature
tempText= temperature_c
tempText= str(tempText)
tempQ.put(tempText)
humidity = dhtDevice.humidity
humidityP = str(humidity)
#this one is just to check if the sensor reads data
print(
"Temp: {:.1f} C Humidity: {}% ".format(
temperature_c, humidity
)
)
time.sleep(2.0)
except RuntimeError as error:
print(error.args[0])
time.sleep(2.0)
continue
except Exception as error:
dhtDevice.exit()
raise error
time.sleep(2.0)
The ultimate goal is to display my sensor data, with a 2 second refresh (the HZ rate of the Sensor), while the sensor continues to read every 2 seconds.
I'd also like to add that this is my first time using Python, since im, again, not really a developer yet.
Thanks a bunch in advance for every critique and help
most simple way of doing this would be using a button to execute a function and then including your while loop in that function,
Using an button gives you an point where you can start running while instead of directly starting it as soon as you run your program
Sample code should be something like this,
import tkinter as t
def execute():
print('hello')
window = t.Tk()
window.title("system")
window.geometry("550x250")
b1 = t.Button(window, text="Start", width=15, command=execute)
b1.grid(row=1, sticky="W", padx=4)
window.mainloop()
As there will be no user interaction, a button can invoked using button.invoke method such as following,
import tkinter as t
def execute():
print('hello')
window = t.Tk()
window.title("system")
window.geometry("550x250")
b1 = t.Button(window, text="Start", width=0, command=execute)
#b1.grid(row=1, sticky="W", padx=4)
b1.invoke()
window.mainloop()
here removing .grid() will cause the button to disapper but can affect your GUI while updating the label value later , also have a look at this ->
Is there a way to press a button without touching it on tkinter / python?
Python tkinter button.invoke method trouble
I've looked around stackoverflow and am pretty sure this isn't a duplicate. I need to poll a queue every 1ms (or as quickly as possible), and this has to run in the same thread as my tkinter window otherwise I can't update my labels from the queue data. (someone correct me if i'm wrong here). Currently my code looks like this:
def newData():
global gotNewData
if q.get == 1: #if there is new data
updateVariables() #call the function to update my labels with said data
q.queue.clear() #clear the queue
gotNewData = 0 #no new data to get
q.put(gotNewData)
MainPage.after(1, newData)
else:
MainPage.after(1, newData)
however when I run this code, my tkinter window freezes instantly. I commented out the line which calls the other function and it still freezes so i'm pretty sure it's this function which is causing the problem. Any help is greatly appreciated.
So what I would do if you must have threading is to use a StringVar() in the threaded function instead of having to work with a widget directly.
I feel like 1000 times a second is excessive. Maybe do 10 times a sec instead.
Take a look at this example and let me know if you have any questions.
import tkinter as tk
import threading
root = tk.Tk()
lbl = tk.Label(root, text="UPDATE ME")
lbl.pack()
q_value = tk.StringVar()
q = tk.Entry(root, textvariable=q_value)
q.pack()
def updateVariables(q):
lbl.config(text=q)
def newData(q):
if q.get() != '':
updateVariables(q.get())
root.after(100, lambda: newData(q))
else:
root.after(100, lambda: newData(q))
print("not anything")
thread = threading.Thread(target=newData, args=(q_value, ))
thread.start()
root.mainloop()
I'm currently trying to build a security system on my raspberry pi. I have 9 buttons to enter a code. Once the code is entered u can press "arm" to arm the system. Then a function checks a PIR sensor for movement. When movement is detected an alarm should go off, for which I need time.sleep.
So my actual problem is, with time.sleep I block the programm for the time its sleeping, thus i cant disarm the system during alarm mode.
My idea so far was just to put everything into threads. But without success so far. Are there any better solutions to time.sleep?
You don't need to use threads. With tkinter you can easily schedule a function to run every couple of seconds in the main thread to check the sensor or do any other work that you want.
Here's a short contrived example, which will work just fine assuming that the sensor check doesn't take more than a couple hundred milliseconds. It's not exactly how I would do it, but it illustrates how you can have some function run periodically without having to put the UI to sleep.
import tkinter as tk
class App():
def __init__(self):
self._job_id = None
self.init_gui()
def init_gui(self):
self.root = tk.Tk()
self.button = tk.Button(self.root, width=6, text="Arm", command=self.arm)
self.button.pack(padx=20, pady=20)
def start(self):
self.root.mainloop()
def arm(self):
self.button.configure(text="Disarm", command=self.disarm)
self.poll()
def disarm(self):
self.button.configure(text="Arm", command=self.arm)
if self._job_id:
self.root.after_cancel(self._job_id)
def poll(self):
# ... check the sensor here ...
movement = True
if movement:
print("beep!")
self._job_id = self.root.after(2000, self.poll)
app = App()
app.start()
If your problem is simply looking for a better method to use then time.sleep, you could consider using time.time rather than time.sleep and then use checks to see what actions should occur. This would avoid using time.sleep which blocks all events (including your GUI). As a quickly written example to demonstrate this idea:
from time import time
millis = lambda: int(time() * 1000)
def updateAlarm(lastTime, beepRate, currentState):
now = millis()
if now > (lastTime + beepRate):
return (not(currentState), now)
return (currentState, now)
last = millis() #set the first time for the alarm
rate = 2000 # change state every two seconds
state = False # the alarm is currently off
while True: # just for demonstration purposes, while True: won't work inside of a tkinter GUI
change = updateAlarm(last, rate, state)
if change[0] != state: # if the state has changed, update it and print
state = change[0]
last = change[1]
print(state)
Depending on your implementation, this may make more sense, especially if you are not using tkinter. Personally, I think Bryan's solution is much more elegant, especially since it doesn't require constantly checking the alarm to see if it needs to update.
i have a much more advanced code, but it all comes to this simple example:
from Tkinter import *
import time
def destroyPrint():
global printOut
try:
printOut.destroy()
except:
pass
def sendData():
global new
global printOut
for i in range(6):
destroyPrint()
time.sleep(1)
printOut=Label(new,text=i,font=('arial',15,'bold'))
printOut.place(x=300,y=500)
def newWindow():
global new
print("ok")
new=Toplevel()
new.minsize(800,600)
functionButton=Button(new,text="Send me",width=20,height=20, command=sendData)
functionButton.place(x=300,y=150)
main = Tk()
main.minsize(800, 600)
menu=Button(main,text="Send data",width=20,height=20, command=newWindow)
menu.place(x=300,y=150)
mainloop()
In this simple example, i want to start sendData function, which will update printOut Label accordingly on every loop iteration. We all know that it doesn't, and that it hangs, until function is done, and prints last number (5).
I tried countless examples with threading and queueing, and i am failing badly.
Please, just simple clarification on this example, how to do threading correctly when you have Tkinter elements in a function that needs to be performed in another thread.
I am really getting frustrated here and i spent last 2 days on this step...
You have to add update_idletasks() to update the label. Instead of destroying and creating, just update the text , and use after() in Tkinter instead of sleep as it spawns a new process whereas time.sleep() hangs the program while sleeping.
from Tkinter import *
import time
def sendData():
global new
##global printOut
printOut=Label(new,text="0",font=('arial',15,'bold'))
printOut.place(x=300,y=500)
for x in range(6):
##destroyPrint()
printOut.config(text=str(x))
new.update_idletasks()
time.sleep(1)
def newWindow():
global new
print("ok")
new=Toplevel()
new.minsize(800,600)
functionButton=Button(new,text="Send me",width=20,
height=20, command=sendData)
functionButton.place(x=300,y=150)
main = Tk()
main.minsize(800, 600)
menu=Button(main,text="Send data",width=20,height=20, command=newWindow)
menu.place(x=300,y=150)
main.mainloop()
When I call the update() method using tkinter instead of rewriting the label it just writes the label under the previous call. I would like for this to rewrite over the previous line.
For Example:
root=Tk()
while True:
w=Label(root, text = (price, time))
w.pack()
root.update()
Your problem is simply this: when you do while True, you create an infinite loop. The code in that loop will run until you force the program to exit. In that loop you create a label. Thus, you will create an infinite number of labels.
If you want to update a label on a regular basis, take advantage of the already running infinite loop - the event loop. You can use after to schedule a function to be called in the future. That function can reschedule itself to run again, guaranteeing it will run until the program quits.
Here's a simple example:
import Tkinter as tk
import time
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.clock = tk.Label(self, text="")
self.clock.pack()
# start the clock "ticking"
self.update_clock()
def update_clock(self):
now = time.strftime("%H:%M:%S" , time.gmtime())
self.clock.configure(text=now)
# call this function again in one second
self.after(1000, self.update_clock)
if __name__== "__main__":
app = SampleApp()
app.mainloop()
No.
I suspect, without having seen it, that there are at least a couple of confusions in the code wDroter has written. In general, it is NOT necessary in well-structured Tkinter code to use update() at all. Here's a small example that illustrates updates to the text of a Label:
import Tkinter
import time
def update_the_label():
updated_text = time.strftime("The GM time now is %H:%M:%S.", time.gmtime())
w.configure(text = updated_text)
root = Tkinter.Tk()
w = Tkinter.Label(root, text = "Hello, world!")
b = Tkinter.Button(root, text = "Update the label", command = update_the_label)
w.pack()
b.pack()
root.mainloop()
Run this. Push the button. Each time you do so (as long as your pushes differ by at least a second), you'll see the text update.
you want to use .configure insted
while True:
w.Configure(text = (price, time))
root.update()
instead of
w.pack()
you can write
w.grid(row=0, column=0)
pack() in tkinter usually packs things in a single row/column. It lays things out along the sides of a box. Whereas, grid() has more of a table like structure. So when you write row=0 and column=0, it has no choice but to replace the previous if it exists. Because you have provided a very specific position instead of just pushing it to the window (which is hat pack() does)
The BadRoot class should demonstrate the problem that you are having. You can comment out the call to the class to verify with a complete, working example. If you run the code as written, it will update the label in the GoodRoot class. The first line that is commented out shows an alternative syntax for changing the text in your label.
from tkinter import Tk, Label
from time import sleep
from random import random
class BadRoot(Tk):
def __init__(self, price, time):
super().__init__()
self.labels = []
while True:
self.labels.append(Label(self, text=(price, time)))
self.labels[-1].pack()
self.update()
sleep(1)
class GoodRoot(Tk):
def __init__(self, callback):
super().__init__()
self.label = Label(self, text=str(callback()))
self.label.pack()
while True:
## self.label['text'] = str(callback())
self.label.configure(text=str(callback()))
self.update()
sleep(1)
if __name__ == '__main__':
## BadRoot('$1.38', '2:37 PM')
GoodRoot(random)
The problem with your original code is that a new label is created and packed into the interface each time through the loop. What you actually want to do is just edit the text being displayed by the label instead replacing the label with a new one. There are others ways of doing this, but this method should work for you.