root.update is lagging in linux but not windows - python

My python tkinter code is lagging severely in linux but not windows. It has something to do with root.update() and root.config(). How do I fix this so that the linux version is just as fast as the windows version?
I've written a python program on windows which works well. I'm currently making a linux version. After some modification the linux version works as it should, except massive lagging. I inserted code to time different parts of the program. There are large differences from windows to linux for the root.update() and root.config() lines.
The following lines are the ones causing the lagging:
root.update()
root.config(menu=menubar)
I ran the program several times in windows and linux and recorded the amount of time used to execute the code.
The following is a record of the run times for the lines:
In windows:
root update: 0.47 seconds
root update: 0.2656 seconds
root update: 0.3125 seconds
root update: 0.3594 second
root update: 0.3593 seconds
menubar root config done: 0.0081
menubar root config done: 0.0
In windows: Process finished with exit code -1
pycharm in windows is using Python 3.7
In linux:
root update: 2.4416 seconds
root update: 87.3216 seconds
root update: 1.5798 seconds
root update: 148.2783 seconds
root update: 2.2533 seconds
root update: 2.2771 seconds
root update: 2.4898 seconds
root update: 8.022 seconds
root update: 171.6852 seconds
root update: 1.7088 seconds
menubar root config done: 0.0441
menubar root config done: 2.4566
menubar root config done: 1.2589
In linux: Process finished with exit code 9
pycharm in linux is using Python 3.6
Here is the code as simple as I can make it. There is a gui made by tkinter, queries to a mysql database, a function which generates a menubar and lots of widgets put in a grid.
root = Tk()
root.title("KLUSTERBOX")
...
def main_frame(): # call function to make the main screen
# define and put widgets on a grid
...
generate_menubar(Frame) # call function to make menubar
# define the menubar
root.config(menu=menubar)
...
# define and put widgets on a grid
root.update()
I was asked for a MCVE. This is a sample program which duplicates the problem:
from tkinter import *
import time
import sys
def main_frame():
starttime = time.time()
F = Frame(root)
F.pack(fill=BOTH, side=LEFT)
C1 = Canvas(F)
C1.pack(fill=BOTH, side=BOTTOM)
Button(C1, text="Refresh", width=12, command=lambda: [F.destroy(),main_frame()]).pack(side=LEFT)
Button(C1, text="Quit", width=12, command=root.destroy).pack(side=LEFT)
# link up the canvas and scrollbar
S = Scrollbar(F)
C = Canvas(F, width=1600)
S.pack(side=RIGHT, fill=BOTH)
C.pack(side=LEFT, fill=BOTH, pady=10, padx=10)
S.configure(command=C.yview, orient="vertical")
C.configure(yscrollcommand=S.set)
if sys.platform == "win32":
C.bind_all('<MouseWheel>', lambda event: C.yview_scroll(int(-1 * (event.delta / 120)), "units"))
elif sys.platform == "linux":
C.bind_all('<Button-4>', lambda event: C.yview('scroll',-1,'units'))
C.bind_all('<Button-5>', lambda event: C.yview('scroll',1,'units'))
# create the frame inside the canvas
preF=Frame(C)
C.create_window((0, 0), window=preF, anchor=NW)
Label(preF, text="To refresh - press REFRESH").pack()
Label(preF, text="To quit - press QUIT").pack()
Label(preF, text="Run times are displayed in console").pack()
FF = Frame(C)
C.create_window((0,108), window=FF, anchor=NW)
for i in range(100):
Button(FF, text=i, width=5, bg="yellow", anchor="w").grid(row=i,column=0)
Button(FF, text="hello there", width=24, bg="yellow", anchor="w").grid(row=i,column=1)
Button(FF, text=" ", width=5, bg="green", anchor="w").grid(row=i, column=2)
Button(FF, text=" ", width=5, bg="green", anchor="w").grid(row=i,column=3)
Button(FF, text=" ", width=5, bg="green", anchor="w").grid(row=i, column=4)
Button(FF, text=" ", width=5, bg="green", anchor="w").grid(row=i, column=5)
endtime = time.time()
print("runtime prior to root.update(): ", round(endtime - starttime,4), " seconds")
starttime = time.time()
root.update()
endtime = time.time()
print("root.update() runtime: ", round(endtime-starttime,4)," seconds")
C.config(scrollregion=C.bbox("all"))
mainloop()
root = Tk()
root.geometry("%dx%d+%d+%d" % (625,600,100,50))
main_frame()
I have timed the run times for the root.update() and root.config(menu=menubar). The times in linux are too long and would make the program unusable, especially considering that there are other part of the program which lag much more.

