I am struggling with a seemingly simple problem: I want to be able to update the maximum value of a progress bar in tkinter by manually changing the value of an entry. What happens is that the initial value, 100, does not change. OK, I thought that by invoking set in the method count I would be able to update the maximum value. It didn't work. What is the problem?
import tkinter as tk
from tkinter import ttk
from time import sleep
class Window():
def __init__(self, master):
self.master = master
self.configure()
self.create_widgets()
def configure(self):
self.master.title('Progress bar')
self.master.minsize(height=100, width=500)
def create_widgets(self):
self.progress = tk.DoubleVar()
self.number = tk.StringVar()
self.number.set('100')
self.max = tk.IntVar()
self.max.set(eval(self.number.get()))
b1 = tk.Button(self.master, text='Count!', command=self.count)
b1.pack()
e1 = tk.Entry(self.master, textvariable=self.number, width=5)
e1.pack()
p = ttk.Progressbar(self.master, orient='horizontal', length=200, mode='determinate', variable=self.progress, value=1, maximum=self.max.get())
p.pack()
def count(self):
self.max.set(eval(self.number.get()))
for i in range(eval(self.number.get())):
sleep(0.01)
print(i)
self.progress.set(i)
self.master.update()
def main():
root = tk.Tk()
app = Window(root)
root.mainloop()
main()
You're struggling probably because you've overcomplicated the issue. Why do you use 3 separate Variable classes when you need none? Currently what happens is you create a progress bar with a static maximum of 100, and then you're changing how long to progress in that window by setting the entry's value.
Here's a minimal example that updates the maximum value of progress bar:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
class RestartableProgress(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.max_input = tk.Entry(self)
self.restart_button = tk.Button(self, text="Restart",
command=self.restart)
self.progressbar = ttk.Progressbar(self)
self.max_input.pack()
self.restart_button.pack()
self.progressbar.pack()
def restart(self):
self.progressbar['value'] = 0
self.progress()
def progress(self):
max_val = self.max_input.get()
if max_val:
self.progressbar['maximum'] = int(max_val)
if self.progressbar['value'] < self.progressbar['maximum']:
self.progressbar['value'] += 1
self.after(10, self.progress)
def main():
root = tk.Tk()
rp = RestartableProgress(root)
rp.pack()
tk.mainloop()
if __name__ == '__main__':
main()
Related
The following code is from https://github.com/PacktPublishing/Modern-Python-Standard-Library-Cookbook/blob/master/Chapter13/gui_03.py.
Using Python 3.9.
With root.withdraw() The idea is to show a widget without the default tk window appearing. This works with other widgets. However, in this case, if left in p = ProgressDialog does not appear either.
I do not have enough experience to be confident that this warrants a bug report. Am I missing something? Is there a workaround?
import tkinter
from tkinter import simpledialog
from tkinter import ttk
from queue import Queue
class ProgressDialog(simpledialog.SimpleDialog):
def __init__(self, master, text='', title=None, class_=None):
super().__init__(master=master, text=text, title=title, class_=class_)
self.default = None
self.cancel = None
self._queue = Queue()
self._bar = ttk.Progressbar(self.root, orient="horizontal",
length=200, mode="determinate")
self._bar.pack(expand=True, fill=tkinter.X, side=tkinter.BOTTOM)
self.root.attributes("-topmost", True)
self.root.after(200, self._update)
def set_progress(self, value):
self._queue.put(value)
def _update(self):
while self._queue.qsize():
try:
self._bar['value'] = self._queue.get(0)
except Queue.Empty:
pass
self.root.after(200, self._update)
if __name__ == '__main__':
root = tkinter.Tk()
root.withdraw() # The idea is to show the widget without default tk window appearing.
# However if left in p = ProgressDialog does not appear either.
p = ProgressDialog(master=root, text='Downloading Something...',
title='Download')
import threading
def _do_progress():
import time
for i in range(1, 11):
time.sleep(0.5)
p.set_progress(i*10)
p.done(0)
t = threading.Thread(target=_do_progress)
t.start()
p.go()
print('Download Completed!')
It is because SimpleDialog makes itself a transient window of its parent, so it will be hidden if its parent is hidden.
One work around is to override SimpleDialog._set_transient():
import tkinter
from tkinter import simpledialog
from tkinter import ttk
from queue import Queue
class ProgressDialog(simpledialog.SimpleDialog):
def __init__(self, master, text='', title=None, class_=None):
super().__init__(master=master, text=text, title=title, class_=class_)
self.default = None
self.cancel = None
self._queue = Queue()
def _set_transient(self, master, relx=0.5, rely=0.3):
self.root.withdraw()
# add the progress bar
self._bar = ttk.Progressbar(self.root, orient="horizontal",
length=200, mode="determinate")
self._bar.pack(expand=True, fill=tkinter.X, side=tkinter.BOTTOM)
# put the dialog at desired position on screen based on relx and rely arguments
master.update_idletasks()
m_width, m_height = master.winfo_screenwidth(), master.winfo_screenheight()
w_width, w_height = self.root.winfo_width(), self.root.winfo_height()
x = int(m_width*relx) - w_width//2
y = int(m_height*rely) - w_height//2
self.root.geometry(f'+{x}+{y}')
self.root.attributes("-topmost", True)
self.root.deiconify()
# start the update loop
self.root.after(200, self._update)
def set_progress(self, value):
self._queue.put(value)
def _update(self):
while self._queue.qsize():
try:
self._bar['value'] = self._queue.get(0)
except Queue.Empty:
pass
self.root.after(200, self._update)
if __name__ == '__main__':
root = tkinter.Tk()
root.withdraw() # The idea is to show the widget without default tk window appearing.
p = ProgressDialog(master=root, text='Downloading Something...',
title='Download')
import threading
def _do_progress():
import time
for i in range(1, 11):
time.sleep(0.5)
p.set_progress(i*10)
p.done(0)
t = threading.Thread(target=_do_progress)
t.start()
p.go()
print('Download Completed!')
I have an app with multiple windows. I use pack_forget to eliminate the login window and invoke the main window. However this main window loses the default centered position of tkinter. The window is created at position (0 , 0).
Is there any simple way to make this main window be created in the default centered position?
example code, 3 files ->
main.py
#!/usr/bin/env python3
from tkinter import *
from frm_login import Wlogin
class Mainframe(Tk):
def __init__(self):
Tk.__init__(self)
self.frame = Wlogin(self)
self.frame.pack()
def change(self, frame):
self.frame.pack_forget() # delete currrent frame
self.frame = frame(self)
self.frame.pack() # make new frame
if __name__== '__main__':
app = Mainframe()
app.mainloop()
frm_login.py
from tkinter import *
from frm_default import Wmain
class Func(Frame):
def check(self, event=None):
if self.pwd.get() == '1':
self.master.change(Wmain)
else:
self.status.config(text='wrong password')
class Wlogin(Func):
def __init__(self, master=None, **kwargs):
Frame.__init__(self, master, **kwargs)
master.title('Enter password')
master.geometry('300x200')
self.status = Label(self, fg='red')
self.status.pack()
self.lbl = Label(self, text='Enter password')
self.lbl.pack()
self.pwd = Entry(self, show='*')
self.pwd.insert(-1, '1')
self.pwd.pack()
self.pwd.focus()
self.pwd.bind('<Return>', self.check)
self.pwd.bind('<KP_Enter>', self.check)
self.btn = Button(self, text='Done', command=self.check)
self.btn.pack()
self.btn = Button(self, text='Cancel', command=self.quit)
self.btn.pack()
frm_default.py
from tkinter import *
class Wmain(Frame):
def __init__(self, master=None, **kwargs):
Frame.__init__(self, master, **kwargs)
master.title('Main application')
master.geometry('600x400')
There is nothing about your forget / repack code that makes this unique. You can use the same commands you would otherwise. So either define the position yourself:
master.geometry('600x400+300+400')
Or use tk PlaceWindow function:
master.eval('tk::PlaceWindow . center')
Or calculate the position from the window size and monitor size:
master.geometry("600x400")
master.update_idletasks()
x = (master.winfo_screenwidth() - master.winfo_reqwidth()) // 2
y = (master.winfo_screenheight() - master.winfo_reqheight()) // 2
master.geometry(f"+{x}+{y}")
FWIW, my experience tells me that setting the window size yourself instead of letting tkinter calculate it will lead to bugs down the road.
import tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.root.config(padx=100, pady=50)
self.root.geometry('800x600')
self.text = tk.Text()
self.entry = tk.Entry()
self.text.place(x=0, y=0)
self.text.after(5000, self.clear_text) # 5000ms
self.root.mainloop()
def clear_text(self):
print ("Le text vient d'etre détruit")
self.text.place_forget()
app = App()
Hello to all,
I am trying to create an Tkinter app where i put some text in a box and if i stop typing for few seconds the text should disappear. I am a beginner, i am stuck!! Thank for giving me an idea!!Yours
Use after and bind Key event to the same method. Use an if statement to check if the event parameter of the method is None, if it is None delete the text.
import tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.root.config(padx=100, pady=50)
self.root.geometry('800x600')
self.delay = 5000
self.after_id = None
self.text = tk.Text(self.root)
self.text.bind("<Key>", self.clear_text)
self.text.place(x=0, y=0)
self.root.mainloop()
def clear_text(self, event=None):
if not event:
self.text.delete("1.0", "end")
else:
if self.after_id:
self.root.after_cancel(self.after_id)
self.after_id = None
self.after_id = self.root.after(self.delay, self.clear_text)
app = App()
Try this:
import tkinter as tk
from time import perf_counter
class App():
def __init__(self):
self.root = tk.Tk()
self.text = tk.Text(self.root)
self.text.pack()
self.text.bind("<Key>", self.text_entered)
self.text.after(100, self.check_clear_text)
self.time_last_written = perf_counter()
self.root.mainloop()
def text_entered(self, event):
self.time_last_written = perf_counter()
def clear_text(self):
print("Le text vient d'etre détruit")
# Delete all of the text
self.text.delete("0.0", "end")
def check_clear_text(self):
time_diff = perf_counter() - self.time_last_written
print(f"Last key pressed: {time_diff} sec ago.")
if time_diff > 5: # The 5 is in seconds
self.clear_text()
self.text.after(100, self.check_clear_text)
app = App()
It keeps track of the last time you pressed a key and if 5 seconds have passed, it deletes all of the widget's contents using: self.text.delete("0.0", "end")
I am trying to create a program in tkinter which allows me to open an initial window then to keep it throughout all classes used. For example, if I was to create a button in a window then when I click this button, it would exuecute a method that destroys the widget, and then executes a new class that builds a new screen within the same window, such as text opposed to a button.
from tkinter import *
class Window1:
def __init__(self, master):
self.master = master
self.label = Button(self.master, text = "Example", command = self.load_new)
self.label.pack()
def load_new(self):
self.label.destroy()
## Code to execute next class
class Window2:
def __init__(self, master):
self.master = master
self.label = Label(self.master, text = "Example")
self.label.pack()
def main():
root = Tk()
run = Window1(root)
root.mainloop()
if __name__ == '__main__':
main()
I understand this is less practical, but I am curious. Cheers.
Tk() creates main window and variable root gives you access to this window. You can use root as argument for Window2 and you will have access to main window inside Window2
from tkinter import *
class Window1:
def __init__(self, master):
# keep `root` in `self.master`
self.master = master
self.label = Button(self.master, text="Example", command=self.load_new)
self.label.pack()
def load_new(self):
self.label.destroy()
# use `root` with another class
self.another = Window2(self.master)
class Window2:
def __init__(self, master):
# keep `root` in `self.master`
self.master = master
self.label = Label(self.master, text="Example")
self.label.pack()
root = Tk()
run = Window1(root)
root.mainloop()
--
Probably nobody use another class to create Label in place of Button ;)
--
EDIT: In this example using names Window1 and Windows2 is misleading because there is only one window and two classes which use this window. I would rather use names FirstOwner, SecondOwner
Everything is implemented in one Tk class and in this case there always is only one window.
from tkinter import *
from tkinter import ttk
class MainWindow():
def __init__(self, mainWidget):
self.main_frame = ttk.Frame(mainWidget, width=300, height=150, padding=(0, 0, 0, 0))
self.main_frame.grid(row=0, column=0)
self.some_kind_of_controler = 0
self.main_gui()
def main_gui(self):
root.title('My Window')
self.main_label_1 = ttk.Label(self.main_frame, text='Object_1')
self.main_label_1.grid(row=0, column=0)
self.main_label_2 = ttk.Label(self.main_frame, text='Object_2')
self.main_label_2.grid(row=1, column=0)
self.main_label_3 = ttk.Label(self.main_frame, text='Object_3')
self.main_label_3.grid(row=2, column=0)
self.setings_button = ttk.Button(self.main_frame, text='Setings')
self.setings_button.grid(row=0, column=1)
self.setings_button.bind('<Button-1>', self.setings_gui)
self.gui_elements = [self.main_label_1,
self.main_label_2,
self.main_label_3,
self.setings_button]
def setings_gui(self, event):
self.gui_elements_remove(self.gui_elements)
root.title('Setings')
self.main_label_1 = ttk.Label(self.main_frame, text='Object_1')
self.main_label_1.grid(row=2, column=0)
self.main_menu_button = ttk.Button(self.main_frame, text='Main menu')
self.main_menu_button.grid(row=0, column=1)
self.main_menu_button.bind('<Button-1>', self.back_to_main)
self.some_kind_of_controler = 1
self.gui_elements = [self.main_label_1,
self.main_menu_button]
def back_to_main(self, event):
if self.some_kind_of_controler == 1:
self.gui_elements_remove(self.gui_elements)
else:
pass
self.main_gui()
def gui_elements_remove(self, elements):
for element in elements:
element.destroy()
def main():
global root
root = Tk()
root.geometry('300x150+50+50')
window = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
I want to show a progress bar while downloading a file from the web using the urllib.urlretrive method.
How do I use the ttk.Progressbar to do this task?
Here is what I have done so far:
from tkinter import ttk
from tkinter import *
root = Tk()
pb = ttk.Progressbar(root, orient="horizontal", length=200, mode="determinate")
pb.pack()
pb.start()
root.mainloop()
But it just keeps looping.
For determinate mode you do not want to call start. Instead, simply configure the value of the widget or call the step method.
If you know in advance how many bytes you are going to download (and I assume you do since you're using determinate mode), the simplest thing to do is set the maxvalue option to the number you are going to read. Then, each time you read a chunk you configure the value to be the total number of bytes read. The progress bar will then figure out the percentage.
Here's a simulation to give you a rough idea:
import tkinter as tk
from tkinter import ttk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.button = ttk.Button(text="start", command=self.start)
self.button.pack()
self.progress = ttk.Progressbar(self, orient="horizontal",
length=200, mode="determinate")
self.progress.pack()
self.bytes = 0
self.maxbytes = 0
def start(self):
self.progress["value"] = 0
self.maxbytes = 50000
self.progress["maximum"] = 50000
self.read_bytes()
def read_bytes(self):
'''simulate reading 500 bytes; update progress bar'''
self.bytes += 500
self.progress["value"] = self.bytes
if self.bytes < self.maxbytes:
# read more bytes after 100 ms
self.after(100, self.read_bytes)
app = SampleApp()
app.mainloop()
For this to work you're going to need to make sure you don't block the GUI thread. That means either you read in chunks (like in the example) or do the reading in a separate thread. If you use threads you will not be able to directly call the progressbar methods because tkinter is single threaded.
You might find the progressbar example on tkdocs.com to be useful.
I simplified the code for you.
import sys
import ttk
from Tkinter import *
mGui = Tk()
mGui.geometry('450x450')
mGui.title('Hanix Downloader')
mpb = ttk.Progressbar(mGui,orient ="horizontal",length = 200, mode ="determinate")
mpb.pack()
mpb["maximum"] = 100
mpb["value"] = 50
mGui.mainloop()
Replace 50 with the percentage of the download.
If you just want a progress bar to show that the program is busy/working just change the mode from determinate to indeterminate
pb = ttk.Progressbar(root,orient ="horizontal",length = 200, mode ="indeterminate")
Here's another simple example that also shows a progress bar moving. (I have simplified the examples given at https://gist.github.com/kochie/9f0b60384ccc1ab434eb)
import Tkinter
import ttk
root = Tkinter.Tk()
pb = ttk.Progressbar(root, orient='horizontal', mode='determinate')
pb.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb.start(50)
root.mainloop()
Modal dialog window with Progressbar for the bigger project
This example is a bit long, but tested on Python 3.6 and can be used in the bigger project.
# -*- coding: utf-8 -*-
# Modal dialog window with Progressbar for the bigger project
import time
import tkinter as tk
from tkinter import ttk
from tkinter import simpledialog
class MainGUI(ttk.Frame):
''' Main GUI window '''
def __init__(self, master):
''' Init main window '''
ttk.Frame.__init__(self, master=master)
self.master.title('Main GUI')
self.master.geometry('300x200')
self.lst = [
'Bushes01.png', 'Bushes02.png', 'Bushes03.png', 'Bushes04.png', 'Bushes05.png',
'Forest01.png', 'Forest02.png', 'Forest03.png', 'Forest04.png', 'Road01.png',
'Road02.png', 'Road03.png', 'Lake01.png', 'Lake02.png', 'Field01.png']
b = ttk.Button(self.master, text='Start', command=self.start_progress)
b.pack()
b.focus_set()
def start_progress(self):
''' Open modal window '''
s = ProgressWindow(self, 'MyTest', self.lst) # create progress window
self.master.wait_window(s) # display the window and wait for it to close
class ProgressWindow(simpledialog.Dialog):
def __init__(self, parent, name, lst):
''' Init progress window '''
tk.Toplevel.__init__(self, master=parent)
self.name = name
self.lst = lst
self.length = 400
#
self.create_window()
self.create_widgets()
def create_window(self):
''' Create progress window '''
self.focus_set() # set focus on the ProgressWindow
self.grab_set() # make a modal window, so all events go to the ProgressWindow
self.transient(self.master) # show only one window in the task bar
#
self.title(u'Calculate something for {}'.format(self.name))
self.resizable(False, False) # window is not resizable
# self.close gets fired when the window is destroyed
self.protocol(u'WM_DELETE_WINDOW', self.close)
# Set proper position over the parent window
dx = (self.master.master.winfo_width() >> 1) - (self.length >> 1)
dy = (self.master.master.winfo_height() >> 1) - 50
self.geometry(u'+{x}+{y}'.format(x = self.master.winfo_rootx() + dx,
y = self.master.winfo_rooty() + dy))
self.bind(u'<Escape>', self.close) # cancel progress when <Escape> key is pressed
def create_widgets(self):
''' Widgets for progress window are created here '''
self.var1 = tk.StringVar()
self.var2 = tk.StringVar()
self.num = tk.IntVar()
self.maximum = len(self.lst)
self.tmp_str = ' / ' + str(self.maximum)
#
# pady=(0,5) means margin 5 pixels to bottom and 0 to top
ttk.Label(self, textvariable=self.var1).pack(anchor='w', padx=2)
self.progress = ttk.Progressbar(self, maximum=self.maximum, orient='horizontal',
length=self.length, variable=self.num, mode='determinate')
self.progress.pack(padx=2, pady=2)
ttk.Label(self, textvariable=self.var2).pack(side='left', padx=2)
ttk.Button(self, text='Cancel', command=self.close).pack(anchor='e', padx=1, pady=(0, 1))
#
self.next()
def next(self):
''' Take next file from the list and do something with it '''
n = self.num.get()
self.do_something_with_file(n+1, self.lst[n]) # some useful operation
self.var1.set('File name: ' + self.lst[n])
n += 1
self.var2.set(str(n) + self.tmp_str)
self.num.set(n)
if n < self.maximum:
self.after(500, self.next) # call itself after some time
else:
self.close() # close window
def do_something_with_file(self, number, name):
print(number, name)
def close(self, event=None):
''' Close progress window '''
if self.progress['value'] == self.maximum:
print('Ok: process finished successfully')
else:
print('Cancel: process is cancelled')
self.master.focus_set() # put focus back to the parent window
self.destroy() # destroy progress window
root = tk.Tk()
feedback = MainGUI(root)
root.mainloop()