so basically what i want to do is that when i click the first button the program will keep printing hello world every one second until the second button is clicked.
here's what i did:
import concurrent.futures
import time
import tkinter as tk
call=""
def on_click1():
while call!="stop":
print("hello")
time.sleep(1)
def on_click2():
call="stop"
root=tk.Tk()
root.title("test")
with concurrent.futures.ProcessPoolExecutor() as executor:
if __name__ == '__main__':
btn1=tk.Button(root,text="button1",command=on_click1)
btn2=tk.Button(root,text="button1",command=on_click2)
btn1.pack()
btn2.pack()
root.mainloop()
but what happens is that when i click the first button the tkinter window/gui freezes and won't let me click the second window
It is generally not preferred to use while loops with sleep function, instead use .after() function in tkinter.
import concurrent.futures
import tkinter as tk
def on_click1():
global call #if not global, then this call variable will be treated as local
print("hello")
call = root.after(1000, on_click1) #every 1s on_click1 function is called
def on_click2():
global call #if not global, then this call variable will be treated as local
if call is not None:
root.after_cancel(call) #cancels the ongoing root.after() if it exists
call = None
root=tk.Tk()
root.title("test")
with concurrent.futures.ProcessPoolExecutor() as executor:
if __name__ == '__main__':
btn1=tk.Button(root,text="button1",command=on_click1)
btn2=tk.Button(root,text="button2",command=on_click2)
btn1.pack()
btn2.pack()
root.mainloop()
Related
Good morning,
What I am aiming for:
I want to create a button that when clicked triggers a call back function (even if that function is still running).
Module:
I am using tkinter
Problem:
When I trigger the button, the function runs, but then I cannot push the button again before the function has finished its run. I want to be able to push the button while the function is still running and have the function stop and run again from start.
My attempts:
I tried both a procedural and a OOP approach: both present the same problem
My attempt n1: Procedural approach
import time
import tkinter as tk # Import tkinter
from tkinter import ttk # Import ttk
def func():
for i in range (100):
print(i)
time.sleep(5)
win = tk.Tk() # Create instance of the Tk class
aButton = ttk.Button(win, text="Click Me!", command=func)
aButton.grid(column=0, row=0) # Adding a Button
win.mainloop() # Start GUI
My attempt n2: OOP approach
import time
import tkinter as tk # Import tkinter
from tkinter import ttk # Import ttk
class OOP():
def func(self):
for i in range (100):
print(i)
time.sleep(5)
def __init__(self):
win = tk.Tk() # Create instance of the Tk class
aButton = ttk.Button(win, text="Click Me!", command=self.func)
aButton.grid(column=0, row=0) # Adding a Button
win.mainloop() # Start GUI
oop = OOP()
Thanks
In tkinter, as with most GUI frameworks, there is a for loop running somewhere that constantly checks for user input. You are hijacking that loop to do your func processing. Tkinter can not check for user input, nor e.g. change the appearance of the button icon, because there is only one thread and you are using it.
What you will want to do is fire up a thread or process to do the work. There are lots of libraries to do parallel processing in elaborate ways if you have lots of background tasks but this will do for a single function (see also this answer).
import time
import tkinter as tk # Import tkinter
from tkinter import ttk # Import ttk
import threading
def button():
thread = threading.Thread(target=func, args=args)
thread.start()
def func():
for i in range (100):
print(i)
time.sleep(5)
win = tk.Tk() # Create instance of the Tk class
aButton = ttk.Button(win, text="Click Me!", command=button)
aButton.grid(column=0, row=0) # Adding a Button
win.mainloop() # Start GUI
If the process is long running, you might need to find a way to clean up the threads when they're done.
here is my sample code:
from time import sleep
import tkinter as tk
import threading
class Action:
counter = 0
def do_something(self):
while True:
print('Looping')
sleep(5)
action = Action()
root = tk.Tk()
button = tk.Button(root, text='pressme harder', command=threading.Thread(target=action.do_something()).start())
button.grid(row=1, column=0)
root.mainloop()
What am I expecting?
I'm expecting that as soon as I click the button in the UI an new thread is running, which is looping in the background and does not interfere with the UI (or later maybe other threads doing tasks in the background)
What is really happening?
When running the code, the method of the class is executeds immdiately and locking the procedure. root.mainloop() is never reached and therefore no UI is drawn
Alternatively I tried the following change:
button = tk.Button(root, text='pressme harder', command=threading.Thread(target=lambda: action.do_something()).start())
This behaves in the following (imho wrong) way:
The method is also called immediately, without pressing the button. This time the UI is drawn but seems the be locked by the thread (UI is slow/stuttering, pressing the buttom does not work most of the time)
Any Idea whats wrong there? Or how do I handle this in a more stable way?
You shouldn't try to start a thread directly in the button command. I suggest you create another function that launches the thread.
from time import sleep
import tkinter as tk
import threading
class Action:
counter = 0
def do_something(self):
while True:
print('Looping')
sleep(2)
print("Finished looping")
def start_thread(self):
thread = threading.Thread(target=self.do_something, daemon=True)
thread.start()
action = Action()
root = tk.Tk()
button = tk.Button(root, text='pressme harder', command=action.start_thread)
button.grid(row=1, column=0)
root.mainloop()
I am trying to create a new window in tkinter and then execute the function after that but the function gets executed first and then the new window pops up.
Here is the code snippet :
import tkinter as tk
import time
def loop():
for i in range(5):
time.sleep(1)
print(i)
def new():
new = tk.Toplevel(window)
new.geometry("450x250")
new.title('new window')
tk.Label(new, text="new one").place(x=150, y=40)
loop()
window = tk.Tk()
window.geometry("450x250")
window.title('main window')
button = tk.Button(window , text='button', width=20,command= new)
button.place(x=180,y=200)
window.mainloop()
numbers are printed from 1 to 9 and then the new window pops up.
Even if it requires destroying the main window, it's fine.
A little help would be appreciated.
Thank You.
Tkinter has two universal widget methods that will help do what you want. One is called wait_visibility() which will pause the program until the new Toplevel is visible. The second is called after() which allows the scheduling of call to a function after a delay specified in milliseconds. If the called function does the same thing, effectively it creates a loop.
Here's how to use them:
import tkinter as tk
import time
def loop(limit, i):
if i < limit:
print(i)
window.after(1000, loop, limit, i+1)
def new():
new = tk.Toplevel(window)
new.geometry("450x250")
new.title('new window')
tk.Label(new, text="new one").place(x=150, y=40)
window.wait_visibility(new)
loop(5, 0)
window = tk.Tk()
window.geometry("450x250")
window.title('main window')
button = tk.Button(window, text='button', width=20, command=new)
button.place(x=180,y=200)
window.mainloop()
I would like to implement a progress bar in Tkinter which fulfills the following requirements:
The progress bar is the only element within the main window
It can be started by a start command without the need of pressing any button
It is able to wait until a task with unknown duration is finished
The indicator of the progress bar keeps moving as long as the task is not finished
It can be closed by a stop command without the need of pressing any stop bar
So far, I have the following code:
import Tkinter
import ttk
import time
def task(root):
root.mainloop()
root = Tkinter.Tk()
ft = ttk.Frame()
ft.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD = ttk.Progressbar(ft, orient='horizontal', mode='indeterminate')
pb_hD.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD.start(50)
root.after(0,task(root))
time.sleep(5) # to be replaced by process of unknown duration
root.destroy()
Here, the problem is that the progress bar does not stop after the 5s are over.
Could anybody help me finding the mistake?
Once the mainloop is active, the script wont move to the next line until the root is destroyed.
There could be other ways to do this, but I would prefer doing it using threads.
Something like this,
import Tkinter
import ttk
import time
import threading
#Define your Progress Bar function,
def task(root):
ft = ttk.Frame()
ft.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD = ttk.Progressbar(ft, orient='horizontal', mode='indeterminate')
pb_hD.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD.start(50)
root.mainloop()
# Define the process of unknown duration with root as one of the input And once done, add root.quit() at the end.
def process_of_unknown_duration(root):
time.sleep(5)
print 'Done'
root.destroy()
# Now define our Main Functions, which will first define root, then call for call for "task(root)" --- that's your progressbar, and then call for thread1 simultaneously which will execute your process_of_unknown_duration and at the end destroy/quit the root.
def Main():
root = Tkinter.Tk()
t1=threading.Thread(target=process_of_unknown_duration, args=(root,))
t1.start()
task(root) # This will block while the mainloop runs
t1.join()
#Now just run the functions by calling our Main() function,
if __name__ == '__main__':
Main()
Let me know if that helps.
I want to track my mouse-position and show that in a tiny window.
For that, I created this piece of code:
#! /usr/bin/python
from Tkinter import *
from Xlib import display
def mousepos():
data = display.Display().screen().root.query_pointer()._data
return data["root_x"], data["root_y"]
root = Tk()
strl = "mouse at {0}".format(mousepos())
lab = Label(root,text=strl)
lab.pack()
root.title("Mouseposition")
root.mainloop()
This little script shows the mouse-position on startup but doesn't refresh it on mouse-movement. I don't get behind it (did I say that I'm new to python?).
I think I have to use an event from Xlib that tells my script when the mouse is moving...
How do I refresh my mouse-position?
Use root.after to call update periodically.
Use strl = tk.StringVar() and tk.Label(...,textvariable=strl) to
allow the Label text to change.
Call strl.set() to change the Label text.
A default value for screenroot equal to display.Display().screen().root was added
to mousepos so that most of that long chain of function calls are
not repeated every time mousepos is called. Calling mousepos() without any arguments will continue to work as usual.
import Tkinter as tk
import Xlib.display as display
def mousepos(screenroot=display.Display().screen().root):
pointer = screenroot.query_pointer()
data = pointer._data
return data["root_x"], data["root_y"]
def update():
strl.set("mouse at {0}".format(mousepos()))
root.after(100, update)
root = tk.Tk()
strl = tk.StringVar()
lab = tk.Label(root,textvariable=strl)
lab.pack()
root.after(100, update)
root.title("Mouseposition")
root.mainloop()