On my Linux Mint it takes 0.3s (Python 3.7, 3.6, 2.7) and I don't know why it runs so slow on your Linux.
Here is only code with some changes - maybe it will help.
I don't use root.update() but after() to change scrollregion 100ms after starting mainloop(). Before running mainloop() all widgets don't exist yet and it can't calculate scrollregion.
I don't destroy F with all widgets but I destroy only frame with buttons which creates table and I recreate only this frame.
I don't run main_frame again so I don't run another mainloop()
I had to use global to keep access to frame with table because command= can't get value from function and assing to variable.
Start takes 0.3s and refresh takes 0.09s
.
from tkinter import *
import time
import sys
def create_table(C):
table = Frame(C)
C.create_window((0,108), window=table, anchor=NW)
for i in range(100):
Button(table, text=i, width=5, bg="yellow", anchor="w").grid(row=i,column=0)
Button(table, text="hello there" + str(X), width=24, bg="yellow", anchor="w").grid(row=i,column=1)
Button(table, text=" ", width=5, bg="green", anchor="w").grid(row=i, column=2)
Button(table, text=" ", width=5, bg="green", anchor="w").grid(row=i,column=3)
Button(table, text=" ", width=5, bg="green", anchor="w").grid(row=i, column=4)
Button(table, text=" ", width=5, bg="green", anchor="w").grid(row=i, column=5)
return table
def refresh(C):
global table
starttime = time.time()
table.destroy()
table = create_table(C)
endtime = time.time()
print("refresh: ", round(endtime-starttime,4)," seconds")
def main_frame():
global table
starttime = time.time()
F = Frame(root)
F.pack(fill=BOTH, side=LEFT)
C1 = Canvas(F)
C1.pack(fill=BOTH, side=BOTTOM)
Button(C1, text="Refresh", width=12, command=lambda:refresh(C)).pack(side=LEFT)
Button(C1, text="Quit", width=12, command=root.destroy).pack(side=LEFT)
# link up the canvas and scrollbar
S = Scrollbar(F)
C = Canvas(F, width=1600)
S.pack(side=RIGHT, fill=BOTH)
C.pack(side=LEFT, fill=BOTH, pady=10, padx=10)
S.configure(command=C.yview, orient="vertical")
C.configure(yscrollcommand=S.set)
if sys.platform == "win32":
C.bind_all('<MouseWheel>', lambda event: C.yview_scroll(int(-1 * (event.delta / 120)), "units"))
elif sys.platform == "linux":
C.bind_all('<Button-4>', lambda event: C.yview('scroll',-1,'units'))
C.bind_all('<Button-5>', lambda event: C.yview('scroll',1,'units'))
# create the frame inside the canvas
preF=Frame(C)
C.create_window((0, 0), window=preF, anchor=NW)
Label(preF, text="To refresh - press REFRESH").pack()
Label(preF, text="To quit - press QUIT").pack()
Label(preF, text="Run times are displayed in console").pack()
table = create_table(C)
endtime = time.time()
print("runtime: ", round(endtime - starttime,4), " seconds")
# update scrollregion 100ms after mainloop start
root.after(100, lambda:C.config(scrollregion=C.bbox("all")))
mainloop()
root = Tk()
root.geometry("%dx%d+%d+%d" % (625,600,100,50))
main_frame()

