I have several widgets, or more correctly, Labels, that update perfectly when a method finishing with self.after() is called in __init__.
That's not the case with this Label here, which gets the weather Information from OWM (OpenWeatherMap) through pyowm, and should be updated every specific amount of time, defined in the after() function.
I've tried almost everything in my knowledge, though I'm pretty newbie to Python. I've searched google for days, but no working solution was found, or not that I could make it work.
I've tried to put the after function in every method, even __init__.
The trimmed down main() for debug and the Weather Class follows:
import tkinter as tk
from Weather import Meteo
def displayMeteo(win):
# Place Meteo portlet
meteoHandler = Meteo(win, bg='black', fg='white')
meteoHandler.pack(side='top', anchor='ne')
def main():
# Set the screen definition, root window initialization
root = tk.Tk()
root.configure(background='black')
width, height = root.winfo_screenwidth(),
root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (width, height))
label = tk.Label(root, text="Monitor Dashboard", bg='black',
fg='red')
label.pack(side='bottom', fill='x', anchor='se')
# Display portlet
displayMeteo(root)
# Loop the GUI manager
root.mainloop(0)
###############################
# MAIN SCRIPT BODY PART #
###############################
if __name__ == "__main__":
main()
And the class:
import pyowm
import tkinter as tk
from PIL import Image, ImageTk
class Meteo(tk.Label):
def __init__(self, parent=None, **params):
tk.Label.__init__(self, parent)
self.API = pyowm.OWM('API KEY', config_module=None,
language='it', subscription_type=None)
self.location = self.API.weather_at_place('Rome,IT')
self.weatherdata = self.location.get_weather()
self.weather = str(self.weatherdata.get_detailed_status())
self.dictionary = {'poche nuvole': 'Parzialmente Nuvoloso',
'cielo sereno': 'Sereno', 'nubi sparse': 'Nuvoloso',
'temporale con pioggia': 'Temporale', 'pioggia leggera':
'Pioggerella'}
self.Display()
def Temperatur(self):
self.Temp = tk.StringVar()
self.tempvalue = self.weatherdata.get_temperature('celsius')
self.temperature = str(self.tempvalue.get('temp'))
self.Temp.set(self.temperature)
self.after(3000, self.Temperatur)
def WeatherInfo(self):
self.Weather = tk.StringVar()
self.weather = str(self.weatherdata.get_detailed_status())
if self.weather in self.dictionary:
self.weather = self.dictionary[self.weather]
self.weatherstring = self.weather.title()
self.Weather.set(self.weatherstring)
self.after(3000, self.WeatherInfo)
def Display(self):
self.Temperatur()
self.WeatherInfo()
self.configure(text=self.Weather.get() + ", " + self.Temp.get() + "°", bg='black',
fg='#FFFF96', font=("arial, 35"))
self.after(3000, self.Display)
Now, what should happen is the widget updating every 3 secs (just to debug), although I know even if updating works it won't change every 3 seconds, 'cause you know...weather doesn't change every 3 secs.
As done in suggestion of Idlehands the problem was not in the Label or the updating.
The Code, if written that way, would call .get_weather only once, thus creating a stagnating variable. I added a method that updates the pyowm parsing and now evertyhing's working fine!
Related
I made a timer for part of a project I am making. I have the timer made, but I would like for the time left in the timer to be printed on to a label. ALso, if this wasn't expected, I would like for the time to be on the label then after a second it deletes itself and places the new time remaining(I do not want it to keep printing the time on a new line one after another).
One post I found that was pretty much what I wanted to do, but it did not work for me and I had to change some functions and add some new ones. I am not sure why this didn't work, but I would prefer it to be different because it has a preset time of 10 seconds and I would like for it to be the users choice. The link: Making a countdown timer with Python and Tkinter?
class Application(Frame):
def createWidgets(self):
# More code here
self.timeLeftLabel = Label(root, text='Time Left: ')
self.timeLeftLabel.pack()
def timeLeft(t):
time.sleep(1)
print(t)
def countdownInGUI():
countdown = Label(root, text=entryInt)
countdown.pack()
entryInt = IntVar()
t = Entry(root, textvariable=entryInt)
t.bind('<Return>', get)
t.pack(pady=5)
I am hoping that the time left will show in the label called countdown, but instead nothing shows up until the timer ends then it says "PY_VAR0" on a new line for each second (So its on 3 lines for 3 seconds, 4 lines for seconds, etc..)
In your func countdownInGUI, you created your Label widget by Label(root, text=entryInt), so tkinter will try to convert what you passed to a string. What you should do is to set entryInt as a textvariable instead.
On the other hand, you don't really need to set a textvariable for your Entry widget - you can retrieve the content directly by calling Entry.get().
Here is how everything could work base on your code:
import tkinter as tk
class Application(tk.Frame):
def __init__(self,master=None,**kwargs):
super().__init__(master,**kwargs)
self.master = master
self.timeLeftLabel = tk.Label(master, text='Time Left: ')
self.timeLeftLabel.pack()
self.entryInt = tk.StringVar()
self.countdown = tk.Label(master, textvariable=self.entryInt)
self.countdown.pack()
self.t = tk.Entry(master)
self.t.bind('<Return>', self.start_countdown)
self.t.pack(pady=5)
def start_countdown(self,event=None):
if self.t.get().isdigit():
self.time_left(int(self.t.get()))
def time_left(self, t):
self.entryInt.set(t)
t-=1
if t>=0:
self.master.after(1000,self.time_left, t)
else:
self.entryInt.set("Boom!")
root = tk.Tk()
frame = Application(root)
frame.pack()
root.mainloop()
I'm making a very simple program for class that involves multiplying the number of a GUI slider by another number of another GUI slider. But, for some reason when I run the program now, I get an AttributeError saying that 'gui' object has no attribute 'slider1'. Any ideas? Here's the code:
import tkinter
import random
class gui:
def __init__(self):
self.main_window = tkinter.Tk()
#widgets
self.__canvas = tkinter.Canvas(self.main_window,bg='white',width=300,height=10)
self.label = tkinter.Label(self.main_window,text=('Product:',0))
self.slider1 = tkinter.Scale(self.main_window,from_=0, to=12)
self.slider2 = tkinter.Scale(self.main_window,from_=0, to=12)
#packs
self.__canvas.pack()
self.label.pack(side='top')
self.slider1.pack(side='left')
self.slider2.pack(side='right')
self.button = tkinter.Button(self.main_window,text='Click to multiply',command=self.multiply())
self.button.pack(side='bottom')
tkinter.mainloop()
def multiply(self):
x = int(self.slider1.get())
y = int(self.slider2.get())
num = x*y
self.label.config(text=('Product:',num))
gui()
There is a few syntax error in the program, I commented those. As well as you should put orientations on the scales. Here is the code.
import tkinter as tk
class gui:
def __init__(self):
self.root = tk.Tk()
# the widgets
self.button = tk.Button(self.root, text="Multiply!", command=self.multiply)
# you need no '()' for the function when inputing it in tkinter.
self.label = tk.Label(self.root, text="Product: 0") # the '0 must be a string
self.sliderX = tk.Scale(self.root, from_=0, to=12, orient=tk.HORIZONTAL)
self.sliderY = tk.Scale(self.root, from_=0, to=12, orient=tk.VERTICAL)
# add an orient to the scales.
# now pack the widgets.
self.button.pack()
self.label.pack()
self.sliderX.pack()
self.sliderY.pack()
def multiply(self):
x = int(self.sliderX.get())
y = int(self.sliderY.get())
num = str(x * y) # need to turn the int to a string.
self.label.config(text="Product: "+num)
app = gui()
app.root.mainloop()
The reason it isn't working for you is because there is no instance of the program. This is what I do at the very end. Python's garbage collecting collects the instance made with gui() and so Tkinter can't reference an instance of the class.
My little brother is just getting into programming, and for his Science Fair project, he's doing a simulation of a flock of birds in the sky. He's gotten most of his code written, and it works nicely, but the birds need to move every moment.
Tkinter, however, hogs the time for its own event loop, and so his code won't run. Doing root.mainloop() runs, runs, and keeps running, and the only thing it runs is the event handlers.
Is there a way to have his code run alongside the mainloop (without multithreading, it's confusing and this should be kept simple), and if so, what is it?
Right now, he came up with an ugly hack, tying his move() function to <b1-motion>, so that as long as he holds the button down and wiggles the mouse, it works. But there's got to be a better way.
Use the after method on the Tk object:
from tkinter import *
root = Tk()
def task():
print("hello")
root.after(2000, task) # reschedule event in 2 seconds
root.after(2000, task)
root.mainloop()
Here's the declaration and documentation for the after method:
def after(self, ms, func=None, *args):
"""Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel."""
The solution posted by Bjorn results in a "RuntimeError: Calling Tcl from different appartment" message on my computer (RedHat Enterprise 5, python 2.6.1). Bjorn might not have gotten this message, since, according to one place I checked, mishandling threading with Tkinter is unpredictable and platform-dependent.
The problem seems to be that app.start() counts as a reference to Tk, since app contains Tk elements. I fixed this by replacing app.start() with a self.start() inside __init__. I also made it so that all Tk references are either inside the function that calls mainloop() or are inside functions that are called by the function that calls mainloop() (this is apparently critical to avoid the "different apartment" error).
Finally, I added a protocol handler with a callback, since without this the program exits with an error when the Tk window is closed by the user.
The revised code is as follows:
# Run tkinter code in another thread
import tkinter as tk
import threading
class App(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def callback(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.callback)
label = tk.Label(self.root, text="Hello World")
label.pack()
self.root.mainloop()
app = App()
print('Now we can continue running code while mainloop runs!')
for i in range(100000):
print(i)
When writing your own loop, as in the simulation (I assume), you need to call the update function which does what the mainloop does: updates the window with your changes, but you do it in your loop.
def task():
# do something
root.update()
while 1:
task()
Another option is to let tkinter execute on a separate thread. One way of doing it is like this:
import Tkinter
import threading
class MyTkApp(threading.Thread):
def __init__(self):
self.root=Tkinter.Tk()
self.s = Tkinter.StringVar()
self.s.set('Foo')
l = Tkinter.Label(self.root,textvariable=self.s)
l.pack()
threading.Thread.__init__(self)
def run(self):
self.root.mainloop()
app = MyTkApp()
app.start()
# Now the app should be running and the value shown on the label
# can be changed by changing the member variable s.
# Like this:
# app.s.set('Bar')
Be careful though, multithreaded programming is hard and it is really easy to shoot your self in the foot. For example you have to be careful when you change member variables of the sample class above so you don't interrupt with the event loop of Tkinter.
This is the first working version of what will be a GPS reader and data presenter. tkinter is a very fragile thing with way too few error messages. It does not put stuff up and does not tell why much of the time. Very difficult coming from a good WYSIWYG form developer. Anyway, this runs a small routine 10 times a second and presents the information on a form. Took a while to make it happen. When I tried a timer value of 0, the form never came up. My head now hurts! 10 or more times per second is good enough for me. I hope it helps someone else. Mike Morrow
import tkinter as tk
import time
def GetDateTime():
# Get current date and time in ISO8601
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return (time.strftime("%Y%m%d", time.gmtime()),
time.strftime("%H%M%S", time.gmtime()),
time.strftime("%Y%m%d", time.localtime()),
time.strftime("%H%M%S", time.localtime()))
class Application(tk.Frame):
def __init__(self, master):
fontsize = 12
textwidth = 9
tk.Frame.__init__(self, master)
self.pack()
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
text='Local Time').grid(row=0, column=0)
self.LocalDate = tk.StringVar()
self.LocalDate.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
textvariable=self.LocalDate).grid(row=0, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
text='Local Date').grid(row=1, column=0)
self.LocalTime = tk.StringVar()
self.LocalTime.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
textvariable=self.LocalTime).grid(row=1, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
text='GMT Time').grid(row=2, column=0)
self.nowGdate = tk.StringVar()
self.nowGdate.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
textvariable=self.nowGdate).grid(row=2, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
text='GMT Date').grid(row=3, column=0)
self.nowGtime = tk.StringVar()
self.nowGtime.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
textvariable=self.nowGtime).grid(row=3, column=1)
tk.Button(self, text='Exit', width = 10, bg = '#FF8080', command=root.destroy).grid(row=4, columnspan=2)
self.gettime()
pass
def gettime(self):
gdt, gtm, ldt, ltm = GetDateTime()
gdt = gdt[0:4] + '/' + gdt[4:6] + '/' + gdt[6:8]
gtm = gtm[0:2] + ':' + gtm[2:4] + ':' + gtm[4:6] + ' Z'
ldt = ldt[0:4] + '/' + ldt[4:6] + '/' + ldt[6:8]
ltm = ltm[0:2] + ':' + ltm[2:4] + ':' + ltm[4:6]
self.nowGtime.set(gdt)
self.nowGdate.set(gtm)
self.LocalTime.set(ldt)
self.LocalDate.set(ltm)
self.after(100, self.gettime)
#print (ltm) # Prove it is running this and the external code, too.
pass
root = tk.Tk()
root.wm_title('Temp Converter')
app = Application(master=root)
w = 200 # width for the Tk root
h = 125 # height for the Tk root
# get display screen width and height
ws = root.winfo_screenwidth() # width of the screen
hs = root.winfo_screenheight() # height of the screen
# calculate x and y coordinates for positioning the Tk root window
#centered
#x = (ws/2) - (w/2)
#y = (hs/2) - (h/2)
#right bottom corner (misfires in Win10 putting it too low. OK in Ubuntu)
x = ws - w
y = hs - h - 35 # -35 fixes it, more or less, for Win10
#set the dimensions of the screen and where it is placed
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
root.mainloop()
I wanted to change the text of my label according to the topic message to which my node subscribes. But the problem is that the text in the label is not changing with the change of topic message. A portion of my code is given below:(I used the code to dynamically change the text in the label from https://bytes.com/topic/python/answers/629499-dynamically-displaying-time-using-tkinter-label)
v = StringVar()
v.set(distance)
self.clock = Label(frame, font=('times', 20, 'bold'), bg='green', textvariable = v)
self.clock.pack(fill=BOTH, expand=1)
rate = rospy.Rate(2)
while not rospy.is_shutdown():
rospy.Subscriber("distance", Float32, self.callback)
v.set(distance)
print("distance = %f", distance)
frame.update_idletasks()
rate.sleep()
Well, your code (or at least the portion you posted) seems to be a messy one.
ROS talking,
define your subscriber in the initialization of the node (not inside
the while loop, that will create multiple subscribers instead!)
create a callback to get the messages exchanged under that topic read the message
update your label accordingly.
That being said, here's an example of how the code should look like : (fill in the blanks)
#!/usr/bin/env python
import rospy
from std_msgs.msg import Float32
def callback(data):
distance = data.data
rospy.loginfo(rospy.get_caller_id() + "distance = ", distance )
#here update your label, I assume the following (maybe not correct)
v = StringVar()
v.set(distance)
self.clock = Label(frame, font=('times', 20, 'bold'), bg='green', textvariable = v)
self.clock.pack(fill=BOTH, expand=1)
def listener():
rospy.init_node('listener', anonymous=True)
rospy.Subscriber("distance", Float32, self.callback)
# spin() simply keeps python from exiting until this node is stopped
rospy.spin()
if __name__ == '__main__':
listener()
Sharing an example ROS subscriber to read 1 sensor and display the result using tkinter and a Class. The display will update 1/second or whatever you set it to.
import tkinter as tk
from tkinter import ttk
import time
from random import randint
import rospy
from std_msgs.msg import Int16
"""
subscribes to a ROS topic (/front_angle_low_res) which in my case is outputing an integer result and displays the result in a tkinter window
credit: https://www.pythontutorial.net/tkinter/tkinter-after/
credit: https://www.youtube.com/watch?v=EAAd5vXA8lE&t=260s
before running make sure to $ rostopic echo /front_angle_low_res to make sure data is available
This script can be started from the folder it resides in with $ python3 sensor_display.py
"""
class Display_Sensor_1(tk.Tk):
def __init__(self):
super().__init__()
self.sub = rospy.Subscriber("/front_angle_low_res", Int16, self.callback_sensor_1)
self.sensor_1_data = tk.IntVar()
# configure the root window
self.title('Sensor 1 Data')
self.resizable(0, 0)
self.geometry('250x80')
self['bg'] = 'black'
# change the background color to black
self.style = ttk.Style(self)
self.style.configure('TLabel', background='black', foreground='red')
self.label = ttk.Label(self, text=self.get_sensor_data(), font=('Digital-7', 20))
self.label.pack(expand=True)
self.label.after(1000, self.update) # schedule an update every 1 second
def callback_sensor_1(self, data):
self.sensor_1_data = data.data
#print(self.sensor_1_data)
#rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
def get_sensor_data(self):
return self.sensor_1_data
def update(self):
""" update the label every 1 second """
self.label.configure(text=self.get_sensor_data())
self.label.after(1000, self.update) # schedule another timer
if __name__ == "__main__":
rospy.init_node('listener', anonymous=True)
sensor = Display_Sensor_1()
sensor.mainloop()
gist link
I have a Tkinter GUI having 2 entry fields, 2 buttons ( initialization of these not shown in code). There is one more button (initialized in code) which performs the main task of performing change detection on two images. Also there is a progress bar.
Now, when the task of change detection has been completed, I want to display the 4 images(pre, post, aligned, chng) returned by wave.changedetection() in a separate Tkinter window. I want the new window to come only after changedetection() has completed.(wave.py is my own file, not some module)
Unfortunately, if I try to add code to make new window, Tk.Toplevel() ,after the wave.changedetection() call, nothing happens and the main GUI window becomes unresponsive and has to be killed.
There is no way to know when the new created thread (start_thread)completes it's work, so that I can do Tk.Toplevel() there.
How can I do what I require?
class GUI(Tkinter.Tk):
def __init__(self, parent)
Tkinter.Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.button = Tkinter.Button(text = "Start")
self.button.bind('<Button-1>', self.OnButtonClick)
self.button.pack()
self.int = Tkinter.IntVar()
self.pgbar = Tkinter.ProgressBar(variable = self.int, mode = determinate)
def OnButtonClick(self,event):
#this func has been made since I have more buttons.It may seem redundant here
self.button['command'] = self.start_thread()
self.update_idletasks()
def start_thread(self):
self.int_var.set(1)
q = queue.Queue()
self.secondary_thread = threading.Thread(target = self.change)
self.secondary_thread.start()
self.after(50, self.check_queue, q)
def check_queue(self, q):
while True:
try:
x = wave.q.get_nowait()
except queue.Empty :
self.after(50,self.check_queue,q)
break
else:
self.int_var.set(x)
if x == 6:
self.button3['state'] = 'normal'
break
def change(self):
'''The 6 functions of wave.changedetection() change the value of self.int
due to which progress bar progresses.'''
pre, post, aligned, chng = wave.changedetection(self.entry_1.get(),
self.entry_2.get())
if __name__ == '__main__':
gui = GUI(None)
gui.mainloop()
code to update progress bar taken from here (2nd answer,Honest Abe's answer)
You have to be able to differentiate name spaces, i.e. this is in the main window and this is in the Toplevel. I would suggest that you get the Toplevels working first and then decide if you want to add threading or not. The code below is a simple example of creating Toplevels and shows how to place widgets in a specific name space (window in this case). You may or may not want a separate "create a Toplevel" class if there are functions you want to associate with each Toplevel's namespace. Also there are examples on the web on using Tkinter's "after" to update a progressbar. That is a different question so start another thread if you have questions about the progressbar.
try:
import Tkinter as tk ## Python 2.x
except ImportError:
import tkinter as tk ## Python 3.x
from functools import partial
class OpenToplevels():
""" open and close additional Toplevels with a button
"""
def __init__(self):
self.root = tk.Tk()
self.button_ctr=0
## in the "root" namespace *********************
but=tk.Button(self.root, text="Open a Toplevel",
command=self.open_another)
but.grid(row=0, column=0)
tk.Button(self.root, text="Exit Tkinter", bg="red",
command=self.root.quit).grid(row=1, column=0, sticky="we")
self.root.mainloop()
def close_it(self, id):
## destroy the window in this id's namespace ***********
id.destroy()
## id.withdraw()
## id.iconify()
def open_another(self):
self.button_ctr += 1
id = tk.Toplevel(self.root)
id.title("Toplevel #%d" % (self.button_ctr))
## in the "id for this Toplevel" namespace ***********
tk.Button(id, text="Close Toplevel #%d" % (self.button_ctr),
command=partial(self.close_it, id),
bg="orange", width=20).grid(row=1, column=0)
Ot=OpenToplevels()