I have a Python program using Tkinter to show a value (var peso inside capturarpeso() function) in realtime.
But the while loop in capturarPeso() doesn't work, the loop only works the first time then the script is "waiting".
If I remove the TK component, it works perfectly. I simplified the script:
import tkinter as tk
from tkinter import *
import threading
import random
def capturarPeso():
global peso
while True:
peso = random.randrange(0, 101, 2)
print (peso)
return(peso)
def capturarPesoHilo():
hilo = threading.Thread(target=capturarPeso, name=None, group=None, args=(), kwargs=None, daemon=True)
hilo.start()
hilo.join()
class ActualizarPeso(Label):
def __init__(self, parent, *args, **kwargs):
Label.__init__(self, parent, *args, **kwargs)
self.tick()
def tick(self):
self.config(text= peso)
self.after(500, self.tick)
capturarPesoHilo()
window = tk.Tk()
window.title('Capturador pesos')
window.resizable(width=False, height=False)
pesoLabel = ActualizarPeso(window, font="Arial 60", fg="red", bg="black", width=8, height= 1)
pesoLabel.grid(row=15, column=0)
window.mainloop()
Any ideas on how to continue? Thank you
The function captuarPeso() has a return statement which will exit the while loop, this is why you only get 1 number printed to the screen.
Removing the return makes it so your program is stuck in that while loop which only prints peso because when you do hilo.join() to a thread it's actually waiting for the thread to exit before continuing, and since we got rid of the return in the first step, the thread never exits and so it's again stuck in a loop. To fix this I changed your while loop to while self.peso != -999: and after calling .mainloop() you set self.peso = -999 which will tell the program: the user has exited the Tkinter interface, exit my loop.
Since you used a class to put some of your tkinter gui in, why not put it all in? Generaly most people would put the entire tkinter interface in a class, I've gone ahead and restructured the program for you but tried to leave as much as the original by itself so you can analyze it and see how it works.
import tkinter as tk
import threading
import random
import time
class ActualizarPeso:
def __init__(self):
self.window = tk.Tk()
self.window.title('Capturador pesos')
self.window.resizable(width=False, height=False)
self.pesoLabel = self.crearLabel()
self.peso = 0
self.tick()
hilo1 = self.capturarPesoHilo()
self.window.mainloop()
self.peso = -999
hilo1.join()
def crearLabel(self):
pesoLabel = tk.Label(self.window, font="Arial 60", fg="red", bg="black", width=8, height=1)
pesoLabel.grid(row=15, column=0)
return pesoLabel
def tick(self):
self.pesoLabel.config(text=self.peso)
self.pesoLabel.after(500, self.tick)
def capturarPeso(self):
while self.peso != -999:
self.peso = random.randrange(0, 101, 2)
print(self.peso)
time.sleep(1)
def capturarPesoHilo(self):
hilo = threading.Thread(target=self.capturarPeso)
hilo.start()
return hilo
ActualizarPeso()
Let me know if you need something explained, and Happy Holidays!
Related
I'm new to tkinter and I'm looking for a button that executes a function in loop as soon as the button is pressed; when released the button, function will no more be executed
I currently have a tk.Button(self, text="S+", command=sch_plus_callback) with
def sch_plus_callback():
self.ser.write(b'\x02\x56\x81\x80\x80\x80\x80\x80\x39\x35\x04')
Now I would like some sort of button that does
def sch_plus_callback():
while button_is_pressed:
self.ser.write(b'\x02\x56\x81\x80\x80\x80\x80\x80\x39\x35\x04')
Is there a way?
My full code is
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# self.insert_lengh_text_label = tk.Label(self, text="Max Packet Length")
self.sch_plus_button = tk.Button(self, text="S+", command=self.sch_plus_callback)
self.sch_plus_button.pack()
def sch_plus_callback(self):
self.ser.write(b'\x02\x56\x81\x80\x80\x80\x80\x80\x39\x35\x04')
I'm now using these methods but it's more a workaround then an actual solution
def sch_plus_stop_callback(self, event):
self.after_cancel(repeat)
def sch_plus_callback(self, *args,):
global repeat
try:
repeat = self.after(300, self.sch_plus_callback, args[0])
except:
pass
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()
self.ser.write(b'\x02\x56\x81\x80\x80\x80\x80\x80\x39\x35\x04')
In the function, catch the event of releasing the left mouse button "ButtonRelease-1",using the method widget.bind(event, handler), by which you interrupt the loop.
https://python-course.eu/tkinter/events-and-binds-in-tkinter.php#:~:text=Events%20can%20be%20key%20presses,and%20methods%20to%20an%20event.&text=If%20the%20defined%20event%20occurs,called%20with%20an%20event%20object.
So that the loop does not block the tkinter, you need to run it through after(time, callback).
Tkinter loop link https://tkdocs.com/tutorial/eventloop.html.
Code as a sample:
from tkinter import *
def sch_plus_callback(event):
global repeat
repeat = root.after(500, sch_plus_callback, event)
text.insert(END, "Bye Bye.....\n")
def stop_callback(event):
root.after_cancel(repeat)
root = Tk()
text = Text(root)
text.insert(INSERT, "Hello.....\n")
text.pack()
button = Button(root, text="S+")
button.pack()
button.bind('<Button-1>', sch_plus_callback)
button.bind('<ButtonRelease-1>', stop_callback)
root.mainloop()
def sch_plus_stop_callback(self, event):
self.after_cancel(self.repeat)
def sch_plus_callback(self, event):
try:
self.repeat = self.after(300, self.sch_plus_callback, event)
I have this skeleton of a game in two files.
First file mygui.py:
from tkinter import Tk, Label, Button
from mygame import p1, play_game
def rungame():
play_game()
gui_widgets()
root = Tk()
def gui_widgets():
health_label = Label(root, text=f"health: {p1.health}")
health_label.grid(column=0, row=0)
mana_label = Label(root, text=f"mana: {p1.mana}")
mana_label.grid(column=0, row=1)
mybtn = Button(root, text="RUN GAME", command=rungame)
mybtn.grid(column=0, row=2)
gui_widgets()
root.mainloop()
Seccond file mygame:
import time
class Player:
def __init__(self, health, mana):
self.health = health
self.mana = mana
def play_game():
p1.health -= 1
time.sleep(0.5)
p1.mana -= 3
time.sleep(0.5)
p1.mana += 1
p1 = Player(10, 15)
The game shows the begining statistic on gui and after clicking button it shows the end result. I want gui to show every p1 atribute change made in play_game() as they are happening. How would I do this?
Edit: I am keen on keeping two seperate files so that program would be more scalable.
I am not sure if this is possible, but I would figure that if I could run gui_widgets() continuosly (for example every 0,1 secconds) It would probalby work as intended. Still I understand that this may be impossible since tkinter seems to wait everytime when play_game()is running, witch takes at least one seccond to finish. So in that case they should probalby run alongside each other (if that even is a thing).
There maybe lot of ways for it.
Here's one way
Define __setattr__ method to send signal to buffer to update GUI.
sleep in main thread may block your GUI to update, so another thread used here.
A fixed and short timer to fresh/update GUI from the content of buffer.
# mygui.py
import threading
from tkinter import Tk, Label, Button
from mygame import p1, play_game, buffer
def rungame():
threading.Thread(target=play_game, args=(), daemon=True).start()
def update():
if buffer:
attribute, value = buffer.pop(0)
if attribute == 'health':
health_label.configure(text=f"health: {value}")
health_label.update()
elif attribute == 'mana':
mana_label.configure(text=f'mana: {value}')
mana_label.update()
root.after(100, update)
def gui_widgets():
health_label = Label(root, text=f"health:", width=20)
health_label.grid(column=0, row=0)
mana_label = Label(root, text=f"mana:", width=20)
mana_label.grid(column=0, row=1)
mybtn = Button(root, text="RUN GAME", command=rungame)
mybtn.grid(column=0, row=2)
return health_label, mana_label
root = Tk()
health_label, mana_label = gui_widgets()
root.after(100, update)
root.mainloop()
#my game.py
import time
class Player(object):
def __init__(self, health, mana):
self.health = health
self.mana = mana
def __setattr__(self, attribute, value):
super().__setattr__(attribute, value)
buffer.append((attribute, value))
def play_game():
for i in range(10):
p1.health = i
time.sleep(0.2)
p1.mana = 9 - i
time.sleep(0.2)
buffer = []
p1 = Player(10, 15)
I was trying to make an auto clicker using Mouse and keyboard modules and tkinter as the gui and wrote this code
#Import
import tkinter as tk
import random as r8
import keyboard as kb
import mouse as ms
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
self.joesd()
def create_widgets(self):
self.joe = tk.Frame(self)#main frame
self.joe.pack(fill=tk.BOTH)
self.lbl = tk.Label(self.joe, text='Example text', height=5, width=30)
self.lbl.pack(side="top")# where the label will be located
self.lb = tk.Label(self.joe, text='Example Text', height=5, width=35)
self.lb.pack(side="top")# where the label will be located
def joesd(self):
while True:
if kb.is_pressed('q') == True:
ms.press('left')
ms.release('left')
root = tk.Tk()
app = Application(master=root)
app.mainloop()
Then I noticed that the gui never appears but will appear if I removed
def joesd(self):
while True:
if kb.is_pressed('q') == True:
ms.press('left')
ms.release('left')
What should I do?
The reason why GUI does not show up is that before the code hits mainloop() it goes into a infinite loop(while loop) and it cannot reach mainloop and hence window does not show up and events are not processed. So what you should do is get rid of while. One way is to use after() method to emulate while.
def joesd(self):
if kb.is_pressed('q'):
ms.press('left')
ms.release('left')
self.after(100,self.joesd)
This will repeat the function every 100 ms, you can reduce it to 1ms too. But make sure it is not too much for the system to handle.
You should not use while loop in a tkinter application. You can register a callback using kb.on_press_key() instead:
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
...
#self.joesd() # <- not necessary
kb.on_press_key("q", self.on_key_press)
...
def on_key_press(self, event):
#print("q pressed", event)
ms.press('left')
ms.release('left')
# above two lines can be replaced by ms.click('left')
I'm writing a program in tkinter using Progressbar. But there is a problem when I added stop function it doesn't work. When I press "stop" button nothing happens, it should stop loading progressbar. I use Python version 3.8. The code below:
from tkinter import *
from tkinter import ttk
import time
root = Tk()
def run():
pb['maximum']=100
for i in range(101):
time.sleep(0.05)
pb['value']=i
pb.update()
def stop():
pb.stop()
runbutt = Button(root,text="Runprogr",command=run)
runbutt.pack()
stopbutt = Button(root,text="Stopbut",command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root,length=300,orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()
The cause is that pb.stop couldn't stop the function in run.it will also increase by itself.
You could use .after(ms, callback) to add the value(then you no longer need to use time.sleep()).
If you want to stop it,use .after_cancel():
from tkinter import *
from tkinter import ttk
import time
root = Tk()
root.add_value = None
def run():
def add():
if pb['value'] >= 100:
return
pb['value'] += 1
root.add_value = root.after(50, add)
if root.add_value: # to prevent increasing the speed when user pressed "Runprogr" many times.
return
root.add_value = root.after(50, add)
def stop():
if not root.add_value: # to prevent raising Exception when user pressed "Stopbut" button many times.
return
root.after_cancel(root.add_value)
root.add_value = None
runbutt = Button(root, text="Runprogr", command=run)
runbutt.pack()
stopbutt = Button(root, text="Stopbut", command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root, length=300, orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()
I have a Tkinter program which I want to pause for 3 seconds.
time.sleep doesn't work and the after method doesn't do exactly what I want to.
here is an example code:
from Tkinter import *
def waithere():
print "waiting..."
root = Tk()
print "1"
root.after(3000,waithere)
print "2"
root.mainloop()
output:
1
2
*3 seconds*
waiting...
the output i want to have:
1
waiting...
*3 seconds*
2
thanks.
Normally it's a very bad idea to have a GUI wait for something. That's imply not how event-based programs work. Or more accurately, GUIs are already in a perpetual wait state, and you don't want to block that with your own waiting.
That being said, tkinter has a way to wait until certain things happen. For example, you can use one of the "wait" functions, such as wait_variable, wait_window, or wait_visibility.
Assuming that you wanted waithere to do the waiting, you could use wait_variable to do the waiting, and after to set the variable after a given amount of time.
Here's the solution based on your original code:
from Tkinter import *
def waithere():
var = IntVar()
root.after(3000, var.set, 1)
print("waiting...")
root.wait_variable(var)
root = Tk()
print "1"
waithere()
print "2"
root.mainloop()
The advantage to using these methods is that your code is still able to respond to events while it is waiting.
I found a way like that, i hope it helps you:
from tkinter import *
def waitToShow():
index = 1
while index < 11:
l1.config(text=index)
l1.after(1000)
l1.update()
index += 1
win = Tk()
l1 = Label(win)
l1.pack()
waitToShow()
win.mainloop()
Just for future reference, refrain from using long or infinite loops in Tkinter; they will prevent the UI from responding to user events (AKA freezing). The method I was taught was to periodically update the field using the after() function.
The after() function creates an alarm-callback meaning when called (with the right parameters) it will queue a call to the target method (in the example below def update(self) with our entered delay. You can use a boolean in the class to exit the loop. Create on on __init__ and then when set to False don't call after() anymore.
Here is an example creating a class inheriting Tkinter.Frame to inherit the functionality.
try:
import tkinter as tk
except:
import Tkinter as tk
import datetime
class DelayedUpdateUI(tk.Frame):
def __init__(self, master=None, **kw):
# Create widgets, if any.
tk.Frame.__init__(self, master=master, **kw)
self.timeStr = tk.StringVar()
self.lblTime = tk.Label(self, textvariable=self.timeStr)
self.lblTime.grid()
# Call update to begin our recursive loop.
self.update()
def update(self):
self.timeStr.set(datetime.datetime.now())
# We use after( milliseconds, method_target ) to call our update
# method again after our entered delay. :)
self.after(1000, self.update)
if __name__ == '__main__':
root = tk.Tk()
DelayedUpdateUI(root).grid()
root.mainloop()
A suggestion based on Bryan's answer:
I understand the recommended way from an event-based perspective, but it does not feel very intuitive to me. I have to look up the trick every time I need it. Therefore, I have created a small mixin class that makes usage a bit more intuitive:
import tkinter as tk
class TkWaitMixin:
"""Simple wait timer that makes Tk waiting functionality
more intiutive. Applies the recommended way according to
https://stackoverflow.com/a/51770561/12646289.
"""
def start_wait_timer(self, milliseconds):
self.resume = tk.BooleanVar(value=False)
self.master.after(milliseconds, self.resume.set, True)
# Assume master attribute is available:
# https://stackoverflow.com/a/53595036/12646289
def wait_on_timer(self):
self.master.wait_variable(self.resume)
Example usage:
import tkinter as tk
class MyWindow(tk.Tk, TkWaitMixin):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
self.start_wait_timer(milliseconds)
self.message_label['text'] = message
self.wait_on_timer()
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
Obviously, this will only be of use if you use classes in your tkinter code. Also note that the master attribute should be available in the main class to which the mixin is added.
Edit
Alternatively, usage can be made even easier with a context manager:
import tkinter as tk
class TkWait:
def __init__(self, master, milliseconds):
self.duration = milliseconds
self.master = master
def __enter__(self):
self.resume = tk.BooleanVar(value=False)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.master.after(self.duration, self.resume.set, True)
self.master.wait_variable(self.resume)
Note that the waiting starts when the context manager is exited.
Example usage:
import tkinter as tk
class MyWindow(tk.Tk):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
with TkWait(self.master, milliseconds):
self.message_label['text'] = message
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
You forgot to do the () at root.after(3000,waithere <<<<-)
from tkinter import *
def waithere():
print("waiting...")
root = Tk()
print("1")
root.after(3000,waithere())
print("2")
root.mainloop()