How to combine pytube and tkinter label to show progress? - python-3.x

I am writing small program which downloads song from youtube (using pytube)
and I want to add python tkinter GUI to show percent value when the file is downloading.
Now when I execute my code, program at first downloads the file (takes about 60sec) and only then shows the label with 100%. What should I change in my code if I want to see label value climbing from 0% to 100% at the same time when the file is been downloaded?
Code Python3:
from pytube import YouTube
import tkinter as tk
from tkinter import ttk
# main application shows:
# label Loading..
# label which configure values when file is downloading
# inderterminate progress bar
class MainApplication(tk.Frame):
def __init__(self, master=None, *args, **kwargs):
tk.Frame.__init__(self, master)
self.master = master
self.master.grid_rowconfigure(0, weight=0)
self.master.grid_columnconfigure(0, weight=1)
self.youtubeEntry = "https://www.youtube.com/watch?v=vVy9Lgpg1m8"
self.FolderLoacation = "C:/Users/Jonis/Desktop/"
# pytube
self.yt = YouTube(self.youtubeEntry)
video_type = self.yt.streams.filter(only_audio = True).first()
# file size of a file
self.MaxfileSize = video_type.filesize
# Loading label
self.loadingLabel = ttk.Label(self.master, text="Loading...", font=("Agency FB", 30))
self.loadingLabel.grid(pady=(100,0))
# loading precent label which must show % donwloaded
self.loadingPercent = tk.Label(self.master, text="", fg="green", font=("Agency FB", 30))
self.loadingPercent.grid(pady=(30,30))
# indeterminate progress bar
self.progressbar = ttk.Progressbar(self.master, orient="horizontal", length=500, mode='indeterminate')
self.progressbar.grid(pady=(50,0))
self.progressbar.start()
# call Download file func
self.DownloadFile
def DownloadFile(self):
self.yt.register_on_progress_callback(self.show_progress_bar)
self.yt.streams.filter(only_audio=True).first().download(self.FolderLoacation)
# func count precent of a file
def show_progress_bar(self, stream=None, chunk=None, file_handle=None, bytes_remaining=None):
# loadingPercent label configure value %
self.loadingPercent.config(text=str(100 - (100*(bytes_remaining/self.MaxfileSize))))
root = tk.Tk()
root.title("Youtube downloader")
root.geometry("1920x1080")
app = MainApplication(root)
root.mainloop()`

The issue which appears to be is that you need to call self.DownloadFile and self.show_progress_bar at the same time as you mentioned. To call both functions at the same time the best solution is to use thread library
from pytube import YouTube
import tkinter as tk
from tkinter import ttk
import threading
# main application shows:
# label Loading..
# label which configure values when file is downloading
# inderterminate progress bar
class MainApplication(tk.Frame):
def __init__(self, master=None, *args, **kwargs):
tk.Frame.__init__(self, master)
self.master = master
self.master.grid_rowconfigure(0, weight=0)
self.master.grid_columnconfigure(0, weight=1)
self.youtubeEntry = "https://www.youtube.com/watch?v=vVy9Lgpg1m8"
self.FolderLoacation = "C:/Users/Jonis/Desktop/"
# pytube
self.yt = YouTube(self.youtubeEntry)
video_type = self.yt.streams.filter(only_audio = True).first()
# file size of a file
self.MaxfileSize = video_type.filesize
# Loading label
self.loadingLabel = ttk.Label(self.master, text="Loading...", font=("Agency FB", 30))
self.loadingLabel.grid(pady=(100,0))
# loading precent label which must show % donwloaded
self.loadingPercent = tk.Label(self.master, text="0", fg="green", font=("Agency FB", 30))
self.loadingPercent.grid(pady=(30,30))
# indeterminate progress bar
self.progressbar = ttk.Progressbar(self.master, orient="horizontal", length=500, mode='indeterminate')
self.progressbar.grid(pady=(50,0))
self.progressbar.start()
threading.Thread(target=self.yt.register_on_progress_callback(self.show_progress_bar)).start()
# call Download file func
threading.Thread(target=self.DownloadFile).start()
def DownloadFile(self):
self.yt.streams.filter(only_audio=True).first().download(self.FolderLoacation)
# func count precent of a file
def show_progress_bar(self, stream=None, chunk=None, file_handle=None, bytes_remaining=None):
# loadingPercent label configure value %
self.loadingPercent.config(text=str(int(100 - (100*(bytes_remaining/self.MaxfileSize)))))
root = tk.Tk()
root.title("Youtube downloader")
root.geometry("1920x1080")
app = MainApplication(root)
root.mainloop()

Related

Is there a way to make tkinter windows work independantly Tkinter?

I have been looking to create a code that opens a second tkinter window to display stuffs live while a program is running on my main window. However, doing so, my main window gets frozen during 5s and then displays stuff on my second window when it is completed.
Is there a way to live display in the second window ?
My code below:
import tkinter as tk
from tkinter import ttk
import time
class PopUpLog(tk.Tk):
def __init__(self, parent):
tk.Tk.__init__(self)
self.y=5
tk.Button(self.master, text="Write in pop-up", command=self.write).pack(side="left")
# canvas
frameL = tk.Frame(self)
frameL.pack(side="left", fill="both")
self.canvasL = tk.Canvas(frameL, height=800, width=800)
self.canvasL.pack(fill="both", expand=True)
# scrollbar
vsb = ttk.Scrollbar(self, orient="v", command=self.canvasL.yview)
vsb.pack(side="left", fill="y")
self.canvasL.configure(yscrollcommand=vsb.set)
self.canvasL.bind("<Configure>", lambda e:self.canvasL.configure(scrollregion=self.canvasL.bbox("all")))
def write(self, text="hi im a pop-up"):
for i in range(5):
self.canvasL.create_text(5, self.y, anchor='nw', justify='left', text=text)
self.y += 25
time.sleep(1)
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="Open window", command=self.popup).pack(side="left")
def popup(self):
self.top = PopUpLog(self)
self.top.geometry("400x400")
self.top.title("pop-up")
self.top.mainloop()
if __name__ == "__main__":
root = App()
root.mainloop()
So far, the program runs for 5s and then displays everything in self.top. BUT I need a live display (made every time create_text is called) in self.top but I can't even get that.
I am sorry if this is redundant to another question asked but I couldn't find helpful enough information.
Thanks a lot !
time.sleep is the reason why your window is freezing. This is the case for virtually any GUI toolkit. If you want the updates to happen incrementally you can use the after method which will execute the callback you assign after a certain number of milliseconds.
Also there should only be one mainloop. There is no need to start one per window and doing so could cause problems.
Here is an example using the after method:
class PopUpLog(tk.Tk):
def __init__(self, parent):
tk.Tk.__init__(self)
self.y=5
self.c=5 # counter
tk.Button(self.master, text="Write in pop-up", command=self.write).pack(side="left")
# canvas
frameL = tk.Frame(self)
frameL.pack(side="left", fill="both")
self.canvasL = tk.Canvas(frameL, height=800, width=800)
self.canvasL.pack(fill="both", expand=True)
# scrollbar
vsb = ttk.Scrollbar(self, orient="v", command=self.canvasL.yview)
vsb.pack(side="left", fill="y")
self.canvasL.configure(yscrollcommand=vsb.set)
self.canvasL.bind("<Configure>", lambda e:self.canvasL.configure(scrollregion=self.canvasL.bbox("all")))
def write(self, text="hi im a pop-up"):
if self.c > 0:
self.canvasL.create_text(5, self.y, anchor='nw', justify='left', text=text)
self.y += 25
self.c -= 1 # reduce counter
self.after(1000, self.write) # call again in 1 second
else:
self.c = 5 # when counter is 0 reset counter
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="Open window", command=self.popup).pack(side="left")
def popup(self):
self.top = PopUpLog(self)
self.top.geometry("400x400")
self.top.title("pop-up")
if __name__ == "__main__":
root = App()
root.mainloop()

Memory used increases with tkinter notebook and matplotlib

I've got a small program that creates a GUI using tkinter. It contains a button that loads a .csv file, creates a notebook with as many tabs as columns in the csv file. Then, on every active tab (at least this is my intention) I have a plot created from a Figure.
The program works as expected , the only problem is that when switching Tabs, the memory used increases with each Tab click.
Memory usage was monitored using the Windows Task Manager.
After loading a csv file, I didn't see the used memory dropping when I chose not loading a new file.
If I don't call the plotting function, when creating only the Tabs, there is no memory issue.
I already tried to manually invoke the garbage collector with gc.collect(), but that didn't help. This is the code I have:
import matplotlib
matplotlib.use('TkAgg')
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import os
import sys
import tkinter as tk
from tkinter import messagebox as msg
from tkinter import ttk, filedialog
##import gc
class Graphs(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=1)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.protocol('WM_DELETE_WINDOW', self._destroyWindow)
self.frames = {}
frame = StartPage(parent=container, controller=self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def _destroyWindow(self):
self.quit() # stops mainloop
self.destroy()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# initialize lists
self.tabs_list = []
self.hdrs = []
self.figs_list = []
self.ax_list = []
self.canvas_list = []
self.toolbars_list = []
# initialize Data Frame
self.df = pd.DataFrame()
self.nb = None
self.canvas = None
self.toolbar = None
# create LOAD button
self.btn = tk.Button(self, text = 'Load file', command=self.load_csv)
self.btn.pack()
def load_csv(self):
'''
Reset Data Frame;
Destroy notebook if exists;
Load CSV file.
'''
# reset Data Frame
self.df = pd.DataFrame()
# destroy notebook if exists
if self.nb:
self.nb.pack_forget()
self.nb.destroy()
self.nb = None
## gc.collect()
# Select CSV file
self.file_path = filedialog.askopenfilename()
if not self.file_path:
msg.showinfo('Select CSV file', "No file chosen.")
return
try:
# read csv file (exemple.csv)
self.df = pd.read_csv(self.file_path, header=0)
except:
msg.showinfo('Select CSV file', 'Not a csv file / corrupt file.')
return
print(self.df.head())
print(self.df.shape)
# get dimensions
self.m, self.n = self.df.shape
# build the abscissa x from first column
self.x = self.df.iloc[:,0]
# create the notebook
self.nb = ttk.Notebook(self)
# allow Tab navigation
self.nb.enable_traversal()
# add Tabs
for k in range(1, self.n):
hdr = self.df.columns[k]
self.hdrs.append(hdr)
tab = tk.Frame(self.nb, name=hdr.lower())
self.nb.add(tab, text=hdr)
self.tabs_list.append(tab)
self.nb.pack(fill='both', expand=1)
# virtual event after a new tab is selected
self.nb.bind("<<NotebookTabChanged>>", self.plotTH)
def plotTH(self, event):
'''
Plot each Column from Data Frame on its own Tab/Figure
'''
# get path of the selected Tab
tab_path = event.widget.nametowidget(event.widget.select())
# add selected Tab to the list of Tabs
self.tabs_list.append(tab_path)
# get the Tab index;
# When there are no tabs, .select() returns an empty string,
# but .index('current') throws an exception;
# nb.select() returns the Tab NAME (string) of the current selection
if self.nb.select():
i = self.nb.index('current')
# get the Tab text
tab_text = self.nb.tab(i)['text']
else:
return
# remove previous figures ... not sure...
# the used memory as seen in Task Manager still increases
if self.canvas_list:
for cnv in self.canvas_list:
cnv.figure.get_axes().clear()
cnv.get_tk_widget().pack_forget()
cnv.get_tk_widget().destroy()
cnv._tkcanvas.pack_forget()
cnv._tkcanvas.destroy()
cnv = None
if self.figs_list:
for fig in self.figs_list:
fig.delaxes(fig.gca())
plt.cla()
fig.clf()
fig.clear()
plt.close(fig)
self.figs_list = []
# remove toolbar
for widget in tab_path.winfo_children():
widget.pack_forget()
widget.destroy()
self.nb.update() #!!!!!!!!!!!!
######## gc.collect()
# prepare plotting
fig = Figure(figsize=(7, 5), dpi=100)
ax = fig.add_subplot(111)
ax.plot(self.x, self.df.iloc[:,i+1], 'b-', linewidth=1, label=tab_text)
ax.set_xlabel('index')
ax.set_title(self.hdrs[i], fontsize = 8)
ax.legend(loc='best')
ax.grid()
# add to list of figures
self.figs_list.append(fig)
# add to list of axes
self.ax_list.append(ax)
canvas = FigureCanvasTkAgg(fig, master=tab_path)
# add to list of canvases
self.canvas_list.append(canvas)
## self.canvas.draw()
canvas.draw_idle()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas, tab_path)
# add to list of toolbars
self.toolbars_list.append(toolbar)
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def clearPlot(self):
""" not used"""
pass
app = Graphs()
app.title('CSV Plots')
app.geometry('800x600+400+150')
app.resizable(True, True)
app.mainloop();
I created a working csv file using this code:
import numpy as np
import pandas as pd
np.random.seed(0)
df = pd.DataFrame(np.random.randn(50,4), columns=['ALPHA', 'BETA', 'GAMMA', 'DELTA'])
df.index.names = ['Rec']
df.index = df.index + 1
df.to_csv('example.csv', index=True)
print(df)
I'm sorry for the long post. I really don't know were to go from here, so any help would be greatly appreciated.
None of the following lists are ever purged; they hold references to large objects that cannot be garbage collected.
self.tabs_list = []
self.hdrs = []
self.figs_list = []
self.ax_list = []
self.canvas_list = []
self.toolbars_list = []
You probably should create an object that holds the data for each tab, and destroy/purge/reassign to None that when changing tabs.
I changed the code; I realized I was creating a new figure (with all the Artists) and a new canvas (FigureCanvasTkAgg) with every TabChanged event. Instead, I create a figure and its canvas at the moment of Tab addition to the notebook; I do the plot only when the TabChanged occurs and I take care to close all the plots and to destroy the previous notebook when performing a new CSV file load. Destroying the notebook removes the canvases too. Here is the code:
import matplotlib
import matplotlib.style as mplstyle
matplotlib.use('TkAgg')
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import os, sys, time
import tkinter as tk
from tkinter import messagebox as msg
from tkinter import ttk, filedialog
mplstyle.use('fast')
class Graphs(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=1)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.protocol('WM_DELETE_WINDOW', self._destroyWindow)
self.frames = {}
frame = StartPage(parent=container, controller=self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def _destroyWindow(self):
self.quit() # stops mainloop
self.destroy()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# initialize Data Frame
self.df = pd.DataFrame()
self.nb = None
self.fig = None
self.canvas = None
self.toolbar = None
# create LOAD button
self.btn = tk.Button(self, text = 'Load file', command=self.load_csv)
self.btn.pack()
def load_csv(self):
''' Close the plots;
Reset Data Frame;
Destroy notebook if exists;
Load CSV file.
'''
# Setting interactive mode off
if plt.isinteractive():
plt.ioff()
plt.close("all")
# reset Data Frame
self.df = pd.DataFrame()
# initialize list
self.hdrs = []
try:
# destroy notebook if exists
self.nb.pack_forget()
self.nb.destroy()
except:
pass
self.nb = None
# Select CSV file
self.file_path = filedialog.askopenfilename()
if not self.file_path:
msg.showinfo('Select CSV file', "No file chosen.")
return
try:
# read csv file (exemple.csv)
self.df = pd.read_csv(self.file_path, header=0)
except:
msg.showinfo('Select CSV file', 'Not a csv file / corrupt file.')
return
# get dimensions
self.m, self.n = self.df.shape
# build the abscissa x from first column
self.x = self.df.iloc[:,0]
# create the notebook
self.nb = ttk.Notebook(self)
# allow Tab navigation
self.nb.enable_traversal()
# add Tabs
for k in range(1, self.n):
hdr = self.df.columns[k]
self.hdrs.append(hdr)
tab = tk.Frame(self.nb, name=hdr.lower())
self.nb.add(tab, text=hdr)
self.fig = plt.figure(num=hdr.lower(), clear=True, figsize=(7, 5), dpi=100)
# self.ax = self.fig.add_subplot()
self.canvas = FigureCanvasTkAgg(self.fig, master=tab)
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(self.canvas, tab)
toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.nb.pack(fill='both', expand=1)
# virtual event after a new tab is selected
self.nb.bind("<<NotebookTabChanged>>", self.plotTH)
def plotTH(self, event):
'''
Plot each Column from Data Frame on its own Tab/Figure
'''
# Setting interactive mode on is essential: plt.ion()
if not plt.isinteractive():
plt.ion()
# tab index
i = self.nb.index('current')
# tab text
tab_text = self.nb.select().split('.')[-1]
# set current figure
cf = plt.figure(tab_text)
plt.clf()
# plotting
ax = plt.subplot(111)
ax.plot(self.x, self.df.iloc[:,i+1], 'b-', linewidth=1, label=tab_text)
ax.set_xlabel('index')
ax.set_title(self.hdrs[i], fontsize = 8)
ax.legend(loc='best')
ax.grid()
cf.canvas.draw()
app = Graphs()
app.title('CSV Plots')
app.geometry('800x600+400+150')
app.resizable(True, True)
app.mainloop()

TkInter - Can't Get Frames to work correctly and resize

TkInter's frames are driving me crazy. My goal is to have an options frame where I can select some options, then press "Archive" and the TkInter window changes to showing the output from the rest of my script.
I cannot get this to size correctly - there appears to be some additional frame taking up space in the window.
import string
from tkinter import *
import tkinter as tk
import threading
def main(argv):
print("In Main")
for arg in argv:
print(arg)
class TextOut(tk.Text):
def write(self, s):
self.insert(tk.CURRENT, s)
self.see(tk.END)
def flush(self):
pass
class Mainframe(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = OptionsFrame(self)
self._frame.pack(expand=True)
def change(self, frameClass):
# make new frame - for archive output
self._frame = frameClass(self)
self._frame.pack(fill="both", expand=True)
return self._frame
class Mainframe(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = OptionsFrame(self)
self._frame.pack(expand=True)
def change(self, newFrameClass):
# make new frame - for archive output
self._frame = newFrameClass(self)
self._frame.pack(fill="both", expand=True)
return self._frame
class OptionsFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
master.title("Test")
master.geometry("325x180")
self.selectedProject = None
self.initUI(master)
def initUI(self, master):
frame1 = Frame(master)
frame1.pack(fill=BOTH, expand=True)
self.label1 = Label(frame1, text="Select Project to Archive, then click Archive")
self.projectListbox = tk.Listbox(frame1, width=60, height=100)
self.projectListbox.bind("<<ProjectSelected>>", self.changeProject)
# create a vertical scrollbar for the listbox to the right of the listbox
self.yscroll = tk.Scrollbar(self.projectListbox,command=self.projectListbox.yview,orient=tk.VERTICAL)
self.projectListbox.configure(yscrollcommand=self.yscroll.set)
# Archive button
self.archiveBtn=tk.Button(frame1,text="Archive",command=self.ArchiveButtonClick)
# Do layout
self.label1.pack()
self.projectListbox.pack(fill="both", expand=True)
self.yscroll.pack(side="right", fill="y")
self.archiveBtn.pack(side="bottom", pady=10, expand=False)
choices = ["test 1", "test 2", "test 3", "test 4", "test 5", "test 6"]
# load listbox with sorted data
for item in choices:
self.projectListbox.insert(tk.END, item)
def getSelectedProject(self):
# get selected line index
index = self.projectListbox.curselection()[0]
# get the line's text
return self.projectListbox.get(index)
# on change dropdown value
def changeProject(self,*args):
self.selectedProject = self.getSelectedProject()
def ArchiveButtonClick(self):
# Switch to second frame - for running the archive
self.changeProject(None)
# Hide existing controls
self.label1.pack_forget()
self.projectListbox.pack_forget()
self.yscroll.pack_forget()
self.archiveBtn.pack_forget()
newFrame = self.master.change(ArchivingOutputFrame)
newFrame.args = [ "-n", self.selectedProject]
newFrame.start()
# Frame shown while archive task is running
class ArchivingOutputFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
master.title("Test Frame 2")
master.geometry("1000x600")
# Set up for standard output in window
self.var = tk.StringVar(self)
lbl = tk.Label(self, textvariable=self.var)
#lbl.grid(row=0, column=0)
lbl.pack(anchor="nw")
def start(self):
t = threading.Thread(target=self.process)
t.start()
def process(self):
main(self.args)
if __name__=="__main__":
# If command line options passed - skip the UI
if len(sys.argv) > 1:
main(sys.argv[1:])
else:
app=Mainframe()
text = TextOut(app)
sys.stdout = text
sys.stderr = text
text.pack(expand=True, fill=tk.BOTH)
app.mainloop()
Here is what I get in the UI; note this is showing the UI hierachy from Microsoft's Spy++ - there is a frame I didn't create (at least I don't think I did) that is at the bottom of the window and taking up half of the UI area; this is the yellow highlight. My options pane is thus squeezed into the top half.
Resize also doesn't work - if I resize the window, I get this:
When I click the button and the code to remove the options frame and put in the frame that is capturing stdout/stderr from the main script runs, I get this:
Now the extra space appears to be at the top!
Thanks for any ideas - I know I could switch to using the "Grid" UI layout engine, but this seems so simple - I'm not doing anything sophisticated here that shouldn't work with pack.
That was a lot of complicated code. It would be easier to debug if you provide a Minimal, Complete, and Verifiable example.
However; the bottom Frame is the TextOut() widget that you pack after Mainframe():
if __name__=="__main__":
app = Mainframe()
text = TextOut(app) # This one
sys.stdout = text
sys.stderr = text
text.pack(expand=True, fill=tk.BOTH)
app.mainloop()
You'll have an easier time debugging if you give each widget a bg colour and then give them all some padding so you can easier identify which widget is inside which widget.

Python 3.5 tkinter confirmation box created progress bar and reads in csv not quite working

I'm realtively new to python and am making a GUI app that does a lot of file i/o and processing. To complete this i would like to get a confirmation box to pop-up when the user commits and actions. From this when clicking 'yes' the app then runs the i/o and displays a progress bar.
From other threads on here I have gotten as far as reading about the requirement to create an addtional thread to take on one of these processes (for example Tkinter: ProgressBar with indeterminate duration and Python Tkinter indeterminate progress bar not running have been very helpful).
However, I'm getting a little lost because I'm not activating the threaded process from the Main() function. So I'm still getting lost in how, and where, I should be creating the progress bar and passing of the i/o process to another thread (reading in a csv file here).
Here is my code and I would be very grateful for any help anyone can give me:
import tkinter as tk
import tkinter.messagebox as messagebox
import csv
import tkinter.ttk as ttk
import threading
class ReadIn(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Read in file and display progress")
self.pack(fill=tk.BOTH, expand=True)
self.TestBtn = tk.Button(self.parent, text="Do Something", command=lambda: self.confirm_pb())
self.TestBtn.pack()
def confirm_pb(self):
result = messagebox.askyesno("Confirm Action", "Are you sure you want to?")
if result:
self.handle_stuff()
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
nf.join()
def Pbar(self):
self.popup = tk.Tk()
self.popup.title('Loading file')
self.label = tk.Label(self.popup, text="Please wait until the file is created")
self.progressbar = ttk.Progressbar(self.popup, orient=tk.HORIZONTAL, length=200,
mode='indeterminate')
self.progressbar.pack(padx=10, pady=10)
self.label.pack()
self.progressbar.start(50)
def import_csv(self):
print("Opening File")
with open('csv.csv', newline='') as inp_csv:
reader = csv.reader(inp_csv)
for i, row in enumerate(reader):
# write something to check it reading
print("Reading Row " + str(i))
def main():
root = tk.Tk() # create a Tk root window
App = ReadIn(root)
root.geometry('400x300+760+450')
App.mainloop() # starts the mainloop
if __name__ == '__main__':
main()
The statement nf.join() in function handle_stuff() will block tkinter's main loop to show the progress bar window. Try modify handle_stuff() as below:
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
#nf.join() # don't call join() as it will block tkinter's mainloop()
while nf.is_alive():
self.update() # update the progress bar window
self.popup.destroy()

tkinter Text Widget duplicating when progress bar displayed

I have an app that displays progress messages via a Text widget, so that it will scroll if there are many lines. It also displays a 'Task' Progress Bar for each individual step, and a different 'Total' Progress Bar for the total completion.
My situation is that I want to have the progress bars not displayed if they have no 'content'. So neither of them at the start and end of the processing. The 'Total' progress bar would display once the first step is complete, and the 'Task' progress bar would be displayed if the task is long enough to warrant it.
It's a big app but here is a shortened version of what I have (still long though).
#!/usr/bin/python
# coding=utf-8
# Try to work with older version of Python
from __future__ import print_function
import sys
if sys.version_info.major < 3:
import Tkinter as tk
import Tkinter.ttk as ttk
else:
import tkinter as tk
import tkinter.ttk as ttk
#============================================================================
# MAIN CLASS
class Main(tk.Frame):
""" Main processing
"""
def __init__(self, root, *args, **kwargs):
tk.Frame.__init__(self, root, *args, **kwargs)
self.root = root
self.MW_f = tk.Frame(self.root)
# Progress Messages
self.PM_prog_msgs_lf_var = tk.StringVar()
self.PM_prog_msgs_lf = tk.LabelFrame(self.MW_f,
text=' Progress Messages: ',
relief='sunken')
# Progress Message text widget
self.PM_prog_msgs_max_frame_height = 6
self.PM_prog_msgs_line_count = 0
self.PM_prog_msgs_t = tk.Text(self.PM_prog_msgs_lf, height=self.PM_prog_msgs_max_frame_height, width=100)
self.PM_prog_msgs_t_vbar = ttk.Scrollbar(self.PM_prog_msgs_lf,
orient='vertical', command=self.PM_prog_msgs_t.yview)
self.PM_prog_msgs_t['yscrollcommand'] = self.PM_prog_msgs_t_vbar.set
self.PM_prog_msgs_t['state'] = 'disabled'
self.PM_prog_msgs_t['border'] = 3
self.PM_task_progbar_var = tk.IntVar()
self.PM_task_progbar = ttk.Progressbar(self.MW_f,
orient='horizontal',
mode='indeterminate',
variable=self.PM_task_progbar_var)
self.PM_progbar_var = tk.IntVar()
self.PM_progbar = ttk.Progressbar(self.MW_f,
orient='horizontal',
mode='determinate',
variable=self.PM_progbar_var,
maximum=4)
self.PM_go_btn = tk.Button(self.MW_f,
text='Go',
command=self.action_go_btn)
self.MW_stsbar_tvar = tk.StringVar()
self.MW_stsbar = tk.Label(self.MW_f,
textvariable=self.MW_stsbar_tvar)
# Grid the widgets
self.MW_f.grid()
MW_grid_row = 0
# Place into MW_f
self.PM_prog_msgs_lf.grid(row=MW_grid_row, column=0, sticky='we')
# Place into PM_prog_msgs_lf
self.PM_prog_msgs_t.grid(row=0, column=0)
self.PM_prog_msgs_t_vbar.grid(row=0, column=1, sticky='ns')
MW_grid_row += 1
self.PM_task_progbar_row = MW_grid_row
self.PM_task_progbar.grid(row=self.PM_task_progbar_row, sticky='we')
MW_grid_row += 1
self.PM_progbar_row = MW_grid_row
self.PM_progbar.grid(row=self.PM_progbar_row, sticky='we')
MW_grid_row += 1
self.MW_stsbar.grid(row=MW_grid_row, sticky='w')
MW_grid_row += 1
self.PM_go_btn.grid(row=MW_grid_row)
# Remove these until needed
self.PM_task_progbar.grid_remove()
self.PM_progbar.grid_remove()
self.PM_prog_msgs_t_vbar.grid_remove()
self.MW_dsp_stsbar_msg('Processing: Refer to progress message pane for more details')
# Window Displays Now
#=========================================================================================
# Functions used by window
def action_go_btn(self):
"""
"""
msg = 'Line 1\nLine 2'
self.PM_dsp_prog_msgs(msg, 2)
self.PM_task_progbar_update()
time.sleep(5)
self.PM_progbar_update()
self.PM_dsp_prog_msgs('Line 3', 1)
self.PM_task_progbar_update()
time.sleep(5)
self.PM_progbar_update()
msg = 'Line 4\nLine 5\nLine 6'
self.PM_dsp_prog_msgs(msg, 3)
self.PM_task_progbar_update()
time.sleep(5)
self.PM_progbar_update()
msg = 'Line 7\nLine 8\nLine 9'
self.PM_dsp_prog_msgs(msg, 3)
self.PM_task_progbar_update()
time.sleep(5)
self.PM_progbar_update()
self.PM_progbar.grid_remove()
return
def MW_dsp_stsbar_msg(self, msg):
"""
"""
# Display the statusbar
self.MW_stsbar_tvar.set(msg)
return
def PM_dsp_prog_msgs(self, msg, lines):
"""
"""
# Populate the message
self.PM_prog_msgs_t['state'] = 'normal'
self.PM_prog_msgs_t.insert(tk.END, ('\n'+msg))
self.PM_prog_msgs_t['state'] = 'disabled'
self.root.update_idletasks()
self.PM_prog_msgs_line_count += lines
# If its time display the vert scroll bar
if not self.PM_prog_msgs_t_vbar.winfo_viewable():
if self.PM_prog_msgs_line_count > self.PM_prog_msgs_max_frame_height:
self.PM_prog_msgs_t_vbar.grid(row=0, column=1)
self.root.update_idletasks()
# Show the last line.
self.PM_prog_msgs_t.see(tk.END)
self.root.update_idletasks()
return
def PM_progbar_update(self):
"""
"""
if not self.PM_progbar.winfo_viewable():
self.PM_progbar.grid()
# Remove the task progbar
self.PM_task_progbar.stop()
self.PM_task_progbar.grid_remove()
# Increment the progbar
self.PM_progbar.step()
self.root.update_idletasks()
return
def PM_task_progbar_update(self):
"""
"""
# Display if not already displayed
if not self.PM_task_progbar.winfo_viewable():
self.PM_task_progbar.grid()
self.root.update_idletasks()
# Step
self.PM_task_progbar.start()
self.root.update_idletasks()
return
# MAIN (MAIN) ===========================================================================
def main():
""" Run the app
"""
# # Create the screen instance and name it
root = tk.Tk()
app = Main(root)
root.mainloop()
root.quit()
# MAIN (STARTUP) ====================================================
# This next line runs the app as a standalone app
if __name__ == '__main__':
# Run the function name main()
main()
What happens is when the 'Task' progress bar is displayed the 'Progress Messages' label frame is duplicated, and moved down one line (see the screen shot)
Sorry, I cannot attach the screen shot. This is a text version of the screenshot!
Progress Messages ---------------------------------------
Progress Messages ---------------------------------------
Message Step 1
[Task Progress Bar ]
[Total Progress Bar ]
The display remains this way until the end. Even when the 'Task' progress bar is removed the duplication remains. The display does not add a third version when the 'Total' progress bar is displayed though.
Everything corrects itself at the end when both progress bars are removed.
All the widgets are gridded to the correct frame and the row/column seems to be correct, so where do I look to correct this.
Many thanks, and sorry this is all so long.
MacBookPro (2007), python 3.4, tkinter 8.5

Resources