tkinter multiprocessing-root window no responding while counting down - python

Currently, I am working on my course work project and there is a simple function I want to achieve. The program is mainly on tkinter, I want the label shows up for 3 seconds, hide for 7 seconds for one period, and in the next period the text in label should change; while the label changes I am trying to disable an entry box from the first 3 seconds then normalise it.
Countdown and change of label text were alright, but the entry box does not respond at all when it is normalised.
here is my code
def c_time():
from tkinter import *
import time
root=Tk()
en = Entry(root)
en.pack(side=TOP)
en.focus_force()
la = Label(root, text='6666')
la.pack(side=BOTTOM)
li = ['a', 'b','c','d']
for i in li:
la.config(text=i)
root.update()
def la_diappear():
root.after(3000)
la.pack_forget()
root.update()
def la_appear():
root.after(7000)
la.pack()
la_diappear()
la_appear()
root.mainloop()
c_time()
Both root.after and time.sleep methods were tried
and I tried multiprocessing when I reached information about GIL in python:
from multiprocessing import Process
import time
from tkinter import *
def count_down():
global total
total = 5
for i in range(total):
time.sleep(1)
total -= 1
print(total)
def tkwindow():
root=Tk()
en = Entry(root)
en.pack(side=TOP)
en.focus_force()
la = Label(root, text='6666')
la.pack(side=BOTTOM)
li = ['a', 'b','c','d']
for i in li:
la.config(text=i)
root.update()
count_down()
if total == 3:
la.pack_forget()
root.update()
if total == 5:
la.pack()
root.mainloop()
if __name__ == "__main__":
a = Process(target=count_down)
b = Process(target=tkwindow)
b.start()
the code above should be work straight away.
plz reply if any thought related
Thank you very much.

You can wrap the for loop in a function, and thread that function. Then you can also use time.sleep without blocking the main thread.
from tkinter import *
from threading import Thread
import time
root=Tk()
en = Entry(root)
en.pack(side=TOP)
en.focus_force()
la = Label(root, text='6666')
la.pack(side=BOTTOM)
def la_diappear():
la.pack_forget()
def la_appear():
la.pack()
def actions():
li = ['a', 'b','c','d']
for i in li:
la.config(text=i)
time.sleep(3)
la_diappear()
time.sleep(7)
la_appear()
t = Thread(target=actions)
t.start()
root.mainloop()

Related

Python tkinter image can't change immediately?

My code:
from tkinter import *
import random
from PIL import ImageTk, Image
import time
class App():
def __init__(self, root, dice_image):
self.dice_label = Label(root, image=dice_image)
self.dice_label.pack()
self.button = Button(root, text="Roll", font=("Fixedsys", 25) , width=20, height=20, bg="red" , command=rolling)
self.button.pack(pady=70)
I want to make the image change fast to make it look like rolling. But I don't know why it change nothing before the for loop end.
def rolling():
global dices
for i in range(1, 10 + 1):
time.sleep(0.3)
random.shuffle(dices)
app.dice_label.configure(image=dices[0])
if __name__ == '__main__':
root = Tk()
root.geometry("800x600")
# Var
dice_image = ImageTk.PhotoImage(Image.open("Dices.jpg"))
dice1 = ImageTk.PhotoImage(Image.open("Dice1.jpg"))
dice2 = ImageTk.PhotoImage(Image.open("Dice2.jpg"))
dice3 = ImageTk.PhotoImage(Image.open("Dice3.jpg"))
dice4 = ImageTk.PhotoImage(Image.open("Dice4.jpg"))
dice5 = ImageTk.PhotoImage(Image.open("Dice5.jpg"))
dice6 = ImageTk.PhotoImage(Image.open("Dice6.jpg"))
dices = [dice1, dice2, dice3, dice4, dice5, dice6]
app = App(root, dice_image)
root.mainloop()
Tkinter and sleep() are not friends. The problem is that Tkinter functions inside of a mainloop and sleep pauses that loop until all the sleep time is over.
This will always freeze your application.
Try using after() and a refactored function instead.
I have not tested this but I believe something like this should fix your problem or at least get rid of the problem that sleep() will cause.
I have changed your button command to send a call to the function using lambda so that the call wont go out at runtime but only when pressing the button.
from tkinter import *
import random
from PIL import ImageTk, Image
class App():
def __init__(self, root, dice_image):
self.dice_label = Label(root, image=dice_image)
self.dice_label.pack()
self.button = Button(root, text="Roll", font=("Fixedsys", 25),
width=20, height=20, bg="red" ,
command=lambda: rolling(1,11))
self.button.pack(pady=70)
I have updated your rolling function to handly a timed loop.
root.after(300, lambda: rolling(stop, counter)) will call the function it is in only if the counter has not finished counting down and only once every 0.3 seconds. Again lambda is used here to make sure the call to rolling does not happen at runtime.
def rolling(s, e, start=False):
global dices
counter = 0
stop = 0
if start:
counter = s
stop = e
if counter > stop:
random.shuffle(dices)
app.dice_label.configure(image=dices[0])
counter -= 1
root.after(300, lambda: rolling(stop, counter))
I have also updated this portion to use a loop so you don't have to repeat yourself when crating a list of images.
if __name__ == '__main__':
root = Tk()
root.geometry("800x600")
dice_image = ImageTk.PhotoImage(Image.open("Dices.jpg"))
dices = []
for i in range(6):
dices.append(ImageTk.PhotoImage(Image.open(f"Dice{i+1}.jpg")))
app = App(root, dice_image)
root.mainloop()

