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
Related
Using the button I m executing a script on a separate thread, which after completing needs to come back to the screen where I started from.
If I mention that snippet at the end of my script which I m running on a separate thread giving me a TCL error since I m trying to call the Tkinter thread from another thread.
I want to know how to achieve this goal.
I have tried these 2 way..
1st:
#imports
#from the screen1 button I m sending arg to this function to run on seperate thread.
def screen2(arg):
#this window show the status of script progress bar, label status etc#
window.geometry("1215x770")
window.configure(bg="#FFFFFF")
#background canvas#
#progressbar#
#label#
def task():
if arg = foo:
#I cant use join here or else it will wait for the script to complete without showing screen2#
#I tried putting the mainloop after starting the thread, but as it means the tkinter is on hault it wont execute the after command.
t1 = threading.Thread(target=script, args(ag1, ag2,)).start()
window.mainloop()
window.resize(false, false)
response = messagebox.showinfo("Task", "Script Ran!")
if response:
screen1()
else:
t1 = threading.Thread(target=script, args(ag3, ag4,)).start()
window.mainloop()
window.resize(false, false)
response = messagebox.showinfo("Task", "Script Ran!")
if response:
screen1()
task()
def script(arg1, arg2):
#Doing something here#
I tried using the while loop to keep checking the status of the thread if it's alive then keep looping with the main loop in it if not go back to screen1. which didn't work.
What else option do I have? I need to fix this temporarily since I m only learning it but I have to present it. I m planning to shift it to another GUI whats the best temporary fix for this?
I don't want to write the whole code again because it's multiple screens.
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()
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()
What I am trying to do with the following code is read from arduino serial and update a label with that data every few seconds.
When I run the code it only gets/updates the label once. So I know its something to do with a loop. My understanding was that all code between Tk() and mainloop() was in a loop. Any help would be appreciated.
from Tkinter import *
import serial
import time
def show_values():
arduinoSerialData.write("55")#Write some data to test Arduino read serial and turn on LED if it does
arduinoSerialData = serial.Serial('/dev/cu.usbmodem1461', 9600, timeout=None)
time.sleep(5) #Arduino Serial Reset Timeout
Joes = Tk()
Joes.wm_title("Read Serial")
myData= arduinoSerialData.readline()
temp = float(myData) #convert string to float store in var
templabel = Label(Joes, text=(temp))
templabel.pack()
c = Button(Joes, text="Send Data", command=show_values)
c.pack()
time.sleep(2)
Joes.mainloop()
It appears that you misunderstand how the TK mainloop works. It is not, as you described, a loop between calling Tk() and mainloop(), but rather within Tkinter, external of your programs code.
In order to have a loop, updating a label, you would have to specifically write a loop, using Tk's after method, calling an iterable function over and over.
You could make a function like this to do what you want:
def update_label():
data= float(arduinoSerialData.readline())
templabel.config(text=str(data)) #Update label with next text.
Joes.after(1000, update_label)
#calls update_label function again after 1 second. (1000 milliseconds.)
I am unsure of how the arduino data is retrieved, so you may need to modify that slightly to have the correct data.
This is a general premise though for creating a loop in the manner you described.
I'm running a slowish process of building a set of PDFs using LaTeX that's put together by my script.
The PDFs are built in a for loop. I wanted to show a status window that would add a line for each student that the loop goes through, so that you could see the progress. I have been doing this with print, but I wanted something that integrated well with the Tkinter interface that I have moved to.
I have this:
ReStatuswin = Toplevel(takefocus=True)
ReStatuswin.geometry('800x300')
ReStatuswin.title("Creating Reassessments...")
Rebox2 = MultiListbox(ReStatuswin, (("Student", 15), ("Standard", 25), ("Problems", 25) ))
Rebox2.pack(side = TOP)
OKR = Button(ReStatuswin, text='OK', command=lambda:ReStatuswin.destroy())
OKR.pack(side = BOTTOM)
and then the loop:
for row in todaylist:
and then, inside the loop, after the PDF has been made,
Rebox2.insert(END, listy)
It inserts the row fine, but they all show up (along with the ReBox2 window itself) only after the entire loop is finished.
Any idea about what's causing the delay in display?
Thanks!
Yes, from what I can tell, there are two problems. First, you are not updating the display with each new entry. Second, you are not triggering the for loop with a button but instead having it run on startup (which means that the display won't be created until after the loop exits). Unfortunately however, I can't really work with the code you gave because it is a snippet of a much larger thing. However, I made a little script that should demonstrate how to do what you want:
from Tkinter import Button, END, Listbox, Tk
from time import sleep
root = Tk()
# My version of Tkinter doesn't have a MultiListbox
# So, I use its closest alternative, a regular Listbox
listbox = Listbox(root)
listbox.pack()
def start():
"""This is where your loop would go"""
for i in xrange(100):
# The sleeping here represents a time consuming process
# such as making a PDF
sleep(2)
listbox.insert(END, i)
# You must update the listbox after each entry
listbox.update()
# You must create a button to call a function that will start the loop
# Otherwise, the display won't appear until after the loop exits
Button(root, text="Start", command=start).pack()
root.mainloop()