tkinter progress bar with file list - python-3.x

I have a loop that read files in python like below:
def Rfile():
for fileName in fileList:
….
How can I add a tkinter progress bar that will be linked to the for loop and the size of the fileList (start before the loop and close after the loop)?.
Thx

This little script should demonstrate how to do that:
import tkinter as tk
from time import sleep
# The truncation will make the progressbar more accurate
# Note however that no progressbar is perfect
from math import trunc
# You will need the ttk module for this
from tkinter import ttk
# Just to demonstrate
fileList = range(10)
# How much to increase by with each iteration
# This formula is in proportion to the length of the progressbar
step = trunc(100/len(fileList))
def MAIN():
"""Put your loop in here"""
for fileName in fileList:
# The sleeping represents a time consuming process
# such as reading a file.
sleep(1)
# Just to demonstrate
print(fileName)
# Update the progressbar
progress.step(step)
progress.update()
root.destroy()
root = tk.Tk()
progress = ttk.Progressbar(root, length=100)
progress.pack()
# Launch the loop once the window is loaded
progress.after(1, MAIN)
root.mainloop()
You can always tweak it to perfectly satisfy your needs.

Related

Why is Python Skipping Over Tkinter Code?

I'm in Python 3.x using Tkinter to make a button that changes a boolean variable's value from true to false then a if statement to check if that value is false. Here is my code for that:
import tkinter
import time
x = True
top = tkinter.Tk()
def helloCallBack():
x = False
print (x)
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
if x == False:
print ('Hello')
top.mainloop()
This unfortunately didn't work, so I replaced the if statement with a time.sleep(10) and then print (x) so I would have enough time to press the button like so:
import tkinter
import time
x = True
top = tkinter.Tk()
def helloCallBack():
x = False
print (x)
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
time.sleep(10)
print (x)
top.mainloop()
The issue with this is it skips over all of the Tkinter code and goes to the time.sleep(10), prints the value of x then brings everything from Tkinter up after. Why is it doing this?
The reason you see that the code does sleep() and then prints True before the tkinter windows opens is due to how the mainloop() works in tkinter.
sleep() is useful in python however due to tkinter's single threaded nature all sleep() can due is block the mainloop until it has completed sleeping. Tkinter has its own method to work around this issue called after() and you don't need it here but it is very useful for timing things in tkinter.
Tkinter does its updates in the mainloop and you have a sleep() command that occurs before your mainloop as well as a print command before the mainloop. Both of those things must finish execution before the mainloop is reached for the first time thus not allowing tkinter to start until they are complete.
What you want to do is to place this print statement into your function. As well as a few other quality of life clean ups.
See this example:
import tkinter as tk
top = tk.Tk()
x = True
def hello_call_back():
global x
x = False
if not x:
print('Hello')
tk.Button(top, text="Hello", command=hello_call_back).pack()
top.mainloop()
However in this case I think the simplest form of the code should look like this:
import tkinter as tk
top = tk.Tk()
def hello_call_back():
print('Hello')
tk.Button(top, text="Hello", command=hello_call_back).pack()
top.mainloop()
As your current use of x is redundant to the goal.

tkinter.filedialog.asksaveasfilename slow to save a file

I noticed that tkinter filedialog asksaveasfilename takes 3 seconds or more to save a file. is it normal? is there a way to make it faster? (notepad or notepad ++ are faster than it)
Python 3.7
example
import tkinter
import tkinter.filedialog
import tkinter.scrolledtext
class app:
def __init__(self):
self._root= tkinter.Tk()
self._root.grid()
self._text= tkinter.Text(self._root)
self._text.grid()
self.menubar = tkinter.Menu(self._root)
self.filemenu = tkinter.Menu(self.menubar)
self.filemenu.add_command(label="Salva", command=self._save)
self.menubar.add_cascade(label="File", menu=self.filemenu)
self._root.config(menu=self.menubar)
def _save(self):
path = tkinter.filedialog.asksaveasfilename(title="Save")
if len(path) > 0:
with open(path, "w") as f:
f.write(self._text.get("1.0", "end-1c"))
def show(self):
self._root.mainloop()
a=app()
a.show()
I tested the provided code and it was saving the file in sub-millisecond time.
Did you use a time function to check the time it took to save? If you did you must make sure to mark the start time before you ask the user for the file location, otherwise it will include the time it takes for the user to pick the file location they want.
def _save(self):
# Do Not Start timer here
path = tkinter.filedialog.asksaveasfilename(title="Save")
# Start timer here
start = time.time()
if len(path) > 0:
with open(path, "w") as f:
f.write(self._text.get("1.0", "end-1c"))
print(time.time() - start)

