How to create GUI objects one by one with Tkinter - python-3.x

I like to create an object per second and make the show up one by one. However, the code below wait for 3 seconds and show them all at the same time.
from tkinter import *
import time
def create():
for i in range(3):
r4=Radiobutton(root, text="Option 1"+str(i), value=1)
r4.pack( anchor = W )
time.sleep(1)
root = Tk()
create()
root.mainloop()

Your code, as is, creates a one object per second as you desired it, but this objects need to be shown, and they're shown when code flow reaches the mainloop. Hence, for observer, it looks like there're no objects at all after one second.
Of course, you can use sleep and update, but beware - sleeping leads to unresponsive window, so it's OK option (to be honest - not OK at all), if your application isn't drawn and you're outside of mainloop, but if it's not - prepare for a "frozen" window, because GUI can redraw himself only in mainloop (an event loop, you can reach it with update as well) and sleep blocks this behaviour.
But there's a good alternative, the after method, take a look on it!
And there's a snippet, so you can see the difference:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import time
def create_with_sleep():
for _ in range(3):
tk.Radiobutton(frame_for_sleep, text="Sleep Option").pack(anchor='w')
time.sleep(int(time_entry.get()))
root.update()
def create_with_after(times=3):
if times != 0:
tk.Radiobutton(frame_for_after, text="After Option").pack(anchor='w')
times -= 1
root.after(int(time_entry.get()) * 1000, lambda: create_with_after(times))
root = tk.Tk()
test_yard_frame = tk.Frame(root)
frame_for_sleep = tk.Frame(test_yard_frame)
frame_for_after = tk.Frame(test_yard_frame)
test_yard_frame.pack()
frame_for_sleep.pack(side='left')
frame_for_after.pack(side='left')
button_frame = tk.Frame(root)
button_for_sleep = tk.Button(button_frame, text='Create 3 radiobuttons with sleep+update', command=create_with_sleep)
button_for_after = tk.Button(button_frame, text='Create 3 radiobuttons with after', command=create_with_after)
button_frame.pack()
button_for_sleep.pack(side='left')
button_for_after.pack(side='left')
time_label = tk.Label(root, text='Time delay in seconds:')
time_label.pack(fill='x')
time_entry = tk.Entry(root)
time_entry.insert(0, 1)
time_entry.pack(fill='x')
root.mainloop()
With 1 seconds delay there's no much difference, but you can try to increase delay to understand why after option is preferable in general.

You can use update to call a refresh on your objects.
In use, you would have to add the line root.update() in your for loop.

Related

Python tkinter- Threading through button click

I am displaying a sensor device's measurement values using the Tkinter GUI application. The sensor sends new data every second. I started a new thread and put the result in a Queue and process the queue to display the values on GUI. Now I am facing another problem. The sensor has two modes of operations. For simplicity, I have used a random generator in the program. Users should be able to switch the modes using two buttons. Button-1 for Mode-1, Button-2 for Mode-2. (say the mode-1 operation is random.randrange(0,10) and mode-2 operation is random.randrange(100, 200). How do I control these two operations through Threading? if a user started a mode-1 operation, when he presses the Button-2, the mode-1 operation should stop (thread-1) and mode-2 operation (thread-2) should start. Does it mean do I need to kill thread-1? Or is there any way to control two modes in same thread? I am totally new into threading. Any suggestions, please.
import tkinter
import threading
import queue
import time
import random
class GuiGenerator:
def __init__(self, master, queue):
self.queue = queue
# Set up the GUI
master.geometry('800x480')
self.output = tkinter.StringVar()
output_label = tkinter.Label(master, textvariable= self.output)
output_label.place(x=300, y=200)
#I haven't shown command parts in following buttons. No idea how to use it to witch modes?
mode_1_Button = tkinter.Button(master, text = "Mode-1")
mode_1_Button.place(x=600, y=300)
mode_2_Button = tkinter.Button(master, text = "Mode-2")
mode_2_Button.place(x=600, y=400)
def processQueue(self):
while self.queue.qsize():
try:
sensorOutput = self.queue.get() #Q value
self.output.set(sensorOutput) #Display Q value on GUI
except queue.Empty:
pass
class ClientClass:
def __init__(self, master):
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.myGui = GuiGenerator(master, self.queue)
#How do I switch the modes of operations? do I need some flags setting through button press?
# Set up the thread to do asynchronous I/O
self.thread1_mode1 = threading.Thread(target=self.firstModeOperation)
self.thread1_mode1.start()
# Start the periodic call in the GUI to check if the queue contains
# anything new
self.periodicCall()
def periodicCall(self):
# Check every 1000 ms if there is something new in the queue.
self.myGui.processQueue()
self.master.after(1000, self.periodicCall)
def firstModeOperation(self):
while True: #??? how do i control here through mode selection
time.sleep(1.0)
msg_mode_1= random.randrange(0,10)
self.queue.put(msg_mode_1)
def secondModeOperation(self):
while True: #??? how do i control here through mode selection
time.sleep(1.0)
msg_mode_2= random.randrange(100,200)
self.queue.put(msg_mode_2)
#Operation part
root = tkinter.Tk()
client = ClientClass(root)
root.mainloop()

