The first set of code works. It displays the updating variable. However, in the second code, when I put the variable in a Treeview widget instead, the variable just stays on 0 and does not update. How come this works with the label but not with treeview? What is the easiest way to fix this?
First (works):
from tkinter import *
from tkinter import ttk
import threading
import time
def fun():
for i in range(10):
var.set(var.get() + 1)
time.sleep(.5)
t = threading.Thread(target=fun)
root = Tk()
var = IntVar()
var.set(0)
mainframe = ttk.Frame(root)
mainframe.grid(column = 0, row = 0)
label = ttk.Label(mainframe, textvariable=var)
label.grid(column = 0, row = 0)
t.start()
root.mainloop()
Second (does not work):
from tkinter import *
from tkinter import ttk
import threading
import time
def fun():
for i in range(10):
var.set(var.get() + 1)
time.sleep(.5)
t = threading.Thread(target=fun)
root = Tk()
var = IntVar()
var.set(0)
mainframe = ttk.Frame(root)
mainframe.grid(column = 0, row = 0)
tree = ttk.Treeview(mainframe, columns = ('number'), height = 1)
tree.insert('', 'end', text = 'Number', values = var.get())
tree.grid(column=0, row=0)
t.start()
root.mainloop()
modify fun to :
def fun():
for i in range(10):
var.set(var.get() + 1)
x = tree.get_children()
tree.item(x, text = 'Number', values = var.get())
time.sleep(.5)
The get_children method returns a list of tuples item IDs, one for each child of the tree. With tree.item then update the child with the required id.
In the second program along with the variable var, you also have to update the child of the tree
Related
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()
from tkinter import *
import tkinter as tk
import random
f = 0
def test_print():
f = random.randint(0,118)
master.update_idletasks()
# creating Tk window
master = Tk()
master.minsize(600,400)
var = IntVar()
var.set(f)
# cretaing a Fra, e which can expand according
# to the size of the window
pane = Frame(master)
pane.pack(fill = BOTH, expand = True)
# button widgets which can also expand and fill
# in the parent widget entirely
# Button 1
b1 = Button(pane, text = "Click me !",
background = "red", fg = "white", command = test_print)
b1.pack(side = TOP, expand = True, fill = BOTH)
label = tk.Label(pane, textvariable = var)
label.pack(side = BOTTOM, expand = False)
# Execute Tkinter
master.mainloop()
This code runs fine but it doesn't give me a random number when i press the button. I need it to give me a random output so i can get a random element from a list. Does anybody have the answer to this?
Here it is. Now you can see that the label changes:
from tkinter import *
import tkinter as tk
import random
def test_print():
f = random.randint(0, 118)
label.configure(text=str(f))
master.update_idletasks()
# creating Tk window
master = Tk()
master.minsize(600, 400)
# cretaing a Fra, e which can expand according
# to the size of the window
pane = Frame(master)
pane.pack(fill=BOTH, expand=True)
# button widgets which can also expand and fill
# in the parent widget entirely
# Button 1
b1 = Button(pane, text="Click me !",
background="red", fg="white", command=test_print)
b1.pack(side=TOP, expand=True, fill=BOTH)
label = tk.Label(pane)
label.pack(side=BOTTOM, expand=False)
# Execute Tkinter
master.mainloop()
here is the answer
from tkinter import *
import tkinter as tk
import random
maxnumber = 118
f = random.randint(0,maxnumber)
def test_print():
master.update_idletasks()
label.configure(textvariable = var)
# creating Tk window
master = Tk()
master.minsize(600,400)
var = IntVar()
var.set(f)
# cretaing a Fra, e which can expand according
# to the size of the window
pane = Frame(master)
pane.pack(fill = BOTH, expand = True)
# button widgets which can also expand and fill
# in the parent widget entirely
# Button 1
b1 = Button(pane, text = "Click me !",
background = "red", fg = "white", command = test_print)
b1.pack(side = TOP, expand = True, fill = BOTH)
label = tk.Label(pane)
label.pack(side = BOTTOM, expand = False)
# Execute Tkinter
master.mainloop()
I am trying to create a progress bar that runs as long as my function is running to show the user that things are happening and not just frozen. My function (generate_reports) makes queries to the database and writes to CSV files. Here is an abstract version of my code:
from tkinter import *
from tkinter import ttk
from billing import generate_reports
class app:
def __init__(self, root):
self.mainframe = ttk.Frame(root, padding = '4 4 12 12')
self.mainframe.grid(column = 0, row = 0, sticky = (N, W, E, S))
ttk.Button(self.mainframe, text = "Generate Billing Reports", command = self.do_reports).grid(column = 2, row = 3, sticky = (W, E))
def do_reports(self, *args):
pbar = ttk.Progressbar(self.mainframe, orient = HORIZONTAL, mode = 'indeterminate')
pbar.grid(row = 4, column = 3, sticky = (W, E))
t1 = threading.Thread(target = generate_reports, args = [start, end])
t1.start()
pbar.start()
t1.join()
pbar.stop()
return
root = Tk()
BillingApp(root)
root.mainloop()
With this code, the progress bar doesn't pop up until after the generate_reports thread is completed and it is unmoving. If I remove the join, everything works fine but it never stops loading. How can I make the loading bar run for only the duration of the generate_reports thread?
Heh welcome to the fun world of event driven programming :). You can't use join here, the point of that function is to block until the thread is done and the whole point of using a thread is to avoid blocking the mainloop. You have 2 choices: either set up the GUI to constantly poll the thread to see if it's still running, or set up the thread to send a message back to the GUI when it's done. This latter option is probably the cleanest, and it's often done using tkinter's event mechanism.
from tkinter import *
from tkinter import ttk
import threading
import time
def generate_reports(start, end):
print("provide a mcve next time!")
time.sleep(5)
def run_report(root, *args):
generate_reports(*args)
root.event_generate("<<PhishDoneEvent>>") # yes, this is using tkinter in a thread, but some tkinter methods are ok to use in threads
class BillingApp:
def __init__(self, root):
self.mainframe = ttk.Frame(root, padding = '4 4 12 12')
self.mainframe.grid(column = 0, row = 0, sticky = (N, W, E, S))
ttk.Button(self.mainframe, text = "Generate Billing Reports", command = self.do_reports).grid(column = 2, row = 3, sticky = (W, E))
root.bind("<<PhishDoneEvent>>", self.report_done)
def do_reports(self, *args):
# note this makes a new widget with every click ... this is bad. Refactor to reuse the widget.
self.pbar = ttk.Progressbar(self.mainframe, orient = HORIZONTAL, mode = 'indeterminate')
self.pbar.grid(row = 4, column = 3, sticky = (W, E))
start, end = 4,5
t1 = threading.Thread(target = run_report, args = [root, start, end])
t1.start()
self.pbar.start()
def report_done(self, event=None):
self.pbar.stop()
Label(self.mainframe, text="report done").grid(row = 4, column = 3)
root = Tk()
BillingApp(root)
root.mainloop()
I see that you have accepted the answer above. However, I would post my answer since it is not based on threading, which I think is simpler and may be more suitable:
from tkinter import *
from tkinter.ttk import *
import os
root = Tk()
# I set the length and maximum as shown to demonstrate the process in the
# proceeding function
progress = Progressbar(root, orient = HORIZONTAL,
length = 200/5, maximum=200/5, mode = 'determinate')
# Function
def my_func():
t=0
r= 1/5
for i in range(200):
print(i)
t=t+r
progress['value'] = t
root.update_idletasks()
progress.pack()
# Button
Button(root, text = 'Start', command = bar).pack(pady = 10)
mainloop()
I created a small GUI using Tkinter. It has a button on click of which some data is written to Excel.
To show the progress, I added a progress bar but since the process is resource intensive, the progress bar appears only at the end. Therefore, I used threading as shown.
In the Main function below, I initialized the progress bar in a different thread but I want to update the current value of the task in Start function.
Because of this line:
progressbar.Start()
it is just continuously running without anything to do with the current progress.
def Start():
x = 0
progressbar["value"] = x
for idx, val in enumerate(rows):
region_url = val[1]
if (model_url != '-'):
url = 'http://testurl/' + region_url
x = x + 1
if (x > 4):
break
# Main
if __name__ == '__main__':
window = Tk()
new = progress(window)
# Add a grid
mainframe = Frame(window)
mainframe.grid(column=0,row=0, sticky=(N,W,E,S) )
mainframe.columnconfigure(0, weight = 1)
mainframe.rowconfigure(0, weight = 1)
mainframe.pack(pady = 100 , padx = 150)
# DropDown
popupMenu = OptionMenu(mainframe, tkvar, *regionList)
Label(mainframe, text="Region").grid(row = 1, column = 0)
# Button
btnSubmit = Button(mainframe, text= "Execute",command=StartScrap).grid(row = 2, column = 18)
popupMenu.grid(row = 2, column =0)
# Progress Bar
progressbar = ttk.Progressbar(window, orient = HORIZONTAL,length=300, mode = 'indeterminate')
progressbar.pack()
t = threading.Thread()
progressbar["maximum"] = 4
progressbar.start()
window.mainloop()
There are a couple things in your code that needs fixing.
Firstly you defined the function Start which controls how your progressbar is updated, but you never call it.
Secondly, you define the progressbar mode as indeterminate and that just start the progress bar - it works but it will just periodically move the progressbar.
Thirdly, you defined the Thread t but never put a target function.
Here's how to move your progressbar in a thread:
from tkinter import *
from tkinter import ttk
import threading
import time
def Start():
def update_pbar():
for i in range(5): #replace this with your method to update your progressbar
time.sleep(1)
progressbar["value"] = i
t = threading.Thread(target=update_pbar)
t.start()
# Main
if __name__ == '__main__':
window = Tk()
progressbar = ttk.Progressbar(window, orient = HORIZONTAL,length=300, mode = 'determinate')
progressbar.pack()
progressbar["maximum"] = 4
btnSubmit = Button(window, text="Execute", command=Start).pack()
window.mainloop()
For some reason, var.set(str) does not update var when a toplevel window is created from a function. The code below works when placed in the mainloop (without the function), though. When adding mainloop to the function it also works as intended. Is that intended behaviour?
from tkinter import *
from tkinter import ttk
root = Tk()
def function():
mainFrame = ttk.Frame(root)
mainFrame.grid(column =1, row =1)
configureWindow = Toplevel(mainFrame)
pathInfoContent = StringVar()
graphPathFrame = ttk.Frame(root)
graphPathFrame.grid(column = 1, row =1)
graphPathLabel = ttk.Label(graphPathFrame, text = 'GraphViz Installation Path:')
graphPathLabel.grid(column = 1, row =1)
graphPathLabel3 = ttk.Label(graphPathFrame, textvariable = pathInfoContent)
graphPathLabel3.grid(column = 2, row =1)
pathInfoContent.set('Something')
function()
mainloop()