How to control processes using Tkinter? - python

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

Related

Tkinter Entry Widget: How to constantly check for activity and delete the field if nothing is typed in a period of time

Trying to write a little program where you type in a Tkinter Entry widget and if you don't type for 5 seconds it deletes everything. The best I can do is have it delete everything on the first key pressed after the five seconds elapses, but I can't figure out how to get it to do it without that extra key press.
import time
from tkinter import *
def click(key):
global click_before_last_click, last_click
# print(key.char)
click_before_last_click = last_click
last_click = time.time()
# print(click_before_last_click)
# print(last_click)
delete_shit()
def disappearing_text_start():
global click_before_last_click, last_click
click_before_last_click = time.time()
last_click = time.time()
entry.delete(1.0, END)
entry.bind("<Key>", click)
def disappearing_text_end():
text_file = open("result.txt", "w")
text_file.write(entry.get(1.0, END))
text_file.close()
entry.delete(1.0, END)
def delete_shit():
if last_click > click_before_last_click + 5:
print("TOO LONG")
entry.delete(1.0, END)
if __name__ == "__main__":
click_before_last_click = time.time()
last_click = time.time()
window = Tk()
window.title("Disappearing Text")
window.config(padx=50, pady=20, bg="#D3D3D3")
title_label = Label(text="Disappearing Text App", fg="black", bg="#D3D3D3", font=("Courier", 24))
title_label.grid(column=1, row=0, columnspan=2)
label = Label(text="Click start to begin, and end to save your text. "
"If you stop typing for 5 seconds, you lose everything.",
bg="#D3D3D3", font=("Courier", 14))
label.grid(column=1, row=1, columnspan=2)
entry = Text(width=100, height=30)
entry.grid(column=1, columnspan=2, row=3)
start_button = Button(text="Start", command=disappearing_text_start)
start_button.grid(column=1, row=4, pady=20)
end_button = Button(text="Save", command=disappearing_text_end)
end_button.grid(column=2, row=4, pady=20)
window.mainloop()
You can use after to delete the characters after the time interval. Each time the user presses a key, delete the old scheduled function and then reschedule it.
Also, FWIW, you've used an index of 1.0 which is invalid. Tkinter will accept it, but an index is a string rather than a floating point number.
Let's start by writing a function that will schedule the text to be deleted in 5 seconds. It will also cancel any pending job, effectively resetting the timer to zero. It needs to accept an event parameter since it will be called from a key binding.
after_id = None
def schedule_delete(event=None):
global after_id
if after_id:
window.after_cancel(after_id)
after_id = window.after(5000, delete_shit)
Next, arrange for this to be called when the user clicks the "start" button. disappearing_text_start might look something like this:
def disappearing_text_start():
schedule_delete()
entry.delete("1.0", END)
You can then call this bind command once in the main body of of your program to have reschedule_delete called on every keypress:
entry.bind("<Any-KeyPress>", reschedule_delete)
Finally, we need to cancel any pending job when the user clicks the "stop" button:
def disappearing_text_end():
global after_id
if after_id:
window.after_cancel(after_id)
after_id = None
... the rest of your code here ...
Here's a complete working example:
from tkinter import *
def click(key):
schedule_delete()
def disappearing_text_start():
schedule_delete()
entry.delete("1.0", END)
def schedule_delete(event=None):
global after_id
if after_id:
window.after_cancel(after_id)
after_id = window.after(5000, delete_shit)
def disappearing_text_end():
global after_id
if after_id:
window.after_cancel(after_id)
after_id = None
text_file = open("result.txt", "w")
text_file.write(entry.get(1.0, END))
text_file.close()
entry.delete("1.0", END)
def delete_shit():
entry.delete("1.0", END)
if __name__ == "__main__":
# this is used to keep track of the scheduled function call
after_id = None
window = Tk()
window.title("Disappearing Text")
window.config(padx=50, pady=20, bg="#D3D3D3")
title_label = Label(text="Disappearing Text App", fg="black", bg="#D3D3D3", font=("Courier", 24))
title_label.grid(column=1, row=0, columnspan=2)
label = Label(text="Click start to begin, and end to save your text. "
"If you stop typing for 5 seconds, you lose everything.",
bg="#D3D3D3", font=("Courier", 14))
label.grid(column=1, row=1, columnspan=2)
entry = Text(width=100, height=30)
entry.grid(column=1, columnspan=2, row=3)
start_button = Button(text="Start", command=disappearing_text_start)
start_button.grid(column=1, row=4, pady=20)
end_button = Button(text="Save", command=disappearing_text_end)
end_button.grid(column=2, row=4, pady=20)
entry.bind("<Any-KeyPress>", schedule_delete)
window.mainloop()

