Show new info in root of Tkinter waiting time as a multiprocess - python

In python, i want to add the Message 2 to the Tkinter window if a condition is given. In this example I use value of X (time) as a multiprocess, but once in the loop of the Tkinter root I cant give the new Message (2).
from tkinter import *
import time as t
from threading import Thread
def time1():
global x
x = 0
while x<3:
t.sleep(1)
x += 1
def gui():
root = Tk()
Label(root, text=("Message 1")).pack()
if x == 2:
Label(root, text=("Message 2")).pack()
root.mainloop()
generator = Thread(target=time1)
processor1 = Thread(target=gui)
generator.start()
processor1.start()
generator.join()

In your gui function, you are only testing for the value of x once, just before entering the mainloop. If x becomes 2 later, nothing will happen.
Try this instead:
import tkinter as tk
import time
from threading import Thread
def time1():
"""
Add a second Label after a certain condition is met.
"""
x = 0
while x < 3:
time.sleep(1)
x += 1
tk.Label(root, text="Message 2").pack()
if __name__ == "__main__":
root = tk.Tk()
tk.Label(root, text="Message 1").pack()
generator = Thread(target=time1)
generator.start()
root.mainloop()
From the Python tutorial:
Note that in general the practice of importing * from a module or package is frowned upon, since it often causes poorly readable code. However, it is okay to use it to save typing in interactive sessions.
It is considered good practice with tkinter that the GUI is run from the main thread. This is to take ensure that everything works if tkinter is not built with multithreading support.

Related

Tkinter crash in python

I was trying to make a stopwatch in python but every time it stops working beacause of overflow, can someone please fix this??
Code:
import time
from tkinter import *
cur=time.time()
root = Tk()
def functio():
while True:
s = time.time()-cur
l1 = Label(root,text=s)
l1.pack()
l1.destroy()
time.sleep(0.5)
Button(root,text='Start',command=functio).pack()
root.mainloop()
The while loop will block tkinter mainloop from handling pending events, use after() instead.
Also it is better to create the label once outside the function and update it inside the function:
import time
# avoid using wildcard import
import tkinter as tk
cur = time.time()
root = tk.Tk()
def functio():
# update label text
l1['text'] = round(time.time()-cur, 4)
# use after() instead of while loop and time.sleep()
l1.after(500, functio)
tk.Button(root, text='Start', command=functio).pack()
# create the label first
l1 = tk.Label(root)
l1.pack()
root.mainloop()
Note that wildcard import is not recommended.
Flow of execution can never exit your endless while-loop. It will endlessly block the UI, since flow of execution can never return to tkinter's main loop.
You'll want to change your "functio" function to:
def functio():
s = time.time()-cur
l1 = Label(root,text=s)
l1.pack()
l1.destroy()
root.after(500, functio)
That being said, I'm not sure this function makes much sense: You create a widget, and then immediately destroy it?
You'll want to do something like this instead:
import time
from tkinter import *
root = Tk()
def functio():
global timerStarted
global cur
# check if we started the timer already and
# start it if we didn't
if not timerStarted:
cur = time.time()
timerStarted = True
s = round(time.time()-cur, 1)
# config text of the label
l1.config(text=s)
# use after() instead of sleep() like Paul already noted
root.after(500, functio)
timerStarted = False
Button(root, text='Start', command=functio).pack()
l1 = Label(root, text='0')
l1.pack()
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.

tkinter multiprocessing-root window no responding while counting down

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()

Tkinter does not update variable

I apologize if this is a redundant post, but I have spent a few hours sniffing out solutions around StackOverflow and other sources, this is where I ended up. AFAIK it should work.
Put simply, I can't fathom why my variable doesn't update more than once. When I run this program, it starts at '0.0' and updates to a realistic value (after time.sleep(5)), but only once. Theoretically it should update in real-time...
I realize you can't recreate my dev environment with my exact Modbus slave device, etc etc, but flow_unpack is a "good" variable because it prints to the Tkinter window once. If it was bad, it would return a ValueError or something. The problem is: why isn't it updating?
import tkinter as tk
import time
import minimalmodbus
import serial
import struct
root = tk.Tk()
root.resizable(width=False, height=False)
root.geometry('{}x{}'.format(500, 500))
i = minimalmodbus.Instrument('/dev/ttyUSB0', 1)
i.serial.baudrate = 9600
i.serial.bytesize = 8
i.serial.parity = serial.PARITY_ODD
i.serial.stopbits = 1
i.serial.timeout = 1
i.debug = False
var = tk.DoubleVar()
label = tk.Label(root, textvariable=var)
label.pack()
mass_flow_rate = i.read_registers(registeraddress=246, numberOfRegisters=2,
functioncode=3)
flow_ = [mass_flow_rate[0], mass_flow_rate[1]]
flow_pack = struct.pack('HH', flow_[0], flow_[1])
flow_unpack = round(struct.unpack('f', flow_pack)[0], 4)
def function():
global flow_unpack
var.set(flow_unpack)
root.update()
time.sleep(5)
root.after(2000, function)
root.mainloop()
Change this in your code
def function():
global flow_unpack
var.set(flow_unpack)
root.after(2000, function) # just add this for further calls
# root.update() -> not needed, see Brian comment.
# time.sleep(5) -> do you really need this?
# you will block the tkinter mainloop and your UI.