Is there a way to update label in real-time in tkinter?

With the Help of Tkinter, I am trying to print a single word at a time(with an interval of 2-sec sleep)
I tried the following code, It doesn't work as I want it to do.
My code is printing the whole string stacked upon one another.
After n*len(words) sleep seconds are passed.
I am trying to print ONLY one word at a time(with an interval of 2-sec)
from tkinter import *
from time import sleep
root = Tk()
words = 'Hey there, This is python3'.split()
l = Label(root, text='')
for w in range(len(words)):
sleep(2)
l = Label(root,text = words[w])
#l['text'] = words[w] # this is what I tried
l.pack()
root.mainloop()
I tried above-commented statement, thought this might update but didn't work at all as I expected.
Take a look at this example:
from tkinter import *
root = Tk()
words = 'Hey there, This is python3'.split()
l = Label(root) #creating empty label without text
l.pack()
w = 0 #index number
def call():
global w
if w <= len(words)-1: #if it is in the range of the list
l.config(text=words[w]) #change the text to the corresponding index element
w += 1 #increase index number
root.after(2000,call) #repeat the function after 2 seconds
else:
print('Done') # if out of index range, then dont repeat
call() #call the function initially
root.mainloop()
I've commented the code to understand better.
The method using after() calls the function repeatedly, which may decrease its efficiency. So alternatively you can also use, threading to start a new thread that will not make the GUI freeze while sleep():
from tkinter import *
from time import sleep
import threading #import the library
root = Tk()
words = 'Hey there, This is python3'.split()
l = Label(root) #empty label
l.pack() #pack()
def call():
for w in words: #loop through the list
l.config(text=w) #update label with each word over each iteration
sleep(2) #sleep for 2 seconds
threading.Thread(target=call).start() #create a separate thread to not freeze the GUI
root.mainloop()
Simple answer that uses threading, since time.sleep(2) will make the whole tkinter wait some seconds before actually showing the window.
from tkinter import *
import time
from threading import Thread
root = Tk()
words = 'Hey there, This is python3'.split()
l = Label(root, text='')
l.pack()
def show_words():
for word in words:
time.sleep(2)
l.configure(text=word)
thread = Thread(target = show_words)
thread.start()
root.mainloop()
well you first need to take the mainloop() out of the for loof since it just send the command to run the root again hence only first word is printed . I used a timer and a new screen as well (Note:you can do the same stuff on root as well ) ,I started the timer and and send a command to run the def after a perticular time from the current time.I hope that helped you.
from tkinter import *
import threading
from time import sleep
root = Tk()
words = 'Hey there, This is python3'.split()
l = Label(root, text='')
print(words);
def newWindow():
global newScreen
newScreen=Toplevel()
newScreen.title("Rahul Screen")
newScreen.geometry("300x300+15+15")
newScreen.resizable(0,0)
l=Label(newScreen,text='')
for w in range(len(words)):
print(w);
sleep(2)
l = Label(newScreen,text = words[w])
l.pack()
start_time = threading.Timer(2,newWindow)
start_time.start()
root.mainloop()

Continous update of sensor data using while loop in python and tkinter