How do I get my function R.loop to respond to the button click

The following code starts a motor with a button click. I have tried uncommenting the R.loop() however it takes over the whole GUI and displays the command prompt with the code running. The code runs fine it's just a matter of getting the code to respond to the button clicks.
#begin GUI CODE
main = tk.Tk()
main.title('REGENERATOR')
main.geometry('800x800')
#createing global variable for motor run
global is_on
is_on=False
#The following code is to run/stop the motor
def motor_run():
global is_on
running_setup = R.setup()
#running_motor = R.loop()
def on_start():
if is_on:
Button_run.config()
motor_run = True
def on_stop():
if is_on:
Button_stop.config()
motor_run = False
frame5 = LabelFrame(main_canvas, text="Run Program", padx=5, pady=5, font=("Times", 20))
frame5.grid(row=3, column=0)
main_canvas.create_window((0,630), window=frame5, height=150, width= 200, anchor="s")
Button_run=Button(frame5, text="run", command=on_start, bg="white", fg="black", padx=5, pady=5, font=("Times", 16))
Button_run.grid(row="2", column="0")
Button_stop=Button(frame5, text="stop", command=on_stop, bg="white", fg="black", padx=5, pady=5, font=("Times", 16))
Button_stop.grid(row="2", column="1")
# function calls
on_stop()
on_start()
motor_run()
update_value()
update_bat_1()
update_bat_2()
update_gen1()
main_canvas.mainloop()
main.mainloop()
I think the issue may be in the placement of the Button_run.config(), It seems like you have the program asking itself for that command only if the motor is already on/off...you could try something like this...
def on_start():
When Button_run.config() = True
motor_run = True
def on_stop():
When Button_stop.config()
motor_run = False
Again, I'm not sure if this has anything to do with it because I'm unfamiliar with coding in robotics but i think that may have something to do with it.

root.update is lagging in linux but not windows

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

tkinter button animation stuck after using wait_window()

This is a dialog form class :
** update full workable source code showing the problem
from tkinter import *
class SGForm:
created_form = False
def __init__(self, root, title=""):
self.form = Toplevel(root)
self.form.wm_title(title)
self.input = dict()
self.var = StringVar()
SGForm.created_form = True
def getform(self):
return self.form
def addinput(self, name, text ,var = None):
p = Frame(self.form)
p.pack(side="top", fill="both", expand=True, padx=10, pady=10)
l = Label(p, text=text)
l.pack(side="left", fill="both", expand=True, padx=10, pady=10)
self.input[name] = Entry(p, textvariable=var)
self.input[name].pack(side="left", fill="both", expand=True, padx=10, pady=10)
def addbutton(self, text, signal, func):
p = Frame(self.form)
p.pack(side="top", fill="both", expand=True, padx=10, pady=10)
b = Button(p, text=text)
b.pack(side="left", fill="both", expand=True, padx=10, pady=10)
b.bind(signal, func)
def showandreturn(self):
value = dict()
value['firstname'] = self.var.get()
SGForm.created_form = False
return value
def closeform(self, event):
self.form.destroy()
def customform(self):
self.addinput('entfirstname', 'frist name', self.var)
self.addbutton('close','<Button-1>', self.closeform)
#example calling dialog class
root = Tk()
def evntshow(event):
form = SGForm(root)
form.customform()
root.wait_window(form.getform())
test = form.showandreturn()
print(test)
button = Button(root, text='show')
button.pack()
button.bind('<Button-1>', evntshow)
root.mainloop()
Each time the button get pressed eventaddperson get triggered, when exiting the function the button animation of the main window get stuck on press status, I am looking for a way to refresh the gui or what if I am doing something wrong how to fix it?
If I use command= instead of bind() then problem disappers
BTW: if you use command= then def evntshow()has to be without event
def evntshow(): # <--- without event
form = SGForm(root)
form.customform()
root.wait_window(form.getform())
test = form.showandreturn()
print(test)
# use `command=` instead of `bind('<Button-1>',...)
button = Button(root, text='show', command=evntshow)
button.pack()
I was experiencing kind of laggy button animations when using bind() as well, switching to command= made it a look a lot better!
from tkinter import *
import time
def func1():
print('waiting for 1 second...')
time.sleep(1)
def func2(event):
print('waiting for 1 second...')
time.sleep(1)
root = Tk()
# button animation runs smoothly
Button1 = Button(root, text="button with command=", command=func1)
Button1.pack()
Button2 = Button(root, text="button with bind()") # button animation does not occur
Button2.bind('<Button-1>', func2)
Button2.pack()
root.mainloop()
I am working with python 3.6 and windows 10

