Digital clock display - multithreading required? - python

Situation
I have the following Tkinter window:
And in the blank space on the right:
I want to be able to continuously update the time, like on a digital clock.
I will take the present time using:
time.strftime('%H:%<:%S')
I think this involves MultiThreading. But please tell me if there is some other way to do this.
The two white areas are input areas for the user.
Please note that the text will be entered in these fields. I don't want that to be affected.
What I think is that the function that will make the time to change after each second, will run on a different thread than the one that includes the text boxes.
The value of time will be in a Label:
a = Label(root,text=time.strftime('%H:%M:%S'))
a.grid(row=3,column=1)
Please give me the code for this function and also for the multithreading.
Please help me with this issue.

You don't need multithreading.
...
a = Label(root, text=time.strftime('%H:%M:%S'))
def update_time():
a['text'] = time.strftime('%H:%M:%S')
root.after(1000, update_time)
root.after(1000, update_time)
a.grid(row=3, column=1)
...

Related

Can You Call '.after' Twice in a Function?

I am asking a question in good faith here. I've had a lot of trouble on StackOverflow; I know this is probably Googleable, but I lack the prerequisite knowledge to do so. Please keep that in mind and be kind.
The Overall Goal
I am creating a digital clock which functions normally for five minutes, and then accelerates rapidly for two minutes. Then, it will freeze for a certain amount of time, show the correct time, and the program repeats.
Immediate Goal
I need to call my faketime function from within my time function. When I add the second .after, I get an IndentationError: unindent does not match any outer indentation level. As my indentation appears to fine, I think the issue is elsewhere. Can use you use .after twice in a function? If not, how can I accomplish this? Thank you!
Code
from tkinter import *
from tkinter.ttk import *
# importing strftime function to
# retrieve system's time
from time import strftime
# creating tkinter window
root = Tk()
root.title('Clock')
# This function is used to
# display time on the label
def newtime():
faketime = "Test"
lbl.config(text=faketime)
lbl.after(1000, time)
def time():
string = strftime('%H:%M:%S')
lbl.config(text=string)
lbl.after(1000, time)
lbl.after(300000, newtime())
# Styling the label widget so that clock
# will look more attractive
lbl = Label(root, font=('calibri', 40, 'bold'),
background='black',
foreground='red')
# Placing clock at the centre
# of the tkinter window
lbl.pack(anchor='center')
time()
mainloop()
Line 18 should be lbl.after(1000, newtime) instead of lbl.after(1000, time)
Comment out line 25 #lbl.after(300000, newtime())
HAPPY NEW YEARS!
Output image:

Real Time Data with tkinter

I am looking for a simple way to display changing real time data in a GUI in python. I am connected to 2 devices and want to display data constantly (like 20 different values), and when I press a button I want to control the one device.
Unfortunately I fail already with the display of the data. For this I have looked at some tkinter tutorials and explanations.
My idea was to implement it with a config function and to overwrite the label continuously. As example how I wanted to display one value:
import tkinter as tk
from pydualsense import pydualsense
# connect to the device
dualsense = pydualsense()
dualsense.init()
# create a window
window = tk.Tk()
# function for updating data
def show_data():
global dualsense
data_label_output.config(text=dualsense.state.LX)
# showing the data as a lable
data_label_output = tk.Label(window)
data_label_output.grid(row=1, column=1)
show_data()
#### or different solution
# showing the data as a lable
data_label_output = tk.Label(window, comand=show_data)
data_label_output.grid(row=1, column=1)
window.mainloop()
Unfortunately, the value is displayed only once at the beginning and nothing changes after that.
Another problem:
When I press the button, I want to be able to control the one device. For this I have a while True loop that permanently checks if a button is pressed and then executes actions. As a separate program no problem, but how do I integrate this into the tkinter GUI? When I start this PyCharm always crashes.
I use PyCharm and Python 3.8
About simple and functional ideas I would be happy, also to other tools/modules etc., as long as you can easily and quickly implement the idea. It's only for a research project and the programming is only a means to an end.
You can use the after method in tkinter to run something after a short delay. The following code will run show_data once the GUI is ready and then again every 1000 milliseconds.
import tkinter as tk
from pydualsense import pydualsense
# connect to the device
dualsense = pydualsense()
dualsense.init()
# create a window
window = tk.Tk()
# function for updating data
def show_data():
global dualsense
data_label_output.config(text=dualsense.state.LX)
window.after(1000,show_data)
# showing the data as a lable
data_label_output = tk.Label(window)
data_label_output.grid(row=1, column=1)
window.after_idle(show_data)
window.mainloop()
This resolves the updating issue, I'm not sure what behaviour you want when you press the button but if you elaborate and explain, I might be able to help and update this answer.