I am using NI instrument to read data and display it on GUI. Have used tkinter. But could not find a way to update the data using while loop.
import nidaqmx
import time
from tkinter import *
master = Tk()
while True:
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
print('1 Channel 1 Sample Read: ')
data = task.read()
sensor_value ='%.2f' % data #said sensor value
master.minsize(width=400, height=400)
w = Label(master, text=sensor_value) #shows as text in the window
w.pack() #organizes widgets in blocks before placing them in the parent.
time.sleep(5)
mainloop()
When working with Tkinter we should avoid Threading, while loop with root.update() it is not like we can't use them but not advisable instead use after(delay_ms, callback=None, *args) method provided by Tkinter itself for a reason.
Now to your code, there are few issues in your code.
In while loop you are creating a Label every 5 secs instead create one label and update the value of that label inside the loop with w['text'] = '...' or w.configure(text='...').
Don't put mainloop inside the loop, instead call it in the last line with the instance of the main window in your case master (master.mainloop()).
Same with master.minsize(width=400, height=400), you don't have to tell the master window every 5 sec to set the minimum size to 400x400 it should be called once if not decide to change the minimum size to different geomentry.
Your code should look like this.
import nidaqmx
from tkinter import *
master = Tk()
master.minsize(width=400, height=400)
w = Label(master) #shows as text in the window
w.pack() #organizes widgets in blocks before placing them in the parent.
def run():
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
print('1 Channel 1 Sample Read: ')
data = task.read()
w['text'] = '%.2f' % data #said sensor value
master.after(5000, run)
run() # run the function once.
master.mainloop()
This should be the right way of doing this and as i couldn't run your code if anything doesn't work, let me know otherwise.
try this code:
import time
from tkinter import *
import nidaqmx
master = Tk()
def test():
while True:
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
print('1 Channel 1 Sample Read: ')
data = task.read()
sensor_value = '%.2f' % data # said sensor value
w['text'] = sensor_value
master.update()
time.sleep(2)
w = Label(master)
w.pack()
btn = Button(text="Start read from sensor", command=test)
btn.pack()
mainloop()
You'll likely need a second thread to poll the sensor.
import nidaqmx
import time
import threading
from tkinter import *
stop_signal = threading.Event()
def read_loop():
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
while True:
data = task.read()
label["text"] = "%.2f" % data
# Wait for the signal, or 5 seconds
if stop_signal.wait(timeout=5):
break # If the signal was set, break
# Build window
master = Tk()
master.minsize(width=400, height=400)
label = Label(master, text="")
label.pack()
# Set up & start reading thread
threading.Thread(target=read_loop).start()
try:
# Enter Tk main loop
mainloop()
finally:
# Clean up afterwards
stop_signal.set()
Keep in mind that mainloop() is blocking, so time.sleep() is useless as you won't get back to the task.read() line.
Consider using the Thread module from the threading lib importing it like this:
from threading import Thread
or using a button to refresh the value as someone else suggested you.

Python Set Button Text While busy

I'm new to python and I am trying to create a program but I can't even get the basics right. I have a button app that looks like this:
#simple GUI
from tkinter import *
import time
#create the window
root = Tk()
#modify root window
root.title("Button Example")
root.geometry("200x50")
button1state = 0
def start():
count = 0
button1["text"] ="Busy!"
while (count < 5):
root.after(1000)
count = count + 1
def button1clicked():
global button1state
if button1state == 0:
start()
button1["text"] ="On!"
button1state = 1
else:
button1["text"] ="Off!"
button1state = 0
app = Frame(root)
app.pack()
button1 = Button(app, text ="Off!", command = button1clicked)
button1.pack()
#kick off the event loop
root.mainloop()
Now everything works except it doesn't change the button text to busy while
**start()** is called. How can I fix this? Once I've got it working I want to use images to show the user that its OFF ON and BUSY. Please help me
You need to force the GUI to update before starting the task:
def start():
count = 0
button1.configure(text="Busy!")
root.update() # <-- update window
while (count < 5):
root.after(1000)
count = count + 1
But if you don't want your GUI to be frozen while the task is executed, you will need to use a thread as Dedi suggested.
You have to make a thread in order to make you function as a "background event" while your interface is working. Consider using that :
from threading import Thread
and then :
my_thread=Thread(target=start())
my_thread.start()
Where the first "start()" is the name of your function and the second one a call for the thread to begin.

Python tkinter GUI freezing/crashing