python spawn multiple message boxes at once

I need my program to spawn multiple message boxes.
They have to be spawned in cascade at once.
(think of it as mimicry of malicious activity)
I tried do this using Tkinter:
import Tkinter
import tkMessageBox
for i in range(0,5):
tkMessageBox.showerror("", "oops")
but it seems program waits for user interaction with each message before showing next which is not quite what I need
and optional there is an empty form at top left corner. any idea to get rid of it?
The solutions might be to use TopLevel() here. This will allow all windows to pop up and you will be able to set a customer messagebox style as well.
Here is a simple example that will open all the windows at once while also hiding the root window. The below will stack all the windows on top of each other and you can move them. You can also provide some tracked variables to open each windows in a different location if you like.
#For python 3 imports:
import tkinter as tk
from tkinter import ttk
# for python 2 imports:
# import Tkinter as tk
# import ttk
root = tk.Tk()
root.withdraw()
for i in range(0,5):
x = tk.Toplevel(root)
x.title("Error Box!")
x.geometry("150x75+0+0")
x.resizable(False, False)
ttk.Label(x, text = "oops").pack()
ttk.Button(x, text = " OK ", command = x.destroy).pack(side=tk.BOTTOM)
root.mainloop()
In response to your comment on using a counter see below code:
#For python 3 imports:
import tkinter as tk
from tkinter import ttk
# for python 2 imports:
# import Tkinter as tk
# import ttk
root = tk.Tk()
root.withdraw()
counter = 0
def close_window(top_window):
global counter
top_window.destroy()
counter -= 1
if counter == 0:
print("destroying root window")
root.destroy()
for i in range(0,5):
counter += 1
x = tk.Toplevel(root)
x.title("Error Box!")
x.geometry("150x75+0+0")
x.resizable(False, False)
ttk.Label(x, text="oops").pack()
ttk.Button(x, text=" OK ", command=lambda tw=x: close_window(tw)).pack(side=tk.BOTTOM)
# this protocol() method is used to re-rout the window close event to a customer function.
# this will allow us to keep our counter and close windows as needed.
x.protocol("WM_DELETE_WINDOW", lambda tw=x: close_window(tw))
root.mainloop()
Better yet here is an example that places the items inside of a list so we do not need a counter.
#For python 3 imports:
import tkinter as tk
from tkinter import ttk
# for python 2 imports:
# import Tkinter as tk
# import ttk
root = tk.Tk()
root.withdraw()
list_of_windows = []
def close_window(tw):
i = list_of_windows.index(tw)
list_of_windows[i].destroy()
del list_of_windows[i]
if len(list_of_windows) == 0:
root.destroy()
print("root destroyed!")
for i in range(0,5):
x = tk.Toplevel(root)
x.title("Error Box!")
x.geometry("150x75+0+0")
x.resizable(False, False)
ttk.Label(x, text="oops").pack()
ttk.Button(x, text=" OK ", command=lambda tw=x: close_window(tw)).pack(side=tk.BOTTOM)
x.protocol("WM_DELETE_WINDOW", lambda tw=x: close_window(tw))
list_of_windows.append(x)
root.mainloop()
My conclusion:
Using tk message boxes wasn't best approach to the task,
because message boxes are modal, and there is no direct way to change that.
So instead I've just got a form shaped like a message box and spawned them with desirable quantity.
Ended up with following code:
from Tkinter import *
di = {}
for i in range(5):
di[i] = Tk()
offset = 300 + i*10
di[i].geometry('150x50+'+str(offset)+'+'+str(offset))
di[i].title('')
di[i].resizable(False, False)
la = Label(di[i],text = 'oops').pack()
button = Button(di[i], text = 'OK', command=di[i].destroy).pack()
di[0].mainloop()
And it serves my needs well. Thanks to Nae and Vasilis G. for their kind responses leading me to a working code.

Categories

Resources