Tkinter updating label after using pyinstaller - python

When I run the .py file, the program runs fine as in self.error updates as robocopy is running. However after I've changed it to a .exe file using pyinstaller -F -w Shortcutsv2.1.py I've noticed that the tkinter label which shows how many files it has updated (self.error) doesn't update until after the terminal is closed. Is there a way to have it work like the script? I'm assuming this is due to a setting in pyinstaller but I may be wrong
CODE:
from subprocess import PIPE, run
import tkinter as tk
from tkinter import ttk
import tkinter.messagebox as mb
import tkinter.font as tkFont
from os import path
from os import listdir
from os import mkdir
from os import walk
from glob import glob
import threading
import pandas as pd
from datetime import datetime
class Windows(threading.Thread):
def __init__(self, master):
threading.Thread.__init__(self)
self.width = 400
self.height = 150
master.geometry(f"{self.width}x{self.height}")
master.title("Shortcuts")
#master.iconbitmap(r'ip.ico')
self.frame = tk.Frame(master,bg="white")
self.frame.place(relx=0, rely=0, relwidth=1, relheight=1)
self.font = ('Helvetica', '10')
#Source path label and text entry
self.srcLabel = tk.Label(self.frame, text="Source Path:", font=self.font, bg="white")
self.srcLabel.place(relx=0.001, rely=0.026, relwidth=0.31, relheight=0.2)
self.srcEntry = tk.Entry(self.frame, font=self.font, bg="white")
self.srcEntry.place(relx=0.31, rely=0.026, relwidth=0.68, relheight=0.2)
#Destination path label and text entry
self.dstLabel = tk.Label(self.frame, text="Destination Path:", font=self.font, bg="white")
self.dstLabel.place(relx=0.001, rely=0.246, relwidth=0.31, relheight=0.2)
self.dstEntry = tk.Entry(self.frame, font=self.font, bg="white")
self.dstEntry.place(relx=0.31, rely=0.246, relwidth=0.68, relheight=0.2)
#New Folder to be created label and text entry
self.nfLabel = tk.Label(self.frame, text="New Folder:", font=self.font, bg="white")
self.nfLabel.place(relx=0.001, rely=0.466, relwidth=0.31, relheight=0.2)
self.nfEntry = tk.Entry(self.frame, font=self.font, bg="white")
self.nfEntry.place(relx=0.31, rely=0.466, relwidth=0.68, relheight=0.2)
#Submit
self.submit = tk.Button(self.frame, text="Submit", bg="white", font=self.font,
command = self.threadCmd)
self.submit.place(relx=0.5, rely=0.733, relwidth=0.3, relheight=0.2)
#Errors
self.error = tk.Label(self.frame, text="", font=self.font, bg="white", fg="red")
self.error.place(relx=0.001, rely=0.733, relwidth=0.53, relheight=0.2)
def findFile(self, dirPath):
#finds the full path of all files including within the subdirectories
i = 0
temp = []
filesInDir = [f"{dirPath}\\{a}" for a in listdir(dirPath)]
for a in filesInDir:
i += 1
fullPath = path.join(dirPath, a)
if path.isdir(fullPath):
temp = temp + self.findFile(fullPath)
else:
temp.append(fullPath)
return temp
def lowestFolder(self, direc):
#finds the lowest last folder if any and creates the ~~~~.txt in there to ensure it is the last file to be copied
subdir = [x[0] for x in walk(direc)]
subdir.sort()
if subdir[-1] == direc:
f = open(path.join(direc,"~~~~.txt"),"w+")
f.close()
else:
self.lowestFolder(str(path.join(direc,subdir[-1])))
def Run(self):
if not path.exists(self.srcEntry.get()):
self.error.config(text="Can't find src path", fg="red")
elif not path.exists(self.dstEntry.get()):
self.error.config(text="Can't find dstn path", fg="red")
else:
dest = self.dstEntry.get() + "\\" + self.nfEntry.get()
if path.isdir(dest):
self.error.config(text="Folder Exists", fg="red")
else:
self.error.config(text="")
self.filename = "logs.xlsx"
self.listOfFiles = glob(path.join(".",self.filename))
self.lowestFolder(self.srcEntry.get())
filesTransferred = self.findFile(self.srcEntry.get())
length = len(filesTransferred)
mkdir(dest)
date = datetime.now()
run(f"start cmd /K RoboCopy.exe \"{self.srcEntry.get()}\" \"{dest}\" *.* /E /Z", stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=True)
#Checks if all files have been transferred before moving on
i = 0
while(i < length):
fullPath = filesTransferred[i].replace(self.srcEntry.get(), dest)
if path.exists(fullPath):
i += 1
self.error.config(text=f'Transferring file(s): {i}/{length}', fg='black')
temp2 = self.findFile(dest)
temp2 = [x.replace(dest, "..") for x in temp2]
if length == len(temp2):
#Creates log file if not created
if not self.listOfFiles:
writer = pd.ExcelWriter(self.filename, engine='xlsxwriter')
writer.save()
df = pd.read_excel(self.filename)
#creates file if it doesn't exist else apppends
df2 = pd.DataFrame({"Started" : [date],
"Source": [self.srcEntry.get()],
"Destination": [self.dstEntry.get()],
"Files": [", ".join(temp2)]})
df = df.append(df2, sort=False)
df.to_excel(self.filename, index=False)
self.error.config(text="Files copied successfully.", fg="green")
#IF ROBOCOPY FREEZES WHEN YOU CLICK ON IT THEN JUST PRESS TAB AND IT SHOULD RESUME
else:
self.error.config(text="All files were not copied.", fg="red")
def threadCmd(self):
self.result = None
y = threading.Thread(target=self.Run)
y.start()
if __name__ == "__main__":
root = tk.Tk()
x = threading.Thread(target=Windows, args=(root,))
x.start()
x.join
root.mainloop()
EDIT 1
"There are couple problems here. 1. Inherit class Windows(threading.Thread): but don't use this object. 2. Passing a class definition as .Thread(target=Windows. 3. Useless x.join without calling it. 4. Accessing tkinter objects, e.g.self.srcEntry.get(), from a Thread."
I've changed the code to the below so now:
1) Can't make this change as I'm using .start()
2) Changed this to an instance
3) called the function
4) Didn't make this change as wasn't sure what the issue was. Needs more clarification
The issue which I had posted for still persists however I appreciate the help to tidy up my code :)
if __name__ == "__main__":
root = tk.Tk()
x = Windows(root)
x.start()
x.join()
root.mainloop()