from Tkinter import *
import tkFileDialog
import tkMessageBox
import os
import ttk
import serial
import timeit
import time
######################################################################################
class MyApp:
def __init__(self, parent):
########################################################
#Setup Frames
self.MiddleFrame = Frame(parent) #Middle Frame
self.MiddleFrame.pack()
#GLOBAL VARIABLES
self.chip_number = 0 #number of chip testing
###########################################
#Middle Frame setup
Label(self.MiddleFrame, text='Done').grid(row=8, column=1, sticky = E)
self.Done = Canvas(self.MiddleFrame, bg="yellow", width=10, height=10)
self.Done.grid(row=8, column=2)
Label(self.MiddleFrame, text='Chip Number:').grid(row=9, column=1, sticky = E)
#start button
self.button1 = Button(self.MiddleFrame,state=NORMAL, command= self.start_pre)
self.button1["text"]= "START"
self.button1.grid(row=1, column=2, sticky = E)
###########################################
#Action of Start Button
def start_pre(self):
x = 0
while x<10000:
self.start_button()
x=x+1
#Talking to Board
def start_button(self):
#increase chip count number and update
self.chip_number += 1
Label(self.MiddleFrame, text=str(self.chip_number)).grid(row=9, column=2, sticky = E)
#reset-yellow
self.reset_color()
print "Still Working", self.chip_number
self.Done.configure(background="green")
self.Done.update_idletasks()
###############################################################
#Color Boxes
#Reset
def reset_color(self):
self.Done.configure(background="yellow")
self.Done.update_idletasks()
###############################################################################################################
#Start Programs
root = Tk() #makes window
root.title("Interface")
myapp = MyApp(root) #this really runs program
root.mainloop() #keep window open
With my program, i first push the start button.
I will print "still working" and the GUi will update chip number and blink done light over and over. The start button go to function that will execute 10000 times. However after 3000 iterations, the gui freeze, but the program is still print "still working". How do I keep the gui from crashing?
There are many problems with your code. For one, this is fundamentally flawed:
while self.stop == True:
self.start_button()
time.sleep(0.5)
You simply can't expect a GUI to behave properly with code like that. As a general rule of thumb you should never have the main thread of a GUI call sleep. Causing sleep prevents the event loop from processing any events, including low level events such as requests to refresh the screen.
The use of sleep has been asked and answered many times on stackoverflow. You might find some of those questions useful. For example,
windows thinks tkinter is not responding
Python Tkinter coords function not moving canvas objects inside loop
How do widgets update in Tkinter?
Tkinter multiple operations
Python Tkinter Stopwatch Error
You have another problem that falls into the category of a memory leak. From that while loop, you call self.start_button() indefinitely. This happens about once a second, due to sleep being called for half a second in the loop, and another half a second in start_button.
Each time you call start_button, you create another label widget that you stack on top of all previous widgets in row 9, column 2. Eventually this will cause your program to crash. I'm surprised that it causes your program to fail so quickly, but that's beside the point.
My recommendation is to start over with a simple example that does nothing but update a label every second. Get that working so that you understand the basic mechanism. Then, once it's working, you can add in your code that reads from the serial port.
May I suggest that you start over with the following code? You can port in back to Python 2 if needed, but your program has been rewritten to use Python 3 and has been designed to use tkinter's ability to schedule future events with the after methods. Hopefully, you will find the code easier to follow.
import collections
import timeit
import tkinter
def main():
root = Application()
root.setup()
root.mainloop()
class Application(tkinter.Tk):
def setup(self):
mf = self.__middle_frame = tkinter.Frame(self)
self.__middle_frame.grid()
bf = self.__bot_frame = tkinter.Frame(self)
self.__bot_frame.grid()
self.__port_set = False
self.__chip_number = 0
self.__chip_pass_num = 0
self.__chip_fail_num = 0
self.__chip_yield_num = 0
self.__stop = True
self.__widgets = collections.OrderedDict((
('COT', 'Continuity Test'), ('CHE', 'Chip Erase'),
('ERT', 'Erase Test'), ('WRT', 'Write Test'),
('WIRT', 'Wire Reading Test'), ('WIT', 'Wire Reading Test'),
('WRAT', 'Write All Test'), ('DO', 'Done')))
for row, (key, value) in enumerate(self.__widgets.items()):
label = tkinter.Label(mf, text=value+':')
label.grid(row=row, column=0, sticky=tkinter.E)
canvas = tkinter.Canvas(mf, bg='yellow', width=10, height=10)
canvas.grid(row=row, column=1)
self.__widgets[key] = label, canvas
self.__cn = tkinter.Label(mf, text='Chip Number:')
self.__cn.grid(row=8, column=0, sticky=tkinter.E)
self.__display = tkinter.Label(mf)
self.__display.grid(row=8, column=1, sticky=tkinter.E)
self.__button = tkinter.Button(bf, text='START',
command=self.__start_pre)
self.__button.grid(sticky=tkinter.E)
def __start_pre(self):
self.__button['state'] = tkinter.DISABLED
self.__start_button(0)
def __start_button(self, count):
if count < 100:
self.__chip_number += 1
self.__display['text'] = str(self.__chip_number)
self.__widgets['DO'][1]['bg'] = 'yellow'
start_time = timeit.default_timer()
print('Still Working:', self.__chip_number)
self.after(500, self.__end_button, count)
else:
self.__button['state'] = tkinter.NORMAL
def __end_button(self, count):
self.__widgets['DO'][1]['bg'] = 'green'
self.after(500, self.__start_button, count + 1)
if __name__ == '__main__':
main()

Categories

Resources