Separating Tkinters GUI and control of the application

1) What is my goal:
I’m creating an application that should read data every 60s from ModBusServer, append those data to Graphs and then when the app is closed save the data to excel file.
Site note:
The process of reading data from ModBusServer and appending them to graphs should start after a start button is pressed.
And end after stop button is pressed OR when ModBusServer sends a request to stop.
2) What I have so far:
I created the GUI without any major problems as a class “GUI_komora”.
Everything there works just fine.
3) What is the problem:
But now I’m lost on how to approach the “read data every 60 seconds”, and overall how to control the application.
I did some research on threading but still I’m confused how to implement this to my application.
I learned how to make functions run simultaneously in this tutorial.
And also how to call a function every few seconds using this question.
But none of them helped me to learn how to control the overall flow of the application.
If you could redirect me somewhere or tell me about a better approach I would be really glad.
Some of my code:
from tkinter import *
from GUI_komora import GUI
root = Tk()
my_gui = GUI(root) #my GUI class instance
#main loop
root.mainloop()
"""
How do I achieve something like this???
whenToEnd = False
while whenToEnd:
if step == "Inicialzation":
#inicializace the app
if step == "ReadData":
#read data every 60 seconds and append them to graphs
if step == "EndApp"
#save data to excel file and exit app
whenToEnd = True
"""
Here is an example of a loop that takes a decision (every 60 sec in your case) and pushes the outcome of the decision to tkinter GUI: https://github.com/shorisrip/PixelescoPy/blob/master/base.py
Parts:
main thread - starts tkinter window
control thread - reads some data and decides what to show in GUI
GUI class - has a method "add_image" which takes input an image and displays on GUI.(add_data_to_graph maybe in your case). This method is called everytime by the control thread.
Snippets:
def worker(guiObj, thread_dict):
# read some data and make decision here
time.sleep(60)
data_to_show = <outcome of the decision>
while some_logic:
pictureObj = Picture(chosen, timer)
pictureObj.set_control_thread_obj(thread_dict["control_thread"])
guiObj.set_picture_obj(pictureObj)
pictureObj.display(guiObj)
# do post display tasks
guiObj.quit_window()
# Keep GUI on main thread and everything else on thread
guiObj = GuiWindow()
thread_list = []
thread_dict = {}
thread_for_image_control = threading.Thread(target=worker, args=(guiObj,
thread_dict))
thread_dict["control_thread"] = thread_for_image_control
thread_list.append(thread_for_image_control)
thread_for_image_control.start()
guiObj.run_window_on_loop()
# thread_for_image_control.join()
Code for Picture class:
class Picture:
def __init__(self, path, timer):
self.path = path
self.timer = timer
self.control_thread_obj = None
def set_control_thread_obj(self, thread_obj):
self.control_thread_obj = thread_obj
def display(self, guiObj):
image_path = self.path
guiObj.add_image(image_path)
time.sleep(self.timer)
Code for GUI class
class GuiWindow():
def __init__(self):
self.picture_obj = None
self.root = Tk()
self.image_label = None
self.image = None
self.folder_path = None
self.timer = None
self.root.protocol("WM_DELETE_WINDOW", self.exit_button)
def add_image(self, image_path):
resized_img = self.resize(image_path)
image_obj = ImageTk.PhotoImage(resized_img)
image_label = Label(self.root, image=image_obj,
height=resized_img.height,
width=resized_img.width)
self.image = image_obj # DO NOT REMOVE - Garbage collector error
if self.image_label is not None:
self.remove_image()
image_label.grid(row=0, column=0, columnspan=3)
self.image_label = image_label
Here based on my control loop thread I am changing image (in your case graph data) of the tkinter GUI.
Does this help?

Why is Python Skipping Over Tkinter Code?