I was using subprocess.run instead of subprocess.Popen, subprocess.run waits for the terminal command to complete before moving on with the script. Whereas subprocess.Popen doesn't.
Edit: What I find strange is that when I would run it as a .py file subprocess.run worked the same as subprocess.Popen however when I had used pyinstaller then subprocess.run worked how it's supposed to.

Related

Python code to close tkinter pop up window without killing the actual program

I'm new to Python and with help from here I have drafted a code that gets input from a user and pass it on to other programs.
is there a way to close the pop up window without actually terminating or closing the program. I tried using Destroy() but it either just clears the content of the pop up message or messes the whole code.
Could somebody help me out here please. Below is the code I drafted.
import openpyxl
import tkinter as tk
class App(tk.Frame):
def __init__(self,master=None,**kw):
self.answers = {}
tk.Frame.__init__(self,master=master,**kw)
tk.Label(self,text="Give Input Sheet Path").grid(row=0,column=0)
self.Input_From_User1 = tk.Entry(self)
self.Input_From_User1.grid(row=0,column=1)
tk.Label(self,text="Give Output Sheet Path").grid(row=1,column=0)
self.Input_From_User2 = tk.Entry(self)
self.Input_From_User2.grid(row=1,column=1)
tk.Button(self,text="Feed into Program",command = self.collectAnswers).grid(row=2,column=1)
def collectAnswers(self):
self.answers['Input_Path'] = self.Input_From_User1.get()
self.answers['Output_Path'] = self.Input_From_User2.get()
print("Given Input Path ", self.answers['Input_Path'])
print("Given Output Path ", self.answers['Output_Path'])
global Input_Path
global Output_Path
Input_Path = self.answers['Input_Path']
Output_Path = self.answers['Output_Path']
def main():
root = tk.Tk()
root.geometry("300x100")
App(root).grid()
root.mainloop()
wb = openpyxl.load_workbook(Input_Path)
return wb["Sheet1"]
if __name__ == '__main__':
main()
If all you want is for the TK window to "go away" without actually closing, you could use the iconify() method instead of destroy(); this will minimize the App() window to the taskbar without actually closing it.
I added self.iconify() to the end of your collectAnswers() method.
I also took the liberty of making some changes to your code's style to better align with standard Python style practices - if you aren't already using a linter like Flake8 or a formatter like Black, I would recommend doing so! Better to get used to having a style guide enforced before you develop bad habits!
import openpyxl
import tkinter as tk
class App(tk.Tk): # root application instance
def __init__(self,**kw) -> None:
super().__init__()
self.geometry("300x100")
self.title = ("Enter Sheet Paths")
self.resizable(width=False, height=False)
self.grid()
input_label = tk.Label(
self,
text="Give Input Sheet Path"
)
input_label.grid(row=0, column=0)
self.Input_From_User1 = tk.Entry(self)
self.Input_From_User1.grid(row=0, column=1)
output_label = tk.Label(
self,
text="Give Output Sheet Path"
)
output_label.grid(row=1, column=0)
self.Input_From_User2 = tk.Entry(self)
self.Input_From_User2.grid(row=1, column=1)
submit = tk.Button(
self,
text="Feed into Program",
command=self.collect_answers
)
submit.grid(row=2, column=1)
self.answers = {}
def collect_answers(self) -> None:
self.answers['Input_Path'] = self.Input_From_User1.get()
self.answers['Output_Path'] = self.Input_From_User2.get()
print("Given Input Path ", self.answers['Input_Path'])
print("Given Output Path ", self.answers['Output_Path'])
input_path = self.answers['Input_Path']
output_path = self.answers['Output_Path']
self.iconify() # MINIMIZE THE WINDOW
def main():
app = App()
app.mainloop()
wb = openpyxl.load_workbook(Input_Path)
return wb["Sheet1"]
if __name__ == '__main__':
main()