Related

Tkinter window swapping

I don't know much about python or Tkinter but my functions aren't firing. No errors but No result either.
from tkinter import *
def root_win():
root = Tk()
root.geometry('700x400+100+100')
root.maxsize(700, 400)
root.minsize(700, 400)
root.title("root")
btn1_root = Button(root, text="ext1", command=lambda: [root.destroy, ext1_win])
btn1_root.place(x=590, y=360, height=30, width=100)
btn1_root.configure(bg="DodgerBlue3", fg="black")
def ext1_win():
ext1 = Tk()
ext1.geometry('700x400+100+100')
ext1.maxsize(700, 400)
ext1.minsize(700, 400)
ext1.title("1")
btn1_ext1 = Button(ext1, text="back", command=lambda: [ext1.destroy, root_win])
btn1_ext1.place(x=590, y=360, height=30, width=100)
btn1_ext1.configure(bg="DodgerBlue3", fg="black")
root_win()
I'm trying to make it so I can hop between different windows to conserve screen space when I start putting together the rest of my project. Tkinter doesn't seem to like all of def root_win(): being a def.
If you use lambda, you need to call the functions inside the body of the lambda.
btn1_ext1 = Button(ext1, text="back", command=lambda: [root.destroy(), ext1_win()])
You also need to call mainloop. With the above code your functions will be called, but they just run to completion and then exit.
def root_win():
root = Tk()
...
root.mainloop()
In Line 12, change this:
btn1_root = Button(root, text="ext1", command=lambda: [root.destroy, ext1_win])
to:
btn1_root = Button(root, text="ext1", command=ext1_win)
Output you can see root.title("root"):
Output you can see ext1.title("1":

How to control processes using Tkinter?

I want to use the tkinter to build a GUI to control the python script.
The code looks like,
kansai = Page(kansai_url)
tokyo = Page(tokyo_url)
def loop_main():
with concurrent.futures.ProcessPoolExecutor() as executor:
k = executor.submit(kansai.compare)
t = executor.submit(tokyo.compare)
kansai_lbl['text'] = k.result()
tokyo_lbl['text'] = t.result()
root.after(60000, loop_main)
if __name__ == '__main__':
root = tk.Tk()
# --buttons--
start_btn = tk.Button(root, text='Start', command=loop_main, font='Raleway', bg='#20bebe', fg='white', height=2,
width=10)
start_btn.grid(column=1, row=3)
refresh_btn = tk.Button(root, text='Refresh', font='Raleway', bg='#20bebe', fg='white', height=2, width=10)
refresh_btn.grid(column=2, row=3)
quit_btn = tk.Button(root, text='Quit', command=root.destroy, font='Raleway', bg='#20bebe', fg='white', height=2,
width=10)
quit_btn.grid(column=3, row=3)
# -- instruction --
kansai_name_lbl = tk.Label(root, text='Kansai', font='Raleway')
kansai_name_lbl.grid(column=1, row=0)
tokyo_name_lbl = tk.Label(root, text='Tokyo', font='Raleway')
tokyo_name_lbl.grid(column=3, row=0)
kansai_lbl = tk.Label(root)
kansai_lbl.grid(column=1, row=1)
tokyo_lbl = tk.Label(root)
tokyo_lbl.grid(column=3, row=1)
root.mainloop()
My goal is that, I want to control the running of the script via the start and stop buttons. The script is written as the loop_main running with multiprocessing, takes about 20secs to finish.
My problem is when I click start, the script started but the GUI just went no responding and I can't click the quit button. Only during the interval of running, I can click the buttons. But I want to exit the script via quit button at any time.
How can I fix this?
I had an issue with tkinter gui becoming unresponsive while application was executing a function. For me the solution was "threading":
import tkinter
import time
from threading import Thread
def start():
def something_slow():
global stop
stop = False
while not stop:
print("doing stuff")
time.sleep(1)
print("stoped doing stuff")
executing = Thread(target=something_slow)
executing.start()
def stop():
global stop
stop = True
main_window_of_gui = tkinter.Tk()
button_start = tkinter.Button(main_window_of_gui, text="Start", command=start)
button_start.grid(row=0, column=0)
button_stop = tkinter.Button(main_window_of_gui, text="Stop", command=stop)
button_stop.grid(row=0, column=1)
main_window_of_gui.mainloop()
stop = True

TKinter window not showing in program with .after() statement

I am trying to do a self-updating script to display a table with some data I am scraping from a few websites. The data is scraped, loaded, formatted and then it should be displayed in a table format in TKinter. However, when I make it self-refreshing with root.after() the window does not appear at all. I know the program is running, because I see output in the terminal, but nothing comes up as a TKwindow. Is there something I am missing from .after()'s behaviour? Would it not run when there are a lot of lines?
I tried just commenting the .after() part and the script would execute completely and then show me the window. I also never entered the Else statement, which is fine, but for some reason the code would not work even with just the If statement.
Below is the code I used
root = Tk()
root.geometry("1080x1080")
def calculations():
Scraping_and_writing_in_files()
final_result = File_format_pandas()
if len(final_result.index) == 0:
label = Label(root, text = "There are no hits")
label.pack()
print("I am in the if statement - no hits found")
else:
print("I am in the else statement") # I was never in the Else statement so you can ignore the code below
#Create a main frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH, expand=1)
#Create a canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
#Add scrollbar
my_scrollbar = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
#Configure scrollbar
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>',lambda e: my_canvas.configure(scrollregion = my_canvas.bbox("all")))
def _on_mouse_wheel(event):
my_canvas.yview_scroll(-1 * int((event.delta / 120)), "units")
my_canvas.bind_all("<MouseWheel>", _on_mouse_wheel)
#Create another frame in the canvas
second_frame = Frame(my_canvas)
#Add that new frame to a window in the canvas
my_canvas.create_window((0,0), window=second_frame, anchor = "nw")
initial_entries1 = Entry(second_frame,font=("Helvetica", 12),bd=0,width=30)
initial_entries1.insert(0, "Match Name")
initial_entries1.config(state = "readonly")
initial_entries1.grid(row=0, column=0)
initial_entries2 = Entry(second_frame,font=("Helvetica", 12),bd=0,width=30)
initial_entries2.insert(0, "Result for T1")
initial_entries2.config(state = "readonly")
initial_entries2.grid(row=0, column=1)
initial_entries3 = Entry(second_frame,font=("Helvetica", 12),bd=0,width=30)
initial_entries3.insert(0, "Result for Draw")
initial_entries3.config(state = "readonly")
initial_entries3.grid(row=0, column=2)
initial_entries3 = Entry(second_frame,font=("Helvetica", 12),bd=0,width=30)
initial_entries3.insert(0, "Result for T2")
initial_entries3.config(state = "readonly")
initial_entries3.grid(row=0, column=3)
for thing in range(len(final_result.index)):
match_name = Entry(second_frame, font=("Helvetica", 10),bd=0,width=30)
match_name.insert(0, final_result['Match name'].iloc[thing])
match_name.config(state = "readonly")
match_name.grid(row=thing+1, column=0)
result_t1 = Entry(second_frame, font=("Helvetica", 10),bd=0,width=15)
result_t1.insert(0, final_result['Difference Team 1 For'].iloc[thing])
if float(final_result['Difference Team 1 For'].iloc[thing]) > 0:
result_t1.config(state = "readonly")
result_t1.grid(row=thing+1, column=1)
result_t2 = Entry(second_frame, font=("helvetica", 10), bd=0, width=15)
result_t2.insert(0, final_result['Difference Team 2 For'].iloc[thing])
if float(final_result['Difference Team 2 For'].iloc[thing])>0:
result_t2.config(state = "readonly")
result_t2.grid(row=thing+1, column=3)
result_draw = Entry(second_frame, font=("helvetica", 10), bd=0, width=15)
result_draw.insert(0, final_result['Difference Draw For'].iloc[thing])
if float(final_result['Difference Draw For'].iloc[thing]) > 0:
result_draw.config(state = "readonly")
result_draw.grid(row=thing+1, column=2)
root.after(2000, main_frame.destroy())
# label1 = Label(second_frame, text="Google Hyperlink", fg="blue", cursor="hand2")
# label1.pack()
# label1.bind("<Button-1>", lambda e: callback("http://www.google.com"))
root.after(1000, calculations())
calculations()
root.mainloop()
In the after method no parentheses are needed, since they call the function immediately and will use whatever it returns as callback after the delay. Thank you Jasonharper and Sujay.
root.after(1000, calculations()) => should be root.after(1000, calculations)

How to make a Tkinter window not resizable?

I need a Python script that uses the Tkinter module to create a static (not resizable) window.
I have a pretty simple Tkinter script but I don't want it to be resizable. How do I prevent a Tkinter window from being resizable? I honestly don't know what to do.
This is my script:
from tkinter import *
import ctypes, os
def callback():
active.set(False)
quitButton.destroy()
JustGo = Button(root, text=" Keep Going!", command= lambda: KeepGoing())
JustGo.pack()
JustGo.place(x=150, y=110)
#root.destroy() # Uncomment this to close the window
def sleep():
if not active.get(): return
root.after(1000, sleep)
timeLeft.set(timeLeft.get()-1)
timeOutLabel['text'] = "Time Left: " + str(timeLeft.get()) #Update the label
if timeLeft.get() == 0: #sleep if timeLeft = 0
os.system("Powercfg -H OFF")
os.system("rundll32.exe powrprof.dll,SetSuspendState 0,1,0")
def KeepGoing():
active.set(True)
sleep()
quitButton1 = Button(root, text="do not sleep!", command=callback)
quitButton1.pack()
quitButton1.place(x=150, y=110)
root = Tk()
root.geometry("400x268")
root.title("Alert")
root.configure(background='light blue')
timeLeft = IntVar()
timeLeft.set(10) # Time in seconds until shutdown
active = BooleanVar()
active.set(True) # Something to show us that countdown is still going.
label = Label(root, text="ALERT this device will go to sleep soon!", fg="red")
label.config(font=("Courier", 12))
label.configure(background='light blue')
label.pack()
timeOutLabel = Label(root, text = 'Time left: ' + str(timeLeft.get()), background='light blue') # Label to show how much time we have left.
timeOutLabel.pack()
quitButton = Button(root, text="do not sleep!", command=callback)
quitButton.pack()
quitButton.place(x=150, y=110)
root.after(0, sleep)
root.mainloop()
The resizable method on the root window takes two boolean parameters to describe whether the window is resizable in the X and Y direction. To make it completely fixed in size, set both parameters to False:
root.resizable(False, False)

How do I create an automatically updating GUI using Tkinter?

from Tkinter import *
import time
#Tkinter stuff
class App(object):
def __init__(self):
self.root = Tk()
self.labeltitle = Label(root, text="", fg="black", font="Helvetica 40 underline bold")
self.labeltitle.pack()
self.labelstep = Label(root, text="", fg="black", font="Helvetica 30 bold")
self.labelstep.pack()
self.labeldesc = Label(root, text="", fg="black", font="Helvetica 30 bold")
self.labeldesc.pack()
self.labeltime = Label(root, text="", fg="black", font="Helvetica 70")
self.labeltime.pack()
self.labelweight = Label(root, text="", fg="black", font="Helvetica 25")
self.labelweight.pack()
self.labelspeed = Label(root, text="", fg="black", font="Helvetica 20")
self.labelspeed.pack()
self.labeltemp = Label(root, text="", fg="black", font="Helvetica 20")
self.labeltemp.pack()
self.button = Button(root, text='Close recipe', width=25, command=root.destroy)
self.button.pack()
def Update(self, label, change):
label.config(text=str(change))
def main():
app = App()
app.mainloop()
if __name__ == "__main__":
main()
I'm trying to create a recipe display which will show the step, instructions, weight and other variables on a screen in a Tkinter GUI.
However, I do not know how to update the GUI to change with each new step of the recipe, as the content has to be dynamically updated based on user input (taken from a server). How can I achieve updating of the GUI's other elements based on the change in steps?
You can use after() to run function after (for example) 1000 miliseconds (1 second) to do something and update text on labels. This function can run itself after 1000 miliseconds again (and again).
It is example with current time
from Tkinter import *
import datetime
root = Tk()
lab = Label(root)
lab.pack()
def clock():
time = datetime.datetime.now().strftime("Time: %H:%M:%S")
lab.config(text=time)
#lab['text'] = time
root.after(1000, clock) # run itself again after 1000 ms
# run first time
clock()
root.mainloop()
BTW: you could use StringVar as sundar nataraj Сундар suggested
EDIT: (2022.01.01)
Updated to Python 3 with other changes suggested by PEP 8 -- Style Guide for Python Code
import tkinter as tk # PEP8: `import *` is not preferred
import datetime
# --- functions ---
# PEP8: all functions before main code
# PEP8: `lower_case_name` for funcitons
# PEP8: verb as function's name
def update_clock():
# get current time as text
current_time = datetime.datetime.now().strftime("Time: %H:%M:%S")
# udpate text in Label
lab.config(text=current_time)
#lab['text'] = current_time
# run itself again after 1000 ms
root.after(1000, update_clock)
# --- main ---
root = tk.Tk()
lab = tk.Label(root)
lab.pack()
# run first time at once
update_clock()
# run furst time after 1000ms (1s)
#root.after(1000, update_clock)
root.mainloop()
if you want to change label dynamically
self.dynamiclabel=StringVar()
self.labeltitle = Label(root, text=self.dynamiclabel, fg="black", font="Helvetica 40 underline bold")
self.dyanamiclabel.set("this label updates upon change")
self.labeltitle.pack()
when ever you get new value then just use .set()
self.dyanamiclabel.set("Hurrray! i got changed")
this apply to all the labels.To know more read this docs
If you are using labels, then you can use this:
label = tk.Label(self.frame, bg="green", text="something")
label.place(rely=0, relx=0.05, relwidth=0.9, relheight=0.15)
refresh = tk.Button(frame, bg="white", text="Refreshbutton",command=change_text)
refresh.pack(rely=0, relx=0.05, relwidth=0.9, relheight=0.15)
def change_text()
label["text"] = "something else"
Works fine for me, but it is dependent on the need of a button press.
I added a process bar in my window, and change its value according to randint for every 1 second using the update function:
from random import randint
def update():
mpb["value"] = randint(0, 100) # take process bar for example
window.after(1000, update)
update()
window.mainloop()
I wrote an example with Python 3.7
from tkinter import *
def firstFrame(window):
global first_frame
first_frame = Frame(window)
first_frame.place(in_=window, anchor="c", relx=.5, rely=.5)
Label(first_frame, text="ATTENTION !").grid(row=1,column=1,columnspan=3)
def secondFrame(window):
global second_frame
second_frame= Frame(window, highlightbackground=color_green, highlightcolor=color_green, highlightthickness=3)
second_frame.place(in_=window, anchor="c", relx=.5, rely=.5)
Label(second_frame, text="This is second frame.").grid(row=1, column=1, columnspan=3, padx=25, pady=(15, 0))
window = Tk()
window.title('Some Title')
window.attributes("-fullscreen", False)
window.resizable(width=True, height=True)
window.geometry('300x200')
firstFrame(window)
secondFrame(window)
first_frame.tkraise()
window.after(5000, lambda: first_frame.destroy()) # you can try different things here
window.mainloop()
Use root.config() and add a way to run

Categories

Resources