I'm in Python 3.x using Tkinter to make a button that changes a boolean variable's value from true to false then a if statement to check if that value is false. Here is my code for that:
import tkinter
import time
x = True
top = tkinter.Tk()
def helloCallBack():
x = False
print (x)
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
if x == False:
print ('Hello')
top.mainloop()
This unfortunately didn't work, so I replaced the if statement with a time.sleep(10) and then print (x) so I would have enough time to press the button like so:
import tkinter
import time
x = True
top = tkinter.Tk()
def helloCallBack():
x = False
print (x)
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
time.sleep(10)
print (x)
top.mainloop()
The issue with this is it skips over all of the Tkinter code and goes to the time.sleep(10), prints the value of x then brings everything from Tkinter up after. Why is it doing this?
The reason you see that the code does sleep() and then prints True before the tkinter windows opens is due to how the mainloop() works in tkinter.
sleep() is useful in python however due to tkinter's single threaded nature all sleep() can due is block the mainloop until it has completed sleeping. Tkinter has its own method to work around this issue called after() and you don't need it here but it is very useful for timing things in tkinter.
Tkinter does its updates in the mainloop and you have a sleep() command that occurs before your mainloop as well as a print command before the mainloop. Both of those things must finish execution before the mainloop is reached for the first time thus not allowing tkinter to start until they are complete.
What you want to do is to place this print statement into your function. As well as a few other quality of life clean ups.
See this example:
import tkinter as tk
top = tk.Tk()
x = True
def hello_call_back():
global x
x = False
if not x:
print('Hello')
tk.Button(top, text="Hello", command=hello_call_back).pack()
top.mainloop()
However in this case I think the simplest form of the code should look like this:
import tkinter as tk
top = tk.Tk()
def hello_call_back():
print('Hello')
tk.Button(top, text="Hello", command=hello_call_back).pack()
top.mainloop()
As your current use of x is redundant to the goal.

Windows program not responding issue with tkinter gui

I'm having an issue with a program I created, Im trying to make a program that images a drive usind dism. When the dism command is run in cmd it gives a percentage of completion, so I am trying to take that percentage (using regex) and put it in my own progress bar gui however when I run my program, windows gives me a program is not responding error and I'm not sure why. The program continues to to run in the background and upon completion the error disappears, but the progress bar does not update. I have a feeling it is because I my run function has its own loop so maybe the tkinter gui isn't updating until that loop completes. If someone can confirm this or give me other reasoning that would be greatly appreciated. Also if you have any ideas for a solution to this I am all ears.
import shlex
from tkinter import *
from tkinter import ttk
def run(command):
progressBar['maximum'] = 100
process = subprocess.Popen(shlex.split(command), stdin= subprocess.PIPE
,stdout=subprocess.PIPE)
while True:
output = process.stdout.readline().decode()
if output == '' and process.poll() is not None:
break
if output:
matchObj = re.search(r'\d+\.\d%',output.strip())
if matchObj:
percentNum = re.search(r'\d+\.\d',matchObj.group())
progressBar["value"] =
round(float(percentNum.__getitem__(0)))
print(type(progressBar["value"]))
print(progressBar["value"])
else:
print(output.strip())
rc = process.poll()
progressBar["value"] = 0
return rc
root = Tk()
root.title('Progress Bar')
root.geometry("300x100")
buttonFrame = LabelFrame(text="Options")
buttonFrame.grid(column=0,row=0)
backupCmd = 'Dism /Capture-Image /ImageFile:F:\my-windows-partition.wim
/CaptureDir:"E:" /name:Windows \n'
button1 = Button(master=buttonFrame, text="Backup",command= lambda:
run(backupCmd))
button1.grid(column = 0, row = 0)
restoreCmd = ''
button2 =Button(master=buttonFrame, text="Restore",command= lambda:
run(restoreCmd))
button2.grid(column = 50, row = 0)
button3 =Button(master=buttonFrame, text="Exit",command= lambda: exit())
button3.grid(column = 100, row = 0)
progressBar = ttk.Progressbar(root, orient="horizontal",
length=286,mode="determinate")
progressBar.grid(column = 0, row = 3, pady=10)
root.mainloop()
I figured out the issue. For anyone struggling with this, you need to add a call to update the progress bar. For me I said, progressBar.update in my run methon
I was stuck with this issue for a long time until I found an answer on stackoverflow about using multi-threading.
Tkinter window says (not responding) but code is running You to create a thread for sql queries function, like this
from threading import Thread
Thread(target = run, args =(command, )). Start()

Ignore input from user

I have a program that shows an image. Every time the user presses enter, I recalculate the image, what takes some time:
root = tk.Tk()
def callback_enter(e):
# Heavy computation
root.bind("<Return>", callback_enter)
root.mainloop()
The problem is, when the user presses enter multiple times, callback function will be called again and again, even when the user stopped pressing the button, since the program remembers all the key presses before. Is there a way callback_enter() removes all key presses, that have been done during its execution?
the problem here is that your program, while is busy with image computation, cannot interact with the main loop that is buffering input. One way to approach it is to filter events with a time frame criterium; here is an implementation as example:
import time
import random
import Tkinter as tk
root = tk.Tk()
LAST_CALL_TIME = 0
TIME_THRESHOLD = .1
def performHeavyComputation():
print("performHeavyComputation() START")
time.sleep(1 + random.random())
print("performHavyComputation() END")
def callback_enter(e):
global LAST_CALL_TIME
global TIME_THRESHOLD
t = time.time()
if t - LAST_CALL_TIME < TIME_THRESHOLD:
print("[%.3f] filtering event e:%s"%(t, e))
else:
LAST_CALL_TIME = t
performHeavyComputation()
t1 = time.time()
TIME_THRESHOLD = t1 - t + 0.1
root.bind("<Return>", callback_enter)
root.mainloop()

Resources