Python: Is it possible to create an tkinter label which has a dynamic string when a function is running in background?

I have created a tkinter GUI for my python script. When I run the script, I want a dynamic string in one of the Label widgets on the GUI window, which will display:
"Working."
Then:
"Working.."
then
"Working..."
and then start from "Working." again until the script is completed.
(Actually I'd prefer a progress bar in this area)
Is it possible?
I wrote two simple scripts to help demonstrate how to do what you want. The first is using the label:
import tkinter as tk
root = tk.Tk()
status = tk.Label(root, text="Working")
status.grid()
def update_status():
# Get the current message
current_status = status["text"]
# If the message is "Working...", start over with "Working"
if current_status.endswith("..."): current_status = "Working"
# If not, then just add a "." on the end
else: current_status += "."
# Update the message
status["text"] = current_status
# After 1 second, update the status
root.after(1000, update_status)
# Launch the status message after 1 millisecond (when the window is loaded)
root.after(1, update_status)
root.mainloop()
The next one is using a progressbar:
import tkinter as tk
# You will need the ttk module for this
from tkinter import ttk
def update_status(step):
# Step here is how much to increment the progressbar by.
# It is in relation to the progressbar's length.
# Since I made the length 100 and I am increasing by 10 each time,
# there will be 10 times it increases before it restarts
progress.step(step)
# You can call 'update_status' whenever you want in your script
# to increase the progressbar by whatever amount you want.
root.after(1000, lambda: update_status(10))
root = tk.Tk()
progress = ttk.Progressbar(root, length=100)
progress.pack()
progress.after(1, lambda: update_status(10))
root.mainloop()
Note however that I couldn't do too much with the progressbar script because progressbars are a little tricky and need to be customized to your script exactly. I just wrote it to maybe shed a little light on the subject. The main part of my answer though is the label script.
Yes, it is possible. There are two ways to do it:
Whenever you want to update the label from your code you can call the_widget.configure(the_text). This will change the text of the label.
You can create an instance of a tkinter.StringVar, and assign it to the textvariable attribute of a label. Whenever you change the value of the variable (via the_variable.set(the_text), the label will automatically update.
Note that for either of these to work, the event loop needs to be able to process events (ie: you won't see anything if your function takes a long time to run and you never call update_idletasks or re-enter the event loop).

Python tkinter .pack/.pack_forget memory issue

I've been teaching myself Python for a few months now and have proceed into learning some GUI techniques.
I wrote this simple script based off a pack_remove example I found within a book. My script simply displays local and UTC time every second. Granted the only difference is the hour, I would still like to redisplay every second.
The script works, yet my RAM is consistently increasing with every time display. I start out with around 4mb then after 2 hours or so the script uses 25mb. This makes some sense to me, but I was curious if there was a way display new times every second, but reduce the memory usage of such a simple clock display.
Or am I using an inefficient technique to re-display data in a GUI at a high frequency?
Here is my code:
from tkinter import *
import time
class TimeDisplay(Frame):
def __init__(self,msecs = 1000):
Frame.__init__(self)
self.msecs = msecs
self.pack()
utc_time = Label(self, text='')
utc_time.pack()
cst_time = Label(self, text='')
cst_time.pack()
self.utc_time = utc_time
self.cst_time = cst_time
self.repeater()
def repeater(self):
self.utc_time.pack_forget()
self.cst_time.pack_forget()
self.utc_time = Label(self, text= 'UTC: ' + time.strftime('%Y/%m/%d %H:%M:%S',time.gmtime()))
self.utc_time.pack()
self.utc_time.config(bg='navy',fg='white')
self.cst_time = Label(self, text= 'CST: ' + time.strftime('%Y/%m/%d %H:%M:%S',time.localtime()))
self.cst_time.pack()
self.cst_time.config(bg='navy',fg='white')
self.after(self.msecs, self.repeater)
if __name__ == '__main__': TimeDisplay(msecs=1000).mainloop()
Thanks in advance
pack_forget doesn't destroy anything, it just makes it non-visible. This is a GUI version of a memory leak -- you keep creating objects without ever destroying them.
So, the first lesson to learn is that you should destroy a widget when you are done with it.
The more important lesson to learn is that you don't have to keep destroying and recreating the same widget over and over. You can change the text that is displayed with the configure method. For example:
self.utc_time.configure(text="...")
This will make your program not use any extra memory, and even use (imperceptibly) less CPU.
To actually free widget's memory you should also call it's .destroy() method. This prevents memory leaking in your case.
However a more efficient way to implement the stuff is to associate string variable with Label widget like this:
v = StringVar()
Label(master, textvariable=v).pack()
v.set("New Text!")
see http://effbot.org/tkinterbook/label.htm for reference

How to run a function in the background of tkinter [duplicate]

This question already has an answer here:
Tkinter locks Python when an icon is loaded and tk.mainloop is in a thread
(1 answer)
Closed 7 months ago.
I am new to GUI programming and I want to write a Python program with tkinter. All I want it to do is run a simple function in the background that can be influenced through the GUI.
The function counts from 0 to infinity until a button is pressed. At least that is what I want it to do. But I have no idea how I can run this function in the background, because the mainloop() of tkinter has control all the time. And if I start the function in an endless loop, the mainloop() cannot be executed and the GUI is dead.
I would like to return control back to the mainloop() after each cycle, but how can I get the control back from the mainloop() to the runapp-function without a user-triggered event?
Here is some sample code that kills the GUI:
from Tkinter import *
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button = Button(frame, text="START", command=self.runapp)
self.button.pack(side=LEFT)
self.hi_there = Button(frame, text="RESTART", command=self.restart)
self.hi_there.pack(side=LEFT)
self.runapp()
def restart(self):
print "Now we are restarting..."
def runapp(self):
counter = 0
while (1):
counter =+ 1
time.sleep(0.1)
Event based programming is conceptually simple. Just imagine that at the end of your program file is a simple infinite loop:
while <we have not been told to exit>:
<pull an event off of the queue>
<process the event>
So, all you need to do to run some small task continually is break it down into bite-sized pieces and place those pieces on the event queue. Each time through the loop the next iteration of your calculation will be performed automatically.
You can place objects on the event queue with the after method. So, create a method that increments the number, then reschedules itself to run a few milliseconds later. It would look something like:
def add_one(self):
self.counter += 1
self.after(1000, self.add_one)
The above will update the counter once a second. When your program initializes you call it once, and from then after it causes itself to be called again and again, etc.
This method only works if you can break your large problem (in your case "count forever") into small steps ("add one"). If you are doing something like a slow database query or huge computation this technique won't necessarily work.
You will find the answer in this other question Tkinter locks python when Icon loaded and tk.mainloop in a thread.
In a nutshell, you need to have two threads, one for tkinter and one for the background task.
Try to understand this example : clock updating in backgroud, and updating GUI ( no need for 2 threads ).
# use Tkinter to show a digital clock
# tested with Python24 vegaseat 10sep2006
from Tkinter import *
import time
root = Tk()
time1 = ''
clock = Label(root, font=('times', 20, 'bold'), bg='green')
clock.pack(fill=BOTH, expand=1)
def tick():
global time1
# get the current local time from the PC
time2 = time.strftime('%H:%M:%S')
# if time string has changed, update it
if time2 != time1:
time1 = time2
clock.config(text=time2)
# calls itself every 200 milliseconds
# to update the time display as needed
# could use >200 ms, but display gets jerky
clock.after(200, tick)
tick()
root.mainloop( )
credits: link to site
I don't have sufficient reputation to comment on Bryan Oakley's answer (which I found to be very effective in my program), so I'll add my experience here. I've found that depending on how long your background function takes to run, and how precise you want the time interval to be, it can be better to put self.after call at the beginning of the recurring function. In Bryan's example, that would look like
def add_one(self):
self.after(1000, self.add_one)
self.counter += 1
Doing it this way ensures that the interval of time is respected exactly, negating any interval drift that might occur if your function takes a long time.
If you don't want to be away from those threads, I would like to give one suggestion for your GUI-
Place the function for your GUI just before the root.mainloop() statement.
Example-
root = tk.Tk()
.
.
graphicsfunction() #function for triggering the graphics or any other background
#function
root.mainloop()
Please up vote if you like.

Categories

Resources