I'm trying to make a button that can pause and resume the loop.
In code:
for index in range(10):
print index
// Runs until here, and pause
// Button pressed
print index + 10
// Runs until here, and pause
// Button pressed
In terminal:
0
// Button pressed
10
// Button pressed
1
// Button pressed
11
...
9
// Button pressed
19
// Button pressed
Is there a way that I can do pause and resume the loop with a button?
You can use generators for this by calling next() on each button click.
A little example of how:
import tkinter as tk
def plusten(x):
i = 0
while i<x:
yield i
yield i+10
i += 1
def next_item():
if gen:
try:
lbl["text"] = next(gen) #calls the next item of generator
except StopIteration:
lbl["text"] = "End of iteration" #if generator is exhausted, write an error
else:
lbl["text"] = "start loop by entering a number and pressing start loop button"
def start_gen():
global gen
try:
gen = plusten(int(ent.get()))
lbl["text"] = "loop started with value: " + ent.get()
except ValueError:
lbl["text"] = "Enter a valid value"
gen = None
root = tk.Tk()
ent = tk.Entry()
ent.pack()
tk.Button(root, text="start loop", command=start_gen).pack()
tk.Button(root, text="next item", command=next_item).pack()
lbl = tk.Label(root, text="")
lbl.pack()
root.mainloop()
'''
Import the following in the code
from tkinter import *
import time
The following programme creates a tkinter window with two buttons
(Pause and Quit),simultaneously printing a sequence of numbers
from 1 to numb(numb=20, for illustration here) with a controllable
time delay between prints (2 secs for illustration here). Pressing the
Pause button stops the printing, changing the button text to Resume.
After a while when the Resume button is pressed, the printing continues.
To quit printing, the quit button is pressed.
In the illustration, printing a number is symbolic. It could as well be
display of images with time time delays. When it is desired to view an
image in more detail, the display can be paused when that image is displayed
and resumed when desired to view remaining images.
'''
from tkinter import *
import time
global move,stop
move = 1 #this a variable which can take a value 1 or 0
stop='Initial-Value' #This is a variable to control the for and while loops.
#Assignment of string as variable is optional. It could be
#integer as well
def switch(): #controls back and forth switching between Pause and Resume
global move
move = not move
def come_out(): # controls the process to be running or quit
global stop
stop='stop'
numb=20
root = Tk()
Pause_Button=Button(root,bg='wheat',command=switch)
Pause_Button.place(relwidth=0.25,relx=0.2,rely=0.2)
Quit_Button=Button(root,bg='lightblue',text='Quit',command=come_out)
Quit_Button.place(relwidth=0.25,relx=0.5,rely=0.2)
time.sleep(2)
for s in range(numb):
if stop=='stop':
break
stop='continue_while_loop'
while s<numb and stop=='continue_while_loop':
stop='enter_for_loop_again'
Pause_Button.update()
if move==1:
Pause_Button["text"]='Pause'
print('s2=',s,'\n')
time.sleep(2)
else:
stop='continue_while_loop'
Pause_Button["text"]='Resume'
Quit_Button.update()
root.mainloop()
Related
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
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.)
When trying to display text overtime on the tkinter window through a button press, the label does not update correctly. After testing multiple different scenarios, the label is classified as visible, the text value for it is correct, the function itself works without a button press and no matter if using pack, grid or place the result is the same. No matter how the label is updated, it is never displayed on the screen, but only if the button calls the function. When the button is pressed, it runs through the function, printing out everything along the way, and therefore the button itself is also working. Code below:
def label_change(self, text):
""" Repeats f|char_show (with a pause) until <text> equals the label text.
Parameters:
text (str): The text displayed in the label.
"""
if self.last_txt == text:
pass
def char_show():
""" Adds the next single character of <text> to the label text.
Curtosy of Hritik Bhat on:
stackoverflow.com/questions/56684556/how-to-have-variable-text-in-a-label-in-tkinter
"""
if self.label_store == "":
self.label_index = 0
self.label_store += text[self.label_index]
print(self.label_index)
self.label_index += 1
self.display.place_forget()
self.display.config(text=self.label_store)
self.display.update()
self.display.place()
print(self.display.cget("text"))
self.display.update_idletasks()
self.label_change(self.txt)
If you want to add char with delay then you could use root.after(time_ms, function_name) to execute function with delay. This doesn't stops mainloop() and it doesn't need update() and `update_idletask()
import tkinter as tk
def start():
global label_index
# add next char to label
display['text'] += text[label_index]
label_index += 1
# check if there is need to run it again
if label_index < len(text):
# run again after 200ms
root.after(200, start)
# ---
text = 'Hello World'
label_index = 0
root = tk.Tk()
display = tk.Label(root)
display.pack()
button = tk.Button(root, text="Start", command=start)
button.pack()
root.mainloop()
EDIT: this version blocks button when it updates label. It also reset settings when it starts animation again.
import tkinter as tk
def add_char():
global label_index
global running
# add next char to label
display['text'] += text[label_index]
label_index += 1
# check if there is need to run it again
if label_index < len(text):
# run again after 200ms
root.after(200, add_char)
else:
# unblock button after end
running = False
def start():
global label_index
global running
# check if animation is running
if not running:
# block button
running = True
# reset settings
display['text'] = ''
label_index = 0
# run animation
add_char()
# ---
text = 'Hello World'
label_index = 0
running = False
root = tk.Tk()
display = tk.Label(root)
display.pack()
button = tk.Button(root, text="Start", command=start)
button.pack()
root.mainloop()
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
Im trying to make a simple GUI with Tkinker that, when you press a button it adds 1 to the text on a label. However, the label simply remains at 0. Is there a way I can refresh it so it stays up-to-date?
heres what i have so far:
from Tkinter import *
clicks = 0
def click():
global clicks
global Window
clicks += 1
print 'ok', clicks
def close_window():
global Window
Window.destroy()
Window = Tk()
Number = Label(text = clicks)
close = Button(Window , text='exit' , command = close_window)
button = Button(Window,text = 'clickme' ,command = click)
button.pack()
close.pack()
Number.pack()
Window.mainloop()
clicks += 1 only changes the variable clicks.
Use Label.config(text=...) or Label['text'] = ... to change the label text.
def click():
global clicks
clicks += 1
Number.config(text=clicks) # <------
print 'ok', clicks
You almost have it, but for your label you don't want to use "text", you want "textvariable". However, this takes a StringVar as a variable, which forces a little bit of busywork:
Window = Tk()
strclicks = StringVar()
Number = Label(textvariable=clicks)
and within click():
clicks += 1
strclicks.set(clicks)
Using "text" evaluates the variable at creation; "textvariable" updates the label when the variable updates.