With the help of the command button, I am able to disconnect the frame in Tkinter. But is there any way which helps to use the same button to start also?
import tkinter as tk
counter = 0
def counter_label(label):
def count():
global counter
counter+=1
label.config(text=counter)
label.after(1000, count)
count()
root = tk.Tk()
root.title("Counting Seconds")
label = tk.Label(root, fg="green")
label.pack()
counter_label(label)
button = tk.Button(root, text='Stop', width=25, command=root.destroy)
button.pack()
root.mainloop()
Suggestions will be grateful
You could simple use an if/else statement to check if the buttons text is Start or Stop then change a Boolean variable that controls the counter while also update the text on the button.
import tkinter as tk
counter = 0
active_counter = False
def count():
if active_counter:
global counter
counter += 1
label.config(text=counter)
label.after(1000, count)
def start_stop():
global active_counter
if button['text'] == 'Start':
active_counter = True
count()
button.config(text="Stop")
else:
active_counter = False
button.config(text="Start")
root = tk.Tk()
root.title("Counting Seconds")
label = tk.Label(root, fg="green")
label.pack()
button = tk.Button(root, text='Start', width=25, command=start_stop)
button.pack()
root.mainloop()
Here is an OOP example as well:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Counting Seconds")
self.counter = 0
self.active_counter = False
self.label = tk.Label(self, fg="green")
self.label.pack()
self.button = tk.Button(self, text='Start', width=25, command=self.start_stop)
self.button.pack()
def count(self):
if self.active_counter:
self.counter += 1
self.label.config(text=self.counter)
self.label.after(1000, self.count)
def start_stop(self):
if self.button['text'] == 'Start':
self.active_counter = True
self.count()
self.button.config(text="Stop")
else:
self.active_counter = False
self.button.config(text="Start")
if __name__ == "__main__":
App().mainloop()
This code is overly complicated (my answer), I suggest improving it. But it shows how one could use the same button for both start and stop as well as keeping most of your code.
import tkinter as tk
def counter_label(label):
a = 0
label.config(text=str(a))
def count():
nonlocal a
label.config(text=str(a))
a += 1
label.after(1000, count)
return count
def start_stop(root, btn_text, counter):
first = True
def call():
nonlocal first
if first:
counter()
first = False
btn_text.set('Stop')
else:
root.destroy()
return call
root = tk.Tk()
root.title("Counting Seconds")
label = tk.Label(root, fg="green")
label.pack()
counter = counter_label(label)
btn_text = tk.StringVar()
button = tk.Button(root, textvariable=btn_text, width=25, command=start_stop(root, btn_text, counter))
btn_text.set('Start')
button.pack()
root.mainloop()
Related
Im making this game called:
IDLE PEN ,(MAKE PENS)
and every 1 second i get a bonus pen
how i get a bonus pen is doing this
Import time
While true
make a pen()
time.sleep(1)
but i have some code under the while true loop.
the code under the while true loop is like buttons to
upgrade the pens or make a pen
So how do i make the code under the while true loop work?
This is my game im happy for anyone to copy it
its not ready yet
import functools
import tkinter
import tkinter.messagebox
import time
from random import seed
from random import randint
# eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
window = tkinter.Tk()
window.title('Idle Pen')
def print_pen(number: int):
return f"Pens: {number}"
class pencount:
def __init__(self):
self.pencount = 0
self.text = tkinter.Text(height=1, width=30)
self.text.insert("1.0", print_pen(0))
self.text['state'] = 'disabled'
self.text.pack()
def changepencount(self, count):
if self.pencount + count < 0:
return
self.pencount = self.pencount + count
self.text['state'] = 'normal'
self.text.delete("1.0", "end")
self.text.insert("1.0", print_pen(self.pencount))
self.text['state'] = 'disabled'
self.text.pack()
pen = pencount()
changepenup = functools.partial(pen.changepencount, 1)
B = tkinter.Button(window, text="Make Pen", command=changepenup)
changependown = functools.partial(pen.changepencount, -100)
A = tkinter.Button(window, text='Penmaker', command=changependown)
Q = tkinter.Button(window, text="Quit", command=window.destroy)
U = tkinter.Button
# eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
B.pack()
A.pack()
Q.pack()
window.mainloop()
You could use threading to run your loop in separated thread and then main thread may run tkitner
OR you can use tkinter function after() (instead of while True) to run function with delay and this function should use again after() to run itself.
import tkinter as tk
# --- functions ---
def update():
global value
value += 1
text = f'make penny: {value}'
print(text)
label['text'] = text
# run again after 1s (1000ms)
root.after(1000, update)
# --- main ---
value = 0
root = tk.Tk()
label = tk.Label(root, text="make penny: 0")
label.pack()
button = tk.Button(root, text="Exit", command=root.destroy)
button.pack()
# run first time after 1s (1000ms)
root.after(1000, update)
root.mainloop()
I would like to achieve, when I click on the "Autopaging" button each page from the PDF gets displayed for 1s. Now when I click it, its loops through the list of pages, jumps to the last page, and doesn't display any other page.
To the auto_read function I placed print(pagenumber) to see when its looping through the pages.
from PIL import ImageTk, Image
from pdf2image import convert_from_path
import time
class SuReader:
def __init__(self, root):
self.root = root
self.next_but = Button(root, text='>>', command=lambda: self.next_page())
self.prev_but = Button(root, text='<<', command=self.prev_page)
self.automate_but = Button(root, text='Autopaging', command=self.auto_read)
self.next_but.grid(row=1, column=2)
self.prev_but.grid(row=1, column=0)
self.automate_but.grid(row=1, column=1)
self.display_pages()
def get_pages(self):
self.pages = convert_from_path('book.pdf', size=(700, 600))
self.photos = []
for i in range(len(self.pages)):
self.photos.append(ImageTk.PhotoImage(self.pages[i]))
def display_pages(self):
self.get_pages()
self.page_number = 0
self.content = Label(self.root, image=self.photos[self.page_number])
self.content.grid(column=0,row=0,sticky='NSEW', columnspan=3)
print(len(self.photos))
def next_page(self):
# self.page_number += 1
self.content.destroy()
self.content = Label(self.root, image=self.photos[self.page_number])
self.content.grid(column=0,row=0,sticky='NSEW', columnspan=3)
self.page_number += 1
def prev_page(self):
self.page_number -= 1
print(self.page_number)
self.content.destroy()
self.content = Label(self.root, image=self.photos[self.page_number])
self.content.grid(column=0,row=0,sticky='NSEW', columnspan=3)
def auto_read(self):
for i in range(len(self.photos)):
time.sleep(1)
print(self.page_number)
self.next_page()
root = Tk()
root.title('Book Reader')
program = SuReader(root)
root.mainloop()
Here I just pass it to the next_page function, but even if I define properly it doesn't work.
time.sleep is blocking mainloop and I believe you are also having a garbage collection problem. I rewrote your code and solved your problems. I solved other problems you were about to have, as well. The changes are commented in the script.
import tkinter as tk
from PIL import ImageTk, Image
from pdf2image import convert_from_path
class App(tk.Tk):
WIDTH = 700
HEIGHT = 640
TITLE = 'Book Reader'
def __init__(self, **kwargs):
tk.Tk.__init__(self, **kwargs)
#get_pages and display_pages are useless just do it all in the constructor
self.pages = convert_from_path('book.pdf', size=(700, 600))
self.photos = {}
for i in range(len(self.pages)):
#the image might get garbage collected if you don't do it this way
self.photos[f'photo_{i}'] = ImageTk.PhotoImage(self.pages[i])
self.auto = tk.IntVar()
self.was_auto = False
self.page_number = 0
self.content = tk.Label(self, image=self.photos[f'photo_{self.page_number}'])
self.content.grid(column=0, row=0, sticky='nsew', columnspan=3)
tk.Button(self, text='<<', command=self.prev_page).grid(row=1, column=0, sticky='w')
tk.Checkbutton(self, text='auto-paging', variable=self.auto, command=self.auto_page).grid(row=1, column=1)
tk.Button(self, text='>>', command=self.next_page).grid(row=1, column=2, sticky='e')
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(2, weight=1)
def next_page(self):
#stop a lingering `after` call from affecting anything if we unchecked auto-paging
if self.auto.get() == 0 and self.was_auto:
self.was_auto = False
return
self.page_number += 1
#never be out of range
self.page_number = self.page_number % len(self.pages)
#you don't have to remake the Label every time, just reassign it's image property
self.content['image'] = self.photos[f'photo_{self.page_number}']
#time.sleep(1) replacement ~ non-blocking
if self.auto.get() == 1:
self.after(1000, self.next_page)
def prev_page(self):
self.page_number -= 1
#never be out of range
if self.page_number < 0:
self.page_number += len(self.pages)
self.content['image'] = self.photos[f'photo_{self.page_number}']
def auto_page(self):
if self.auto.get() == 1:
#allows us to catch a lingering `after` call if auto-paging is unchecked
self.was_auto = True
self.next_page()
if __name__ == '__main__':
app = App()
app.title(App.TITLE)
app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
app.resizable(width=False, height=False)
app.mainloop()
I am trying to create a tkinter GUI for a script which performs some task. The task is triggered by clicking a start button, and I would like to add a dynamic label showing that the task is "in progress" by displaying:
"Working." → "Working.." → "Working..."
I referred to this post and wrote the following script. Here I used a progress bar to represent my "task", and was expecting the status label to change (as stated above) WHILE the progress bar is updating.
import tkinter as tk
class UI:
def __init__(self):
self.root = tk.Tk()
self.root.title('Hello World')
self.prog_Label = tk.Label(self.root, text='Progress')
self.prog_Label.grid(row=0, column=0, sticky=tk.W, padx=20, pady=(10, 0))
self.prog_Bar = tk.ttk.Progressbar(self.root)
self.prog_Bar.configure(value=0, mode='determinate', orient='horizontal')
self.prog_Bar.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=20, pady=5)
self.exe_Btn = tk.Button(self.root, text='Start', padx=15, command=self.run, relief='groove')
self.exe_Btn.grid(row=2, column=0, padx=80, pady=(40, 20), sticky=tk.E)
self.prog_Label = tk.Label(self.root, text='Status:-')
self.prog_Label.grid(row=3, column=0, sticky=tk.W, padx=20, pady=10)
self.root.mainloop()
def run(self):
self.update_status('Working')
n = 0
self.prog_Bar.configure(value=n, maximum=100000, mode='determinate', orient='horizontal')
for i in range(100000):
n += 1
self.prog_Bar.configure(value=n)
self.prog_Bar.update_idletasks()
def update_status(self, status=None):
if status is not None:
current_status = 'Status: ' + status
else:
current_status = self.prog_Label['text']
if current_status.endswith('...'):
current_status = current_status.replace('...', '')
else:
current_status += '.'
self.prog_Label['text'] = current_status
self.prog_Label.update_idletasks()
self._status = self.root.after(1000, self.update_status)
if __name__ == '__main__':
ui = UI()
However, the program behaves in a way that, when the start button is clicked, although the status label changes from '-' to 'Working' immediately, it only starts to add the dots after the progress bar reaches the end.
Is there a way to modify it so as to achieve my purpose?
I changed your structure a little so your task is now in its own class, instead of sleeping you would perform the task there. I added a thread for the task as i assumed that it would need its own process, this stops the application freezing as it would block the main UI loop.
import threading
import time
import tkinter as tk
import tkinter.ttk as ttk
class Task:
def __init__(self):
self.percent_done = 0
threading.Thread(target = self.run).start()
def run(self):
while self.percent_done < 1:
self.percent_done += 0.1
# Do your task here
time.sleep(0.5)
self.percent_done = 1
class Application():
def __init__(self):
self.root = tk.Tk()
self.root.title("Window Title")
self.task = None
self.label_dots = 0
self.prog_bar = tk.ttk.Progressbar(self.root)
self.prog_bar.configure(value=0, maximum=100, mode='determinate', orient='horizontal')
self.prog_bar.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=20, pady=5)
self.run_btn = tk.Button(self.root, text='Start', padx=15, command=self.start_task, relief='groove')
self.run_btn.grid(row=2, column=0, padx=80, pady=(40, 20), sticky=tk.E)
self.prog_label = tk.Label(self.root, text='Status: -')
self.prog_label.grid(row=3, column=0, sticky=tk.W, padx=20, pady=10)
def start_task(self):
self.task = Task()
self.update_ui()
def update_ui(self):
# Percent is between 0 and 1
if 0 < self.task.percent_done < 1:
status = "Working"
self.label_dots += 1
self.label_dots = self.label_dots % 4
else:
status = "Finished"
self.label_dots = 0
self.prog_bar.configure(value=self.task.percent_done * 100)
label_text = "Status: - " + status + ("." * self.label_dots)
self.prog_label.config(text = label_text)
if status != "Finished":
self.root.after(1000, self.update_ui)
Application().root.mainloop()
i have made a double countdown timer with python and tkinter but it seemed that it cannot be run if the tkinter window is not on the foreground and it cannot simultaneously run. This is my code:
import tkinter as tk
import tkinter.ttk as ttk
import time
class app:
def __init__(self):
self = 0
def mainGUIArea():
def count_down_1():
for i in range(79, -1, -1):
timeCount = "{:02d}:{:02d}".format(*divmod(i, 60))
time_str.set(timeCount)
root.update()
time.sleep(1)
def count_down_2():
for j in range(10, -1, -1):
timeCount = "{:02d}:{:02d}".format(*divmod(j, 60))
time_str1.set(timeCount)
root.update()
time.sleep(1)
#Main Window
root = tk.Tk()
root.title("Super Timer V1.0")
root.minsize(300,300)
root.geometry("500x300")
#Timer1
time_str = tk.StringVar()
label_font = ('Courier New', 40)
tk.Label(root, textvariable = time_str, font = label_font, bg = 'white', fg = 'blue', relief = 'raised', bd=3).pack(fill='x', padx=5, pady=5)
tk.Button(root, text=' Start Timer! ',bg='lightgreen',fg='black', command=count_down_1).pack()
tk.Button(root, text='Stop and Exit',bg='red',fg='white', command=root.destroy).pack()
#Timer2
time_str1 = tk.StringVar()
label_font = ('Courier New', 40)
tk.Label(root, textvariable = time_str1, font = label_font, bg = 'white', fg='blue', relief='raised', bd=3).pack(fill='x', padx=5, pady=5)
tk.Button(root, text=' Start Timer! ',bg='lightblue',fg='black', command=count_down_2).pack()
tk.Button(root, text='Stop and Exit',bg='red',fg='white', command=root.destroy).pack()
def main():
app.mainGUIArea()
main()
Do you have any suggestion? Thank You :)
The calls to time.sleep are at least part of the problem. When you call sleep it does literally that -- it puts the application to sleep. No events can be processed and the GUI freezes. This is the wrong way to do a countdown timer.
The other problem is the calls to update inside the loops alongside the calls to time.sleep. This call will process events, which means that when one of the loops is running and you click a button, you may end up calling the other function, interleaving your two loops.
The proper way to do something periodically is to use after to repeatedly call a function. The general pattern is this:
def update_display(self):
<do whatever code you want to update the display>
root.after(1000, self.update_display)
You can have as many of these running in parallel that you want (up to practical limits, obviously), and your GUI will be completely responsive between updates.
Here's a quick example:
class Countdown(tk.Label):
def __init__(self, parent):
tk.Label.__init__(self, parent, width=5, text="00:00")
self.value = 0
self._job_id = None
def tick(self):
self.value -= 1
text = "{:02d}:{:02d}".format(*divmod(self.value, 60))
self.configure(text=text)
if self.value > 0:
self._job_id = self.after(1000, self.tick)
def start(self, starting_value=60):
if self._job_id is not None: return
self.value = starting_value
self.stop_requested = False
self.after(1000, self.tick)
def stop(self):
self.after_cancel(self._job_id)
self._job_id = None
This is a simple tkinter(gui) stopwatch I made which works perfectly. Check it out
__author__ = 'Surya'
from tkinter import *
import time
class StopWatch(Frame):
def __init__(self, parent = None, ** kw):
Frame.__init__(self, parent, kw)
self._timeelapsed = 0.0
self._start = 0.0
self._run = 0
self.timestr = StringVar()
self.makeWidgets()
def makeWidgets(self):
l = Label(self, textvariable=self.timestr)
self._setTime(self._timeelapsed)
l.pack(fill=X, expand=NO, pady=2, padx=2)
def _update(self):
self._timeelapsed = time.time() - self._start
self._setTime(self._timeelapsed)
self._timer = self.after(50, self._update)
def _setTime(self, elap):
minutes = int(elap/60)
seconds = int(elap - minutes*60.0)
hseconds = int((elap - minutes*60.0 - seconds)*100)
self.timestr.set('%02d:%02d:%02d' % (minutes, seconds, hseconds))
def Start(self):
if not self._run:
self._start = time.time() - self._timeelapsed
self._update()
self._run = 1
def Stop(self):
if self._run:
self.after_cancel(self._timer)
self._timeelapsed = time.time() - self._start
self._setTime(self._timeelapsed)
self._run = 0
def Reset(self):
self._start = time.time()
self._timeelapsed = 0.0
self._setTime(self._timeelapsed)
def main():
root = Tk()
sw = StopWatch(root)
sw.pack(side=TOP)
Button(root, text='Start!', command=sw.Start).pack(side=LEFT)
Button(root, text='Stop!', command=sw.Stop).pack(side=LEFT)
Button(root, text='Reset!!!', command=sw.Reset).pack(side=LEFT)
Button(root, text='Quit!!!', command=root.quit).pack(side=LEFT)
root.mainloop()
if __name__ == '__main__':
main()
I am just learning python and jumping into classes. I have been using the following code for a simple stopwatch that I found. I now want to create a class with this code. I will have 1-4 timers on the screen at any one time. I need to start them all at the same time but end each timer independently. I am still plugging away at this but any help would be much appreciated.
import Tkinter as tk
def update_timeText():
if (state):
global timer
timer[2] += 1
if (timer[2] >= 100):
timer[2] = 0
timer[1] += 1
if (timer[1] >= 60):
timer[0] += 1
timer[1] = 0
timeString = pattern.format(timer[0], timer[1], timer[2])
timeText.configure(text=timeString)
root.after(10, update_timeText)
def start():
global state
state = True
def pause():
global state
state = False
def reset():
global timer
timer = [0, 0, 0]
timeText.configure(text='00:00:00')
def exist():
root.destroy()
state = False
root = tk.Tk()
root.wm_title('Simple Kitchen Timer Example')
timer = [0, 0, 0]
pattern = '{0:02d}:{1:02d}:{2:02d}'
timeText = tk.Label(root, text="00:00:00", font=("Helvetica", 150))
timeText.pack()
startButton = tk.Button(root, text='Start', command=start)
startButton.pack()
pauseButton = tk.Button(root, text='Pause', command=pause)
pauseButton.pack()
resetButton = tk.Button(root, text='Reset', command=reset)
resetButton.pack()
quitButton = tk.Button(root, text='Quit', command=exist)
quitButton.pack()
To create a class simply try this:
import Tkinter as tk
class StopWatch(object): # sub-class the object class maybe
def update_timeText(self):
#your code logic...
def start(self):
#your code logic...
#etc...
#the rest of your functions are the same
#just specify the 'self' Python keyword before any other arguments in the function. :D
Let me know if this works. :D
Here is my solution. It took me so long to get my head around the class creation of this. Any ideas for improving this?
import Tkinter as tk
root = tk.Tk()
root.wm_title('Stopwatch Class')
root.geometry("300x350")
class StopWatch(object):
def __init__(self):
self.pattern = '{0:02d}:{1:02d}:{2:02d}'
self.timer = [0, 0, 0]
self.state = False
self.timeText = tk.Label(root, text='00:00:01', font=("Helvetica", 15))
self.timeText.pack()
self.startButton = tk.Button(root, text='Start', command=self.start)
self.startButton.pack()
self.resetButton = tk.Button(root, text='Reset', command=self.reset)
self.resetButton.pack()
self.pauseButton = tk.Button(root, text='Pause', command=self.pause)
self.pauseButton.pack()
def update_timeText(self):
if (self.state):
self.timer[2] += 1
if (self.timer[2] >= 100):
self.timer[2] = 0
self.timer[1] += 1
if (self.timer[1] >= 60):
self.timer[0] += 1
self.timer[1] = 0
self.timeString = self.pattern.format(self.timer[0], self.timer[1], self.timer[2])
self.timeText.configure(text=self.timeString)
root.after(10, self.update_timeText)
def start(self):
self.state = True
def pause(self):
self.state = False
def reset(self):
self.timer = [0, 0, 0]
self.timeText.configure(text='00:00:00')
# create a new stopwatch
swatch1 = StopWatch()
swatch1.update_timeText()
# create a new stopwatch
swatch2 = StopWatch()
swatch2.update_timeText()
# create a new stopwatch
swatch3 = StopWatch()
swatch3.update_timeText()
root.mainloop()