Tkinter GUI is not responding

I have only one while loop and the Tkonter say: GUI is not responding.
What I'm doing wrong ? I would like with button "Pause" break and
again with "button "Start" continue the program.
import Tkinter, time
root = Tkinter.Tk
class InterfaceApp(root):
def __init__ (self, parent):
root.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.but_state = 0
self.but_start = Tkinter.Button(self, text='Start', command=lambda: self.Start(), width=10)
self.but_pause = Tkinter.Button(self, text="Pause", command=lambda: self.Pause(), width=10)
self.but_stop = Tkinter.Button(self, text='Stop', command=lambda: self.Stop(), width=10)
self.but_start.grid(row=1, column=1, sticky='W')
self.but_pause.grid(row=1, column=2, sticky='W')
self.but_stop.grid(row=1, column=3, sticky='W')
def Start(self):
while True:
print "X"
time.sleep(2)
if self.but_state == 1:
break
else:
continue
def Stop(self):
self.but_state = 1
def Pause(self):
pass
if __name__ == "__main__":
app = InterfaceApp(None)
app.title("MPW4 microHP - Long Term Test")
app.mainloop()
First issue:
Using the while loop. To call a function again after it finished use
self.after(<time in ms>, <function to call>)
at the end of your def Start(self)
Would look like this:
# ...
def Start(self):
print("X")
if self.but_state == 0:
self.after(2000, self.Start)
# ...
Second Issue:
Do not use lambdas for simple calls. Use the name for the binding instead, just like #Parviz_Karimli pointed out.
def initialize(self):
self.but_state = 0
self.but_start = Tkinter.Button(self, text='Start', command=self.Start, width=10)
self.but_pause = Tkinter.Button(self, text="Pause", command=self.Pause, width=10)
self.but_stop = Tkinter.Button(self, text='Stop', command=self.Stop, width=10)
self.but_start.grid(row=1, column=1, sticky='W')
self.but_pause.grid(row=1, column=2, sticky='W')
self.but_stop.grid(row=1, column=3, sticky='W')
Your code is nonsense. You have to figure out how to define functions and use them properly first. I wrote a little example for you:
from tkinter import *
class App:
def __init__(self, master):
self.master = master
self.startb = Button(master, text="Start", command=self.startf)
self.startb.pack()
self.pauseb = Button(master, text="Pause", command=self.pausef)
self.pauseb.pack()
self.stopb = Button(master, text="Stop", command=self.stopf)
self.stopb.pack()
def startf(self):
print("Started")
self.after_id = self.master.after(1000, self.startf)
def pausef(self):
if self.startf is not None: # to handle any exception
self.master.after_cancel(self.after_id) # this will pause startf function -- you can start again
print("Paused")
def stopf(self):
if self.startf is not None:
self.master.after_cancel(self.after_id)
self.startf = None # this will stop startf function -- you cannot start again
print("Stopped")
root = Tk()
myapp = App(root)
root.mainloop()
Then you can modify this code -- change the behaviors of the functions etc. If you have a working piece of code which will behave as the "motor" function that does the core idea of your program, include that function in as well, and return it in the startf function, pause it in the pausef function, and finally, stop it in the stopf function.
P.S.: My code was written in Python 3.
EDIT: I completed the code and above is a working program that starts, pauses and stops depending on the button you click.

Categories

Resources