How to create a progressbar capturing the progress of a subprocess in tkinter with threading

I am currently programming a GUI with tkinter in Python 3.6 for another Program, which is called through subprocess. I would like to represent the progress of the Program called, with a Progressbar, popping up in another window as soon as I hit the 'Run Program' Button. The value of the progressbar is set by arguments passed from the subprocess script, where I calculate the percentage of progress in a function. What I need:
1. How do I properly use the Threading-Module for spawning the subprocess?
2. How do I import a function (and therefore its arguments) of a script, that runs with command line parameters?
I was looking for a answer in other posts the whole day, but couldn't really connect them to my problem.
I tried to spawn the subprocess by simply calling its function with threading.Thread(target=self.runProgram()).start()
Apparently the GUI-program still waits until the subprocess/Thread is finished and then continues with creating the progressbar, but I expected it to happen at the same time. I then tried to import the percentage value of the subprocess-Program, which didn't work due to missing parameters. Also I don't think that this would really work the way I want it to, because I want the arguments passed during the runtime, when the subprocess is called. It's very hard to properly explain what I am looking for, so here is some code:
GUI-Program:
import tkinter as tk
from tkinter import ttk
from tkinter import *
from tkinter.filedialog import askdirectory,askopenfilename
import os,subprocess
import threading
class eodApplication(tk.Tk):
def __init__(self):
#different entryboxes are created, not necessary for understanding
self.runButton=''
self.progress=''
self.window = tk.Tk()
self.window.geometry("500x350")
self.window.configure(background="white")
self.window.title("End of Day Application")
self.create_widgets()
def create_widgets(self):
#creating entryboxes initiated in __init__-method, containing paths one can browse for and a submit/'Run Program'-Button
self.submit()
def submit(self):
#Button calls function when pressed
self.runButton=ttk.Button(self.window,text='Run Program',command=lambda:self.bar_init())
self.runButton.grid(row=7, columnspan=2)
def bar_init(self):
barWindow = tk.Tk()
barWindow.geometry("250x150")
barWindow.configure(background="white")
barWindow.title("Progress")
self.progress=ttk.Progressbar(barWindow, orient="horizontal", length=200, mode="determinate",value=0)
self.progress.grid(row=0, column=0, sticky=tk.W)
threading.Thread(target=self.runProgram()).start()
from PythonProgrammeDWH import get_percentage
self.progress["value"] = percentage
def runProgram(self):
devnull = open(os.devnull, 'w')
subprocess.call(f"{self.entryboxPyExe.get()} {self.entryboxPyCode.get()} {self.entrybox64.get()} {self.entrybox66.get()} {self.entryboxXsd.get()} {self.entryboxOutput.get()} {self.entryboxXmlExe.get()}",cwd=os.getcwd(),stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,shell=True)
eodApplication=eodApplication()
eodApplication.window.mainloop()
PythonProgrammeDWH (subprocess):
def main(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4]):
~ a lot of stuff going on ~
sumFiles=len(os.listdir(path))
checkedFiles=0
for file in os.listdir(path):
checkedFiles+=1
get_percentage(sumFiles, checkedFiles)
def get_percentage(sumFiles, checkedFiles):
percentage= round((checkedFiles/sumFiles)*100)
return percentage
#percentage is the argument I want to pass to the other script for updating the progressbar

how to make the python program light, which is written to print the text on screen directly without window?

