Holding Down Button and window updating issue - python

When I keep holding down Ctrl+Up, I need that the label to update on intervals of 1000 ms. (If I hold Ctrl+Up down for 5,2 seconds, the commands should run 5 times.)
The after method does not seem to work on this. It also acts weird, as if it records how many times I pressed the key and keeps looping that even after Ctrl+Up is unpressed.
from Tkinter import *
root = Tk()
def start(event):
global x
x = x+1
x_var.set(x)
root.after(1000, lambda: start(event))
x=1
x_var=IntVar()
x_var.set(x)
r = Label(root, textvariable=x_var)
r.pack()
root.bind('<Control-Up>', start)
root.mainloop()

Here is a way to run a command every 1000 ms while holding Control + Up:
Store and update the current pressed statuses of Control and Up.
Have an .after() loop which calls itself every 1000 ms, in which you run the desired command if both Control and Up are currently pressed.
Code
import Tkinter as tk
root = tk.Tk()
x = 1
x_var = tk.IntVar()
x_var.set(x)
r = tk.Label(root, textvariable=x_var)
r.pack()
isPressed = {"Control_L": False, "Up": False}
def update_key_status(key, value):
global isPressed
isPressed[key] = value
# Make the Press/Release events of both keys update the "isPressed" dictionary
for key in ["Up", "Control_L"]:
root.bind('<KeyPress-{}>'.format(key),
lambda evt, key=key: update_key_status(key, True))
root.bind('<KeyRelease-{}>'.format(key),
lambda evt, key=key: update_key_status(key, False))
def increment_x():
global x, x_var
x += 1
x_var.set(x)
def start():
if (isPressed["Control_L"] and isPressed["Up"]):
increment_x()
root.after(1000, start)
start()
root.mainloop()

The after method does not seem to work on this.
No, it is working as your program asks it to do.
It also acts weird, as if it records how many times I pressed the key
and keeps looping that even after Ctrl+Up is unpressed.
It is not that weird. Throw a glance to a given documentation and you will read:
The callback is only called once for each call to this method. To keep
calling the callback, you need to reregister the callback inside
itself
The bold text is what you exactly did in the start() function. So the logic behavior with your current code is that, once you press Ctrl + Up, that callback will wait 1000 milliseconds to be executed, and after that it will continue running forever (well, until you end the mainloop() event)
So to make your program increment the label's content only once after each press on the keys you specified, you need to avoid the design written in bold text above. A solution then could consist in creating a specific incrementalist function like this:
def increment_x():
global x, x_var
x += 1
x_var.set(x)
Then use this function as a callback within your start() function:
def start(event):
root.after(1000, increment_x)
So your code becomes:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from Tkinter import *
root = Tk()
def start(event):
root.after(1000, increment_x)
def increment_x():
global x, x_var
x += 1
x_var.set(x)
x=1
x_var=IntVar()
x_var.set(x)
r = Label(root, textvariable=x_var)
r.pack()
root.bind('<Control-Up>', start)
root.mainloop()
P.S. Please follow PEP8

Related

tkinter .after() second and minute

