I would like to introduce 'Indeterminate progress bar' for each of these two functions. Is there anyway to achieve it using Threading or any other method?
import threading
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import time
def submit_button1():
i=0
for i in range(5):
time.sleep(2)
i+=1
messagebox.showinfo("", "Completed")
def submit_button2():
j=0
for j in range(5):
time.sleep(3)
j+=1
messagebox.showinfo("", "Completed")
root = Tk()
root.geometry("600x300")
root.title("Progress Bar")
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
ttk.Button(mainframe, text="submit_button1", command=submit_button1).grid(column=3, row=12, sticky=W)
ttk.Button(mainframe, text="submit_button2", command=submit_button1).grid(column=3, row=15, sticky=W)
root.mainloop()
Your idea was already correct. Just open a thread about an extra function. For the progress bar it is necessary to define a style and pass it to the widget. If you want to show the progress in the progress bar, then you need to define another style. For this it is enough to add a letter or a number in text.Horizontal.TProgressbar and change the name of the style. In this example, I disabled the button until the Messagebox.showinfo appears and then enabled the button again. Otherwise you would open a new thread every time you press the button and that could lead to errors. If you want to set a boundray you can also use semaphore, but for that I recommend to read the documentation.
import threading
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import time
class pseudo_example():
def __init__(self):
self.root = Tk()
self.root.geometry("600x300")
self.root.title("Progress Bar")
def submit_button1(self):
self.bu1["state"] = "disable"
i = 0
for i in range(101):
time.sleep(.1)
self.progressbar_1["value"] = i
i += 1
messagebox.showinfo("", "Completed")
self.bu1["state"] = "normal"
self.progressbar_1["value"] = 0
def submit_button2(self):
self.bu2["state"] = "disable"
i = 0
for i in range(101):
time.sleep(.1)
self.progressbar_2["value"] = i
i += 1
messagebox.showinfo("", "Completed")
self.bu2["state"] = "normal"
self.progressbar_2["value"] = 0
def thread_sub1(self):
threading.Thread(target=self.submit_button1, daemon=True).start()
def thread_sub2(self):
threading.Thread(target=self.submit_button2, daemon=True).start()
def display(self):
self.bu1 = ttk.Button(self.root, text="submit_button1", command=self.thread_sub1)
self.bu1.grid(column = 0, row = 0)
self.bu2 = ttk.Button(self.root, text="submit_button2", command=self.thread_sub2)
self.bu2.grid(column = 0, row = 1)
style = ttk.Style()
style.configure('text.Horizontal.TProgressbar', anchor='center', troughcolor='white',
background='#009999')
self.progressbar_1 = ttk.Progressbar(self.root, style='text.Horizontal.TProgressbar')
self.progressbar_1.grid(column = 1, row = 0)
self.progressbar_2 = ttk.Progressbar(self.root, style='text.Horizontal.TProgressbar')
self.progressbar_2.grid(column=1, row=1)
self.root.mainloop()
start = pseudo_example()
start.display()
Related
I am making a random generator for my friends and I'm stuck trying to make a scroll down option. So if you generate more the window can show, a scroll down window should be possible. But I can't seem to get any to work. I've tried many online tutorials.
And my second issue with my code is that I can't clear the generated labels from the window. I got it working that it expands the window.
from cProfile import label
from pickle import FRAME
import random
import tkinter as tk
from tkinter import BOTH, DISABLED, LEFT, RIGHT, VERTICAL, Y, Frame, Label, filedialog, Text
import os
from tkinter import ttk
from tkinter.font import NORMAL
from tkinter.messagebox import YES
root = tk.Tk()
root.title('guesser')
#Pelin arvonta ohjelma !
def delete():
for child in root.children.values():
info = child.grid_info()
if info['column'] == 0:
child.grid_forget()
def arvonta():
global label
list1 = []
lista = ["Valorant","Rainbow","Vampire: The masquerade","Playerunknown's battlegrounds","Fortnite","Left 4 Dead 2","Counter strike Global offensive","Realm roayale","Black ops 1 zombies/multiplayer","Black ops 2 zombies/multiplayer","Black ops 3 zombies/multiplayer"]
numero = random.randint(0, 10)
hahmo = (lista[numero])
list1.append(hahmo)
for app in list1:
label = tk.Label(frame, text=app, bg="red",font=('Helvetica',20))
label.pack()
def valorant():
list2 = []
lista2 = ["Brimstone","Viper","Omen","Killjoy","Cypher","Sova","Sage","phoenix","Jett","Reyna","Raze","Raze","Breach","Skye","Yoru","Astra","Kay/o","Chamber","Neon","Fade"]
numero = random.randint(0, 19)
randomValorantagent=(lista2[numero])
list2.append(randomValorantagent)
for app in list2:
label = tk.Label(frame, text=app, bg="red",font=('Helvetica',20))
label.pack()
def quitter():
quit()
canvas = tk.Canvas(root,height=700,width=700,bg="#263D42")
canvas.pack(side=LEFT,fill=BOTH,expand=1)
frame = tk.Frame(root,bg="green")
frame.place(relwidth=0.8,relheight=0.8,relx=0.1,rely=0.1)
frame.pack(fill=BOTH,expand=1)
my_scrollbar = ttk.Scrollbar(frame, orient=VERTICAL, command=canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure The Canvas
canvas.configure(yscrollcommand=my_scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion = canvas.bbox("all")))
# Create ANOTHER Frame INSIDE the Canvas
second_frame = Frame(canvas)
# Add that New frame To a Window In The Canvas
canvas.create_window((0,0), window=second_frame, anchor="nw")
#rlls the game
openfile = tk.Button(second_frame,text="Roll a game",padx=10,pady=5,fg="white",bg="#263D42", command=arvonta)
openfile.pack()
#rolls a valorant agent
valorantA = tk.Button(second_frame,text='Roll valorant agent',padx=10,pady=5,fg="white",bg="#263D42",command=valorant)
valorantA.pack()
# stops program
stop = tk.Button(second_frame,text="Quit",padx=10,pady=5,fg="white",bg="#263D42",command=quitter)
stop.pack()
# deletes all info from screen.
deletor = tk.Button(second_frame,text="delete info",padx=10,pady=5,fg="white",bg="#263D42",command=delete)
deletor.pack()
root.mainloop()```
The following does most of what you want. I wrote it some time ago to test Scrollbars because they are wonky IMHO
from tkinter import *
from functools import partial
class ButtonsTest:
def __init__(self):
self.top = Tk()
self.top.title("Click a button to remove")
self.top.geometry("425x200+50+50")
Label(self.top, text=" Click a button to remove it ",
bg="lightyellow", font=('DejaVuSansMono', 12)
).grid(row=0, sticky="nsew")
Button(self.top, text='Exit', bg="orange", width=9,
command=self.top.quit).grid(row=1,column=0,
sticky="nsew")
self.add_scrollbar()
self.button_dic = {}
self.buttons()
self.top.mainloop()
##-------------------------------------------------------------------
def add_scrollbar(self):
self.canv = Canvas(self.top, relief=SUNKEN)
self.canv.config(width=400, height=200)
self.top_frame = Frame(self.canv, height=100)
##---------- scrollregion has to be larger than canvas size
## otherwise it just stays in the visible canvas
self.canv.config(scrollregion=(0,0, 400, 500))
self.canv.config(highlightthickness=0)
ybar = Scrollbar(self.top, width=15, troughcolor="lightblue")
ybar.config(command=self.canv.yview)
## connect the two widgets together
self.canv.config(yscrollcommand=ybar.set)
ybar.grid(row=3, column=2, sticky="ns")
self.canv.grid(row=3, column=0)
self.canv.create_window(1,0, anchor=NW,
window=self.top_frame)
##-------------------------------------------------------------------
def buttons(self):
b_row=1
b_col=0
for but_num in range(1, 51):
## create a button and send the button's number to
## self.cb_handler when the button is pressed
b = Button(self.top_frame, text = str(but_num), width=5,
command=partial(self.cb_handler, but_num))
b.grid(row=b_row, column=b_col)
## dictionary key=button number --> button instance
self.button_dic[but_num] = b
b_col += 1
if b_col > 4:
b_col = 0
b_row += 1
##----------------------------------------------------------------
def cb_handler( self, cb_number ):
print("\ncb_handler", cb_number)
self.button_dic[cb_number].grid_forget()
##===================================================================
BT=ButtonsTest()
I am trying to write a Python program to count how many times a button is clicked. I have written the following code:
import tkinter
from tkinter import ttk
def clicked(event):
event.x = event.x + 1
label1.configure(text=f'Button was clicked {event.x} times!!!')
windows = tkinter.Tk()
windows.title("My Application")
label = tkinter.Label(windows, text="Hello World")
label.grid(column=0, row=0)
label1 = tkinter.Label(windows)
label1.grid(column=0, row=1)
custom_button = tkinter.ttk.Button(windows, text="Click on me")
custom_button.bind("<Button-1>", clicked)
custom_button.grid(column=1, row=0)
windows.mainloop()
I know that event.x is used to capture the location of mouse. And hence the result of the program is not as expected. I want something else. Can you please help me solve the issue.
You don't need event for this. You need own variable to count it.
And it has to be global variable so it will keep value outside function.
count = 0
def clicked(event):
global count # inform funtion to use external variable `count`
count = count + 1
label1.configure(text=f'Button was clicked {count} times!!!')
EDIT: Because you don't need event so you can also use command= instead of bind
import tkinter as tk
from tkinter import ttk
count = 0
def clicked(): # without event because I use `command=` instead of `bind`
global count
count = count + 1
label1.configure(text=f'Button was clicked {count} times!!!')
windows = tk.Tk()
windows.title("My Application")
label = tk.Label(windows, text="Hello World")
label.grid(column=0, row=0)
label1 = tk.Label(windows)
label1.grid(column=0, row=1)
custom_button = ttk.Button(windows, text="Click on me", command=clicked)
custom_button.grid(column=1, row=0)
windows.mainloop()
You can add 'counter' as an attribute with a decorator like this:
def static_vars(**kwargs):
def decorate(func):
for k in kwargs:
setattr(func, k, kwargs[k])
return func
return decorate
#static_vars(counter=0)
def clicked(event):
clicked.counter += 1
I don't know is this best way or not, but this code is worked
import tkinter as tk
class Main():
def __init__(self, root):
self.root = root
self.count = 0
frame = tk.Frame(self.root)
frame.pack()
btn = tk.Button(frame, text ='click me')
btn.pack()
btn.bind('<Button-1>', self.click)
self.lbl = tk.Label(frame, text = 'Count is 0')
self.lbl.pack()
def click(self, event):
self.count += 1
self.lbl.config(text=f'count {self.count}')
if __name__=="__main__":
root = tk.Tk()
Main(root)
root.mainloop()
I am learning GUI development using Tkinter. I want to show multiple messages on the label which I have stored in a string. I used sleep to view the changes.However only the last message string is shown at execution.
from tkinter import *
import time
master = Tk()
def onClick():
for i in range(0,len(list_of_str)):
w.configure(text=list_of_str[i])
time.sleep(5)
list_of_str = ['first','second','third','fourth','fifth']
w = Label(master, text="Hello, world!")
b = Button(master,text='Click me',command = onClick)
w.pack()
b.pack()
mainloop()
I am a noobie. So thanks for helping !
A simple solution to your problem is to use a combination of the try/except method and using after().
In tkinter sleep() will pause the application instead of providing a timer. For tkinter you want to use the after() method to scheduled an event after a set amount of time instead. The after() method is meant for this exact problem and is what you will always use in tkinter for a delayed event.
In my below example I modified your onClick function to take 1 argument and to use that in our after() method to select the next item in the list after 5 seconds. Note that for the after() method time is done in milliseconds so 5000 is 5 seconds.
from tkinter import *
master = Tk()
def onClick(ndex):
try:
w.configure(text=list_of_str[ndex])
master.after(5000, onClick, ndex+1)
except:
print("End of list")
list_of_str = ['first','second','third','fourth','fifth']
w = Label(master, text="Hello, world!")
b = Button(master,text='Click me',command = lambda: onClick(0))
w.pack()
b.pack()
mainloop()
I think you want this:
from tkinter import *
import time
master = Tk()
global i
i = 0
def onClick():
master.after(1, change)
def change():
global i
if i == len(list_of_str):
pass
else:
w.configure(text=list_of_str[i])
i += 1
master.after(1000, onClick)
list_of_str = ['first','second','third','fourth','fifth']
w = Label(master, text="Hello, world!")
b = Button(master,text='Click me',command = onClick)
w.pack()
b.pack()
mainloop()
time.sleep is a no-no in tkinter. I advise you make your gui in a class and it wil be easier.
example with class:
import tkinter as tk
from tkinter import *
class GUI:
def __init__(self, master):
self.list_of_str = ['first','second','third','fourth','fifth']
self.count = 0
self.master = master
self.w = Label(master, text="Hello, world!")
self.w.pack()
self.b = Button(master,text='Click me',command = self.onClick)
self.b.pack()
def onClick(self, event=None):
if self.count == len(self.list_of_str):
pass
else:
self.w.configure(text=self.list_of_str[self.count])
self.count += 1
self.master.after(1000, self.onClick)
def main():
root = tk.Tk()
app = GUI(root)
root.mainloop()
if __name__ == '__main__':
main()
I made this tkinter application, and I have a bug where the layout changes and the button at the bottom disappears.
I am unable to reproduce the bug 100%. It happens at random times.
For this reason, this SSCCE may contain more than it needs. It's still a lot less than my original app.
from random import random
from threading import Thread, Lock
from time import sleep
from tkinter import Tk, LEFT, RIGHT, BOTH, X, Y, Listbox, Scrollbar, VERTICAL, END
from tkinter.ttk import Frame, Button, Style, Entry
class ListManager:
def __init__(self, ui):
self.entry_list = []
self.ui = ui # to notify of list update
self.timer = ListManager.interval + 1
self.stop = False
interval = 1 # between updates
#staticmethod
def api_request() -> dict:
new_list = ["line"]
while random() > .4:
new_list.append("line")
return {"data": new_list}
def get_entries(self):
r = self.api_request()
self.entry_list = []
for line in r["data"]:
self.entry_list.append(line)
def run(self):
self.timer = ListManager.interval + 1
while not self.stop:
if self.timer > ListManager.interval:
self.get_entries()
if self.ui is None:
print("entries:", len(self.entry_list))
for entry in self.entry_list:
print(entry)
else:
self.ui.receive(self.entry_list)
self.timer = 0
else:
self.timer += 1
sleep(1)
class UI(Frame):
def __init__(self):
super().__init__()
self.style = Style()
self.listbox = None
self.name_entry = None
self.entries = []
self.mutex = Lock()
self.init_pack()
def init_pack(self):
# TODO: investigate open button disappearing (probably need to repack in receive function)
self.master.title("list")
self.style.theme_use("default")
add_streamer_frame = Frame(self)
add_button = Button(add_streamer_frame, text="Add Line")
add_button.pack(side=RIGHT, padx=5, pady=5)
self.name_entry = Entry(add_streamer_frame)
self.name_entry.pack(fill=X, padx=5, expand=True)
add_streamer_frame.pack(fill=X)
list_frame = Frame(self)
self.listbox = Listbox(list_frame, height=20)
self.listbox.configure(font=("Courier New", 12, "bold"))
scrollbar = Scrollbar(self.listbox, orient=VERTICAL)
self.listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=self.listbox.yview)
scrollbar.pack(side=RIGHT, fill=Y)
self.listbox.pack(fill=BOTH, padx=5, expand=True)
list_frame.pack(fill=BOTH, expand=True)
self.pack(fill=BOTH, expand=True)
open_button = Button(self, text="Open")
open_button.pack(side=LEFT, padx=5, pady=5)
def receive(self, entries):
self.mutex.acquire()
self.listbox.delete(0, END)
self.entries = []
for entry in entries:
self.listbox.insert(END, entry)
self.entries.append(entry.split(" ")[0])
self.mutex.release()
def main():
root = Tk()
root.geometry("800x400+300+300")
app = UI()
tl = ListManager(app)
thread = Thread(target=tl.run)
thread.start()
root.mainloop()
tl.stop = True
thread.join()
if __name__ == "__main__":
main()
Here's what it normally looks like most of the time, and should look like:
Here's what it looks like after the bug:
Add:
self.ui.update_idletasks()
after:
sleep(1)
or:
Replace:
self.listbox = Listbox(list_frame, height=20)
with:
self.listbox = Listbox(list_frame)
# or self.listbox = Listbox(list_frame, height=17)
It must be that when too much is going on there's no time spent on updating the GUI. I couldn't reproduce the 2nd screenshot directly without lowering sleep argument as low as 0.001.
The root of your problem seems to be that each time something's added into the listbox its height(winfo_height which is in pixels) is recalculated and resized down from 20 character height, to 17, for which there isn't always time for before a new insertion.
Basically, your widget does not necessarily has a fixed 20 character height at all times, it resizes based on its content and other widgets, unless propagation is disabled by calling self.listbox.pack_propagate(False).
Add:
print(self.ui.listbox.winfo_height())
after:
sleep(1)
to see its height changing for yourself.
Is It possible to improve my progressbar in Tkinter-Python adding a label in the middle (ex: reading file)?
I tried to find a elegant coding solution but without a real result
from Tkinter import *
import ttk
import tkFileDialog
import time
class MainWindow(Frame):
def __init__(self):
Frame.__init__(self)
self.master.title("ProgressBar example")
self.master.minsize(200, 100)
self.grid(sticky=E+W+N+S)
top = self.winfo_toplevel()
top.rowconfigure(0, weight=1)
top.columnconfigure(0, weight=1)
self.start_ind = Button(self, text='Start indeterminate', command=self.start_ind, activeforeground="red")
self.start_ind.grid(row=0, column=0, pady=2, padx=2, sticky=E+W+N+S)
self.pbar_ind = ttk.Progressbar(self, orient="horizontal", length=300, mode="indeterminate")
self.pbar_ind.grid(row=1, column=0, pady=2, padx=2, sticky=E+W+N+S)
def start_ind(self):
for i in xrange(50):
self.pbar_ind.step(1)
self.update()
# Busy-wait
time.sleep(0.1)
if __name__=="__main__":
d = MainWindow()
d.mainloop()
I added the label inside the progressbar by creating a custom ttk style layout. The text of the label is changed by configuring the style:
from tkinter import Tk
from tkinter.ttk import Progressbar, Style, Button
from time import sleep
root = Tk()
s = Style(root)
# add the label to the progressbar style
s.layout("LabeledProgressbar",
[('LabeledProgressbar.trough',
{'children': [('LabeledProgressbar.pbar',
{'side': 'left', 'sticky': 'ns'}),
("LabeledProgressbar.label", # label inside the bar
{"sticky": ""})],
'sticky': 'nswe'})])
p = Progressbar(root, orient="horizontal", length=300,
style="LabeledProgressbar")
p.pack()
# change the text of the progressbar,
# the trailing spaces are here to properly center the text
s.configure("LabeledProgressbar", text="0 % ")
def fct():
for i in range(1, 101):
sleep(0.1)
p.step()
s.configure("LabeledProgressbar", text="{0} % ".format(i))
root.update()
Button(root, command=fct, text="launch").pack()
root.mainloop()
Have you tried creating a text Label and putting it in the same row/column and setting it the same size like so:
self.Lab = Label(self,length=200)
self.Lab.grid(row=1,column=0,pady=2,padx=2,sticky=E+W+N+S))
But you would want to put it after the progress bar widget.
I created a solution for it, which works.
I use a label that is placed on top of the progressbar and the background of the label updates in sync with the progressbar using relwidth and the same color as the progressbar.
from threading import Thread
from tkinter import *
from tkinter import ttk
import time
#Tkinter window
class GUI(Frame, object):
def __init__(self, progress_frame):
super().__init__(progress_frame)
self.progress_frame = progress_frame
self.progress_frame.geometry('300x100')
self.progress_frame.title('Progressbar')
self.progressbar = ttk.Progressbar(self.progress_frame, orient='horizontal', mode='determinate', length=280)
# place the progressbar
self.progressbar.grid(column=0, row=1, columnspan=2, padx=10, ipady=3)
# initialize label
self.value = StringVar()
self.value.set(self.update_progress_label("0 MB/s"))
self.value_label = Label(self.progress_frame, textvariable=self.value, font='default 10', borderwidth=0)
#set background to grey
self.value_label['bg'] = '#e6e6e6'
self.value_label.grid(column=0, row=1, columnspan=2, padx=10, pady=20)
self.current_value = 0
self.start_download()
def update_progress_label(self, mb_s): #the value you want to show in the label
return f"{self.progressbar['value']}% / {mb_s}"
def start_download(self): #start thread that does calculation
download_thread = Download()
download_thread.start()
self.monitor(download_thread)
def monitor(self, download_thread): # monitor download progress
""" Monitor the download thread """
if download_thread.is_alive():
progress = download_thread.value
# update the label
self.value.set(self.update_progress_label(download_thread.mb_s))
widget_x, widget_width = self.value_label.winfo_x(), self.value_label.winfo_width() # get position and width of text label
progressbar_pixellen = self.progressbar.winfo_width() # get total width of progressbar
# get the current position in pxl of progressbar
calculation_ppixel = progress*progressbar_pixellen/100
# get the overlap with the text label
calculation_ptext = calculation_ppixel-widget_x+self.progressbar.winfo_x()
# ensure that no negative value is set
calculation_text = max(calculation_ptext/widget_width, 0)
if calculation_text>0: # if calculation_text (relwidth) is 0 it will still show a small bar, so don't update the label
# update the label with the new text value and the color of the progressbar
self.pblabel = Label(self.value_label, textvariable=self.value, font='default 10', background='#06b025', borderwidth=0, anchor="w")
# relwidth will only show the given percentage of the text in the color
self.pblabel.place(x=0, y=0, anchor="nw", relwidth=calculation_text)
# update the progressbar progress
self.progressbar['value'] = progress
# update the label
self.value_label.update_idletasks()
# rerun this method in 100ms
self.after(100, lambda: self.monitor(download_thread))
class Download(Thread):
def __init__(self):
super().__init__()
self.picture_file = None
self.value = 0
self.mb_s = 0
def run(self):
""" do some calculation like downloading a file """
for i in range(100):
time.sleep(1)
self.value = i
self.mb_s = "100 MB/s"
if __name__ == '__main__':
progress_frame = Tk()
app = GUI(progress_frame)
app.mainloop()
Example