From last few days, I have been working on a program which one part is to show the text directly on the window screen, and also update/change them as per requirement. I have completed this part with Tkinter module in python.
The problem is whenever I run the program it behaves like a heavy program due to which other processes become slow. Also If I tried to do some other process in parallel, the background of the text becomes black, which is absolutely undesirable.as the shown in image
I also want to show some dynamic symbol like loading but the use of two Tkinter widget make it slower. if possible please make it more modular and light.
from tkinter import *
import win32api, win32con, pywintypes
from time import sleep
f=Tk()
var = StringVar()
var.set(' ')
f =Label(textvariable = var, font=('Cooper','60'), fg='blue', bg='white')
f.master.overrideredirect(True)
f.master.geometry("+900+200")
f.master.lift()
f.master.wm_attributes("-topmost", True)
f.master.wm_attributes("-disabled", True)
f.master.wm_attributes("-transparentcolor", "white")
f.pack()
for i in range(10):
sleep(5) # Need this to slow the changes down
var.set(u'[ A ]' if i%2 else u'[ B ]')
f.update_idletasks()
also, want to ask can we do it without using the Tkinter module.so it becomes more light and modular. and dependency will be less.
Here is a code which makes your code responsive and also don't use your cpu too much.
from tkinter import *
import win32api, win32con, pywintypes
import time
f=Tk()
var = StringVar()
var.set(' ')
f =Label(textvariable = var, font=('Cooper','60'), fg='blue', bg='white',bd=0)
f.master.overrideredirect(True)
f.master.geometry("+900+200")
f.master.lift()
f.master.wm_attributes("-topmost", True)
f.master.wm_attributes("-disabled", True)
f.master.wm_attributes("-transparentcolor", "white")
f.pack()
for i in range(10):
f.update()
t = time.time()
while time.time() - t < 5:
f.update()
var.set(u'[ A ]' if i%2 else u'[ B ]')
f.update_idletasks()
f.update()
Here is an image of task manager. Its taking only 15 MB memory and no cpu:

How to create GUI objects one by one with Tkinter

I like to create an object per second and make the show up one by one. However, the code below wait for 3 seconds and show them all at the same time.
from tkinter import *
import time
def create():
for i in range(3):
r4=Radiobutton(root, text="Option 1"+str(i), value=1)
r4.pack( anchor = W )
time.sleep(1)
root = Tk()
create()
root.mainloop()
Your code, as is, creates a one object per second as you desired it, but this objects need to be shown, and they're shown when code flow reaches the mainloop. Hence, for observer, it looks like there're no objects at all after one second.
Of course, you can use sleep and update, but beware - sleeping leads to unresponsive window, so it's OK option (to be honest - not OK at all), if your application isn't drawn and you're outside of mainloop, but if it's not - prepare for a "frozen" window, because GUI can redraw himself only in mainloop (an event loop, you can reach it with update as well) and sleep blocks this behaviour.
But there's a good alternative, the after method, take a look on it!
And there's a snippet, so you can see the difference:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import time
def create_with_sleep():
for _ in range(3):
tk.Radiobutton(frame_for_sleep, text="Sleep Option").pack(anchor='w')
time.sleep(int(time_entry.get()))
root.update()
def create_with_after(times=3):
if times != 0:
tk.Radiobutton(frame_for_after, text="After Option").pack(anchor='w')
times -= 1
root.after(int(time_entry.get()) * 1000, lambda: create_with_after(times))
root = tk.Tk()
test_yard_frame = tk.Frame(root)
frame_for_sleep = tk.Frame(test_yard_frame)
frame_for_after = tk.Frame(test_yard_frame)
test_yard_frame.pack()
frame_for_sleep.pack(side='left')
frame_for_after.pack(side='left')
button_frame = tk.Frame(root)
button_for_sleep = tk.Button(button_frame, text='Create 3 radiobuttons with sleep+update', command=create_with_sleep)
button_for_after = tk.Button(button_frame, text='Create 3 radiobuttons with after', command=create_with_after)
button_frame.pack()
button_for_sleep.pack(side='left')
button_for_after.pack(side='left')
time_label = tk.Label(root, text='Time delay in seconds:')
time_label.pack(fill='x')
time_entry = tk.Entry(root)
time_entry.insert(0, 1)
time_entry.pack(fill='x')
root.mainloop()
With 1 seconds delay there's no much difference, but you can try to increase delay to understand why after option is preferable in general.
You can use update to call a refresh on your objects.
In use, you would have to add the line root.update() in your for loop.

Resources