hey guys i have a problem im making a timer in tkinter but i cant use time.sleep() so i use .after() and i have new problem,I made an entry that I want the entry number to be * 60 and after the set time, a text will be written that says >> time is over! ,but then, how should that 60 be converted into seconds? my code:
from tkinter import *
from playsound import playsound
from time import sleep
import time
def jik():
a = int(text.get())
app.after(a * 600)
Label(app,text="time is over").pack()
app = Tk()
app.minsize(300,300)
app.maxsize(300,300)
text = Entry(app,font=20)
text.pack()
Button(app,text="start",command=jik).pack()
app.mainloop()
For example, if I press the number 1, it >>time is over in a fraction of a second
The after command takes input in milliseconds, so multiply it by 1000 to convert it to seconds.
Additionally, I just made a small example that displays the countdown for you as the clock ticks down:
# Usually it is a good idea to refrain from importing everything from the tkinter
# package, as to not pollute your namespace
import tkinter as tk
root = tk.Tk() # Customary to call your Tk object root
entry = tk.Entry(root)
entry.pack()
curtime = tk.Label(root)
curtime.pack()
is_running = False # Global variable to ensure we can't run two timers at once
def countdown(count):
global is_running
if count > 0:
curtime['text'] = f'{count:.2f}' # Update label
# Call the countdown function recursively until timer runs out
root.after(50, countdown, count-0.05)
else:
curtime['text'] = 'time is over'
is_running = False
def btn_press():
global is_running
if is_running:
return
cnt = int(entry.get())
is_running = True
countdown(cnt)
tk.Button(root, text='start', command=btn_press).pack()
root.minsize(300, 300)
root.maxsize(300, 300)
root.mainloop()
.after function takes two arguments, the first is the time in milliseconds, that is 1000 milliseconds is equal to one second, the second argument is a function to call after that time has passed, simply define what to do after the time has passed in a second function, and use it as a second argument as follows.
from tkinter import *
from playsound import playsound
from time import sleep
import time
MILLISECOND_TO_SECOND = 1000
def jik():
a = int(text.get())
app.after(a * MILLISECOND_TO_SECOND, show_label)
def show_label():
Label(app,text="time is over").pack()
app = Tk()
app.minsize(300,300)
app.maxsize(300,300)
text = Entry(app,font=20)
text.pack()
Button(app,text="start",command=jik).pack()
app.mainloop()

How do I delay a line of code in tkinter without it crashing? [duplicate]

Hey I am new to python and am using tkinter for my gui. I am having trouble using the "after" method.
The goal is to make a random letter appear every 5 seconds.
Here is my code:
import random
import time
from tkinter import *
root = Tk()
w = Label(root, text="GAME")
w.pack()
frame = Frame(root, width=300, height=300)
frame.pack()
L1 = Label(root, text="User Name")
L1.pack(side=LEFT)
E1 = Entry(root, bd =5)
E1.pack(side=LEFT)
tiles_letter = ['a', 'b', 'c', 'd', 'e']
while len(tiles_letter) > 0:
rand = random.choice(tiles_letter)
tile_frame = Label(frame, text=rand)
tile_frame.pack()
frame.after(500)
tiles_letter.remove(rand) # remove that tile from list of tiles
root.mainloop()
can someone please help me --- the problem is definitely frame.after(500):
i'm not sure if it is correct to use "frame" and I don't know what which argument follows the 500.
Thanks
You need to give a function to be called after the time delay as the second argument to after:
after(delay_ms, callback=None, *args)
Registers an alarm callback that is called after a given time.
So what you really want to do is this:
tiles_letter = ['a', 'b', 'c', 'd', 'e']
def add_letter():
rand = random.choice(tiles_letter)
tile_frame = Label(frame, text=rand)
tile_frame.pack()
root.after(500, add_letter)
tiles_letter.remove(rand) # remove that tile from list of tiles
root.after(0, add_letter) # add_letter will run as soon as the mainloop starts.
root.mainloop()
You also need to schedule the function to be called again by repeating the call to after inside the callback function, since after only executes the given function once. This is also noted in the documentation:
The callback is only called once for each call to this method. To keep
calling the callback, you need to reregister the callback inside
itself
Note that your example will throw an exception as soon as you've exhausted all the entries in tiles_letter, so you need to change your logic to handle that case whichever way you want. The simplest thing would be to add a check at the beginning of add_letter to make sure the list isn't empty, and just return if it is:
def add_letter():
if not tiles_letter:
return
rand = random.choice(tiles_letter)
tile_frame = Label(frame, text=rand)
tile_frame.pack()
root.after(500, add_letter)
tiles_letter.remove(rand) # remove that tile from list of tiles
Live-Demo: repl.it
I believe, the 500ms run in the background, while the rest of the code continues to execute and empties the list.
Then after 500ms nothing happens, as no function-call is implemented in the after-callup (same as frame.after(500, function=None))
after is used to delay execution of the program or to execute a command in background sometime in the future. But you can build a loop inside the mainloop by calling itself.
import tkinter as tk #import tkinter
import datetime #import datetime for our clock
def tick(): #function to update the clock
showed_time = clock['text'] #current showed time
current_time = datetime.datetime.now().strftime("%H:%M:%S") #real time
if showed_time != current_time: #if the showed time is not the real time
clock.configure(text=current_time) #update the label with the current time
clock.after(1000, tick) #call yourself in 1000ms (1sec.) again to update the clock
return None
root=tk.Tk()
clock = tk.Label(root)
clock.pack()
tick()
root.mainloop()
In the above script we had built a digital clock and get in touch with the after method. The after method is nothing but an interval and on the end of that interval we want that something happen.
To learn more about this basic widget method [click]
after(delay_ms, callback=None, args)
This method registers a callback function that will be called after a
given number of milliseconds. Tkinter only guarantees that the
callback will not be called earlier than that; if the system is busy,
the actual delay may be much longer.
import tkinter as tk
import datetime
def tick():
showed_time = clock['text']
current_time = datetime.datetime.now().strftime("%H:%M:%S")
if showed_time != current_time:
clock.configure(text=current_time)
global alarm #make sure the alarm is reachable
alarm = clock.after(1000, tick)#assign the alarm to a variable
return None
def stop():
stop.after_cancel(alarm) #cancel alarm
root=tk.Tk()
clock = tk.Label(root)
clock.pack()
stop = tk.Button(root, text='Stop it!', command=stop)
stop.pack()
tick()
root.mainloop()
Here we have the same code but with the ability to cancel our loop with the after_cancel method of tkinter. You dont need to global the alarm inside a class. self.alarm = self.clock.after(...) works fine.
after_cancel(id)
Cancels an alarm callback.
id
Alarm identifier.