Getting missing required dependencies Numpy while using Pyinstaller

I have created a mini app that is used for combining a bunch of excel files, but I cant execute the exe due to this error. What I find strange is that I am not even using Numpy in my script. I have installed and reinstalled numpy, but that did not fix the issue.
I created a virtual environment which has the following libraries installed.
Error on Executing exe
Here is my code:
import tkinter as tk
from tkinter.simpledialog import askstring, askinteger
from tkinter.messagebox import showerror
from tkinter import messagebox
from tkinter import filedialog
from tkinter import ttk
import os
from os import path
import pandas as pd
import fnmatch
import glob
import datetime
from datetime import datetime
import time
import calendar
class Splash(tk.Toplevel):
def __init__(self, parent, width=0.8, height=0.6, useFactor=True):
tk.Toplevel.__init__(self, parent)
self.title("Splash")
w = 300
h = 200
x = 50
y = 100
self.geometry('%dx%d+%d+%d' % (w, h, x, y))
lbl = tk.Label(self,text="Combine Excel\n Version 1.0", bg = 'lightgrey' )
lbl.place(x=25, y=30)
self.master.overrideredirect(True)
self.lift()
## required to make window show before the program gets to the mainloop
self.update()
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.withdraw()
splash = Splash(self)
## simulate a delay while loading
time.sleep(1)
## finished loading so destroy splash
splash.destroy()
def getfiles():
listbox.delete(0,tk.END)
beginput = entry_1.get()
endinput = entry_2.get()
timefmt = "%m/%d/%y"
start = calendar.timegm(datetime.strptime(beginput, timefmt).timetuple())
end = calendar.timegm(datetime.strptime(endinput, timefmt).timetuple())
year = datetime.strptime(beginput, timefmt).strftime('%Y')
monthyr = datetime.strptime(beginput, timefmt).strftime('%m-%y') + ' Daily Collection'
yearmm = 'CA_LoanLvl_' + time.strftime("%Y%m%d")
yrnmsum = 'CA_Sum_' + time.strftime("%Y%m%d")
frame = pd.DataFrame()
list_ = []
cols = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,37,39,40,41,43,44,45,46,47,49,50,51,52,53,54,55,56,57]
path1 = path.join(r'\\corp.int\cms\DeptData\Monthly Reporting'
'\Daily Collection',year,monthyr,'Data')
pathexpt = path.join(r'\\corp.int\cms\DeptData\XX\DS\DataDownloads\Combine',yearmm)
pathexptsum = path.join(r'\\corp.int\cms\DeptData\XX\DS\DataDownloads\Combine',yrnmsum)
mypath = path1
def test(f):
if (not os.path.isfile(f)):
return 0
(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(f)
return start<=ctime and end>=ctime
files = [f for f in glob.glob(os.path.join(mypath, "adv*")) if test(f)]
for item in files:
listbox.insert(tk.END, os.path.basename(item))
if __name__ == "__main__":
App()
# Create the main window
root = tk.Tk()
s = ttk.Style(root)
s.theme_use('clam')
root.title("Combine GNMA Files")
# set the root window's height, width and x,y position
# x and y are the coordinates of the upper left corner
w = 600
h = 400
x = 50
y = 100
# use width x height + x_offset + y_offset (no spaces!)
root.geometry("%dx%d+%d+%d" % (w, h, x, y))
# use a colorful frame
frame = tk.Frame(root, bg='darkblue')
frame.pack(fill='both', expand='yes')
label = tk.Label(frame, text="Start Date-mm/dd/yy", bg = 'lightblue')
label.place(x=20, y=30)
label2 = tk.Label(frame, text="End Date-mm/dd/yy", bg = 'lightblue')
label2.place(x=20, y=100)
entry_1 = tk.Entry(root)
entry_1.place(x=20,y=60)
entry_2 = tk.Entry(root)
entry_2.place(x=20,y=130)
btn_1 = tk.Button(root,text="Get Files", bg='light grey', command = getfiles)
btn_1.place(x=400, y=15)
listbox = tk.Listbox(root)
listbox.place(x=20, y=160, width = 400)
def on_closing():
if messagebox.askokcancel("Quit", "Do you want to quit?"):
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
If anyone runs across this, it seems it has to do with the version of Numpy that is causing errors with Pyinstaller. Take a look at the link below.
[How to fix 'Missing required dependencies ['numpy']' when running packaged app made with PyInstaller?

How to retrieve file name after choosing in dialog box using Python Tkinter

Please advise on how to retrieve a file's full path into a variable after i pick one using tkinter
The whole idea of my GUI is to:
1. Have few buttions
2. Have address bar with file's full address
Once user clicks the button and picks the file >> file's path is displayed in the address bar as well as stored in a separate variable for future usage later in code
I've done some testing, but when checking for retrieved value - I get None.
def file_picker():
"""Pick enova .xlsx file"""
path = filedialog.askopenfilename(filetypes=(('Excel Documents', '*.xlsx'), ('All Files', '*.*')))
return path
file_button = tkinter.Button(root, text='Users File', width=20, height=3,
bg='white', command=custom_functions.file_picker).place(x=30, y=50)
Apart form that I found another code snippet, but this simply captures line onto the GUI interface, not saving file path in any variable though:
def browsefunc():
filename = filedialog.askopenfilename()
pathlabel.config(text=filename)
print(pathlabel)
browsebutton = tkinter.Button(root, text="Browse", command=browsefunc).pack()
pathlabel = tkinter.Label(root).pack()
Expected result: https://imgur.com/a/NbiOPzG - unfortunatelly I cannot post images yet so uploaded one onto imgur
To capture the full path of a file using Tkinter you can do the following. The output of your full file path will be displayed in the "Entry" field / your address bar as per your requirement in your original post.
Update
import tkinter
from tkinter import ttk, StringVar
from tkinter.filedialog import askopenfilename
class GUI:
def __init__(self, window):
# 'StringVar()' is used to get the instance of input field
self.input_text = StringVar()
self.input_text1 = StringVar()
self.path = ''
self.path1 = ''
window.title("Request Notifier")
window.resizable(0, 0) # this prevents from resizing the window
window.geometry("700x300")
ttk.Button(window, text = "Users File", command = lambda: self.set_path_users_field()).grid(row = 0, ipadx=5, ipady=15) # this is placed in 0 0
ttk.Entry(window, textvariable = self.input_text, width = 70).grid( row = 0, column = 1, ipadx=1, ipady=1) # this is placed in 0 1
ttk.Button(window, text = "Enova File", command = lambda: self.set_path_Enova_field()).grid(row = 1, ipadx=5, ipady=15) # this is placed in 0 0
ttk.Entry(window, textvariable = self.input_text1, width = 70).grid( row = 1, column = 1, ipadx=1, ipady=1) # this is placed in 0 1
ttk.Button(window, text = "Send Notifications").grid(row = 2, ipadx=5, ipady=15) # this is placed in 0 0
def set_path_users_field(self):
self.path = askopenfilename()
self.input_text.set(self.path)
def set_path_Enova_field(self):
self.path1 = askopenfilename()
self.input_text1.set(self.path1)
def get_user_path(self):
""" Function provides the Users full file path."""
return self.path
def get_enova_path1(self):
"""Function provides the Enova full file path."""
return self.path1
if __name__ == '__main__':
window = tkinter.Tk()
gui = GUI(window)
window.mainloop()
# Extracting the full file path for re-use. Two ways to accomplish this task is below.
print(gui.path, '\n', gui.path1)
print(gui.get_user_path(), '\n', gui.get_enova_path1())
Note: I added a comment to point you to where the full file path is stored, in my example it's 'path' & 'path1'.

Using Tkinter to create a GUI that creates a folder and unwrap a ZIP file

Hello fellow programmers,
Im trying to make a GUI with Tkinter. This is the first time that Ive used Tkinter, and Ive run into some issues. The script and GUI should do the following:
Ask user (via entry and 'ok' button) to enter the name of the working folder. Result -> Create the newly made folder on the desktop of the system.
Select an OWL file (which is a zip-file) via TkFileDialog.
Result -> unwrap the selected zip-file in the folder that has been created in step 1.
The script Ive written so-far using online tutorials:
import Tkinter
import Tkconstants
import tkFileDialog
import zipfile
from Tkinter import *
import os
class TkFileDialogExample(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root)
root.configure(background='lightgrey')
root.wm_title("Audit tool: Thickness of pavement")
root.geometry('{}x{}'.format(500, 500))
Label_1 = Message(root, text="Step 1. Please fill in the name of the output folder and click on 'create'. The output folder will be created in the desktop folder:", width=380,)
Label_1.grid(row=1, columnspan=5)
Entry1 = Entry(root)
Entry1.grid(row=2, sticky=E)
folder_location = '~/Desktop/' + Entry1.get()
def createname():
return os.mkdir(os.path.expanduser(folder_location))
button_1 = Button(root, text="Create", command=createname)
button_1.grid(row=2, column =1, sticky=W)
Label_3 = Message(root, text="Step 2. Please select the OWL file:", width=300,)
Label_3.grid(row=5, sticky=W)
button_2 = Button(self, text='Click here to select the OWL file', command=self.askopenfilename)
button_2.grid(row=4,column=1, sticky=W)
self.file_opt = options = {}
options['defaultextension'] = '.owl'
options['filetypes'] = [('all files', '.*'), ('owl files', '.owl')]
options['initialdir'] = 'C:\\'
options['initialfile'] = 'Title_of_OWL-file.ccr'
options['parent'] = root
options['title'] = 'This is a title'
self.dir_opt = options = {}
options['initialdir'] = 'C:\\'
options['mustexist'] = False
options['parent'] = root
def askopenfile(self):
return tkFileDialog.askopenfile(mode='r', **self.file_opt)
def askopenfilename(self):
filename = tkFileDialog.askopenfilename(**self.file_opt)
zip_ref = zipfile.ZipFile(filename, 'r')
if filename:
return zip_ref.extractall(folder_location)
if __name__=='__main__':
root = Tkinter.Tk()
TkFileDialogExample(root).grid()
root.mainloop()
The problem probably lies within the third use of 'folder_location'. Since I am relatively new to the Python language, I cannot seem to find a solution to this problem.
Thank you for your help and time!
Yours truly,
Ruben van der Heijden
The issue is that you have defined the variable folder_location only in the local scope of your TkFileDialogExample.__init__ method. As a result, it is not accessible to any of the other methods of your class. If you do want it to be accessible, then you'll want to set it as an attribute of your class using the self keyword.
def __init__(self, root):
# Stuff
self.folder_location = os.path.join('~', 'Desktop', Entry1.get())
Then you can access it from your TkFileDialogExample.askopenfilename method:
def askopenfilename(self):
filename = tkFileDialog.askopenfilename(**self.file_opt)
zip_ref = zipfile.ZipFile(filename, 'r')
if filename:
return zip_ref.extractall(self.folder_location)
Side Note: In general, it is best to use os.path.join to construct file paths from strings.

How can I get number of files uploaded in tkinter Python

I currently have this tkintr application that would allow you to upload a file or multiple files. I would like to be able to print to the console how many files were selected.
Currently I can only print what the files selected are with print self.uploadedfilenames
I tried to do len(self.uploadedfilenames) but I got a number of 52 for one file which I do not understand what it is
#!/usr/bin/env python
from Tkinter import *
import tkFileDialog
import tkMessageBox
class Application(object):
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.file_opt = options = {}
options['defaultextension'] = '.txt'
options['filetypes'] = [('all files', '.*'), ('text files', '.txt')]
options['initialdir'] = 'C:\\'
options['initialfile'] = 'myfile.txt'
options['parent'] = master
options['title'] = 'This is a title'
#UPLOAD SECTION
Label(frame, text='Upload: ').grid(row=1, column=1)
self.upload = Button(frame, text = "Browse", command = self.askopenfile, width = 10).grid(row=1, column=2)
def askopenfile(self):
self.uploadedfilenames = tkFileDialog.askopenfilenames(multiple=True)
if self.uploadedfilenames == '':
tkMessageBox.showinfo(message="No file was selected")
return
else:
print len(self.uploadedfilenames)
root = Tk()
root.title('application')
_ = Application(root)
root.mainloop()
Is there a way to find out how many files there is ?
The length you see is because it does not return a tuple as expected, but returns a string instead. This is due to a Windows error.
When I test your code on my PC everything is fine.
Please have a look here:
Parsing the results of askopenfilenames()?
Also, when you have issues like this, simply print
self.uploadedfilenames
Then you can see what is returned. That would have helped you out I think.

Categories

Resources