tkinter wont refresh widget

I want to write a program that has only a button, and after pressing that, program will start making 3 labels and then change the color of each one every 1 second only once.
It looks very simple and I wrote the following code :
import tkinter as tk
from time import sleep
def function():
mylist=list()
for i in range(3):
new_label=tk.Label(window,text='* * *',bg='yellow')
new_label.pack()
mylist.append(new_label)
print('First state finished')
sleep(1)
for label in mylist:
label.config(bg='red')
print('one label changed')
sleep(1)
window = tk.Tk()
window.geometry('300x300')
btn=tk.Button(window,text='start',command=function)
btn.pack()
tk.mainloop()
First the app is look like this (that is OK):
Second its look like this (its not OK because its print on the terminal but didn't update the lable) :
Third its look like this (at the end the app must be look like this and its OK) :
But I need to see the changes in the moment and use sleep for that reason.
Thank you All.
I would recommend to use .after(delay, callback) method of the tkinter to set the colour.
Hope this is what you want.
import tkinter as tk
def start():
global mylist
mylist = list()
for i in range(3):
new_label = tk.Label(window, text='* * *', bg='yellow')
new_label.pack()
mylist.append(new_label)
delay = 1000 # delay in seconds
for label in mylist:
# Additional delay so that next color change
# is scheduled after previous label color change
delay += 1000
schedule_color_change(delay, label)
def schedule_color_change(delay, label):
print("schedule color change for:", label)
label.after(delay, set_color, label)
def set_color(label):
print("setting color of:", label)
label.config(bg="red")
window = tk.Tk()
window.geometry('300x300')
btn = tk.Button(window, text='start', command=start)
btn.pack()
tk.mainloop()
Problem
The problem is your sleep(1), because it's a function that suspends the execution of the current thread for a set number of seconds, so it's like there is a stop to the whole script
Solution
The solution is to instantiate Thread with a target function, call start(), and let it start working. So you have to use timer which is included in the threading, then a timer from the threading module (import threading)
Inside the first "for" loop, remove your sleep(1) and write for example Time_Start_Here = threading.Timer (2, function_2) and then of course Time_Start_Here.start() to start.
start_time=threading.Timer(1,function_2)
start_time.start()
Instead you have to remove the second "for" loop and write what's inside ... inside the new function that will be called. Next you need to create the function
def function_2():
for label in mylist:
label.config(bg='red')
label.pack()
print('one label changed')
As Meritor guided me, I followed the after method and wrote the following recursive code without sleep :
import tkinter as tk
def recursive(i, listt):
lbl = listt[i]
if i >= 0:
lbl.config(bg='orange')
i -= 1
lbl.after(500, recursive, i, listt)
def function():
mylist = list()
for i in range(3):
new_label = tk.Label(window, text='* * *', bg='yellow')
new_label.pack()
mylist.append(new_label)
print('all label created')
# 2 is length of list minus 1
recursive(2, mylist)
window = tk.Tk()
window.geometry('300x300')
tk.Button(window, text='start', command=function).pack()
tk.mainloop()
Most likely my code is not optimized because it uses recursive and if you know anything better please tell me

How do I get the value in event hander function (tkinter)?

I want to get the value in the event hander function.
If clicks prints in event hander function, then it will show 1, 2, 3, 4... by every click.
import tkinter as tk
root = tk.Tk()
clicks = 0
def click(event):
global clicks
clicks += 1
print(clicks)
button = tk.Button(root, text="click me!")
button.pack()
button.bind("<Button-1>", click)
root.mainloop()
If I print it in global, it will show 0 forever. But I want it prints 1, 2, 3 by every click not always 0.
def click(event):
global clicks
clicks += 1
return clicks
print(clicks)
This case is simplified. Actually, my real case is that there are many different items(like listbox, button1, button2...) event hander function(def xxx(event)), and I need to get some value in each function after trigger their events. Then do some conditional expressions in global.
I knew the methods like class, global variable, init, (self, master),
I think global variable is the simplest method but I really don't know how to return it in event hander function and use it in global.
Thanks for Help !!!
The reason why it always prints 0 is that the program resets everything every after you close the tk window. If you want to save the values even after you close the window, maybe save it in the csv file or put it in the database. Does this help? or am I too far from what you're trying to say?
Your first set of code should work with the global variable like what TheLizzard said. Like what Matiiss said, if you place the print outside the function, it would be called once and never be called again.
As for what you are looking for, I'm assuming you want the button to be like a next button going through each item from the listbox and each time the next button hit one of the item in the listbox, it calls another function to obtain whatever that item has. Hopefully this code is what you are looking for or give you an idea.
import tkinter as tk
root = tk.Tk()
lst = {'a':'a value', 'b':'b value', 'c':'c value'}
clicks = -1
def print_text(event):
keys = list(lst.keys())
print(lst[keys[lstbx.curselection()[0]]])
def click():
global clicks
lstbx.selection_clear(0, 'end')
clicks = clicks + 1 if clicks < 2 else 0 # loop the list when reach end
lstbx.selection_set(clicks)
lstbx.event_generate('<<ListboxSelect>>') # force call function
btn = tk.Button(root, text='Next', command=click)
btn.pack()
lstbx = tk.Listbox(root, selectmode='single')
lstbx.insert(0, *lst.keys())
lstbx.pack()
lstbx.bind('<<ListboxSelect>>', print_text)
root.mainloop()

How to add a delay between 2 text messages being displayed in Tkinter Python?

So my aim is to use a single function to show a text message upon a button click. Then there should be a delay and then another text message should be displayed.
The game is a dice game that should show 'Rolling...' upon a button click. And then after a while, it should display a random number.
I tried both .sleep() and .after() and both of them resulted in my program not showing the before delay text. Here's my code:
# Imports
import tkinter as tk
from random import randrange
import time
# Global variables
# SIDES is a constant
SIDES = 12
# Functions
def func():
display["text"] = "Rolling..."
window.after(2000)
display["text"] = str(randrange(SIDES) + 1)
# Main program loop
window = tk.Tk()
display = tk.Label(window, text="Press the button \nto roll the dice.", width=20, height=3)
button = tk.Button(window, text="Roll", command=func)
display.pack()
button.pack(pady=10)
window.mainloop()
Any help would be much appreciated!
Try:
window.after(2000, lambda: display.config(text=randrange(SIDES) + 1))
instead of the:
window.after(2000)
display["text"] = str(randrange(SIDES) + 1)
The problem is that when you sleep in the function, the tkinter main loop is interrupted and the screen isn't updated. (window.after() is just a gloified sleep here). The correct solution is to pass a callback to after, which will make it immediately return and call the callback later:
def func():
display["text"] = "Rolling..."
window.after(2000, lambda: display.__setitem__("text", str(randrange(SIDES) + 1)))
(Note that the call to __setitem__ is a direct one-liner lambda translation. This is not good design.)

Categories