I want to use tkinter to browse an excel sheet and make a drop down menu of the rows of that excel sheet.
I am pretty new to python and do not know how to work it through. The code until now looks like this:
import xlrd
import os
from subprocess import call
import Tkinter,tkFileDialog
root = Tkinter.Tk()
root.withdraw()
filename = tkFileDialog.askopenfiles(title='Choose an excel file')
print(filename)
print type(filename)
#file = str(filename)
file = [filetypes for filetypes in filename if ".xlsx" in filetypes]
workbook = xlrd.open_workbook(filename)
for file in filename:
sheet = workbook.sheet_by_index(0)
print(sheet)
for value in sheet.row_values(0):
print(value)
This throws an error:
Traceback (most recent call last):
File "C:/Geocoding/test.py", line 14, in
workbook = xlrd.open_workbook(filename)
File "C:\Python27\ArcGIS10.3\lib\site-packages\xlrd__init__.py", line 394, in open_workbook
f = open(filename, "rb")
TypeError: coercing to Unicode: need string or buffer, list found
I am not even able to read the excel sheet that the user browses. I have no idea why this error. I would really appreciate if anybody can help me with it. Am I on the right path ?
Thanks
The new code that works:
import xlrd
from Tkinter import *
import Tkinter,tkFileDialog
root = Tkinter.Tk()
root.withdraw()
filename = tkFileDialog.askopenfilename(title='Choose an excel file')
print(filename)
print type(filename)
#file = str(filename)
file = [filetypes for filetypes in filename if ".xlsx" in filetypes]
workbook = xlrd.open_workbook(filename)
#for file in filename:
sheet = workbook.sheet_by_index(0)
print(sheet)
for value in sheet.row_values(0):
print(value)
print(type(value))
master = Tk()
variable=StringVar(master)
#variable=sheet.row_values(0)[0]
variable.set(sheet.row_values(0)[0])
#for var in value:
# variable = StringVar(master)
# variable.set(value) # default value
#w = OptionMenu(master, variable, value)
w = apply(OptionMenu, (master, variable) + tuple(sheet.row_values(0)))
w.pack()
mainloop()
You may have more errors along the way but in your code here:
filename = tkFileDialog.askopenfiles(title='Choose an excel file')
the result from that dialog is a list of file objects. So you are passing that list of fileobjects to open_workbook here:
workbook = xlrd.open_workbook(filename)
Instead what you need to do is pass the name of the file you care about as a string to open_workbook:
workbook = xlrd.open_workbook(filename[0].name) # the name of the first file in the list
here is a working Python3 (sorry I abandoned Python2) example for tkinter to properly select filenames:
from tkinter import filedialog
from tkinter import *
root = Tk()
root.withdraw()
filename = filedialog.askopenfiles(title='Choose an excel file')
print(filename) # filename is a list of file objects
print(filename[0].name) # this is the name of the first selected in the dialog that you can pass to xlrd
Related
I have a program I am writing for an internal employee that takes a CSV file and searches a file server for the files listed in the CSV then copys each file to a folder in the desktop. The issue I am running into with my current code is that the CSV must hold the exact names but instead I need to regex search this and copy the files with file names like the ones in the CSV.
file name in excel looks like: D6957-QR-1452
file name on server looks like: WM_QRLabels_D6957-QR-1452_11.5x11.5_M.pdf
from tkinter import filedialog, messagebox
import openpyxl
import tkinter as tk
from pathlib import Path
import shutil
import os
desktop = Path.home() / "Desktop/Comps"
tk.messagebox.showinfo("Select a directory","Select a directory" )
folder = filedialog.askdirectory()
root = tk.Tk()
root.title("Title")
lbl = tk.Label(
root, text="Open the excel file that includes files to search for")
lbl.pack()
frame = tk.Frame(root)
frame.pack()
scrollbar = tk.Scrollbar(frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
listbox = tk.Listbox(frame, yscrollcommand=scrollbar.set)
def load_file():
wb_path = filedialog.askopenfilename(filetypes=[('Excel files', '.xlsx')])
wb = openpyxl.load_workbook(wb_path)
global sheet
sheet = wb.active
listbox.pack()
file_names = [cell.value for row in sheet.rows for cell in row]
for file_name in file_names:
listbox.insert('end', file_name)
return file_names # <--- return your list
def search_folder(folder, file_name):
# Create an empty list to store the found file paths
found_files = []
for root, dirs, files in os.walk(folder):
for file in files:
if file in file_name:
found_files.append(os.path.join(root, file))
shutil.copy2(file, desktop)
return found_files
excelBtn = tk.Button(root, text="Open Excel File",
command=None)
excelBtn.pack()
zipBtn = tk.Button(root, text="Copy to Desktop",
command=search_folder(folder, load_file()))
zipBtn.pack()
root.mainloop()
Program is able to find and copy exact file names but unable to file *like* files.
Change your search_folder method to something like this:
def search_folder(folder, file_name):
# Create an empty list to store the found file paths
found_files = []
for root, dirs, files in os.walk(folder):
for file in files:
for file_pattern in file_name:
if file.find(file_pattern) > -1:
replaced_backslashes = re.sub(r"\\+", "/", os.path.join(root, file), 0, re.MULTILINE)
found_files.append(replaced_backslashes)
shutil.copy2(file, desktop)
return found_files
Basically, for every file we iterate over the file_name array to test all the patterns.
For the test we use find method, that in case it matches, returns the position of the found substring.
That is, if searching for 'foo', all these files would be returned:
zazfoo
foobar
zazfoobar
foo
I want to make new file but first choose directory.
This code works but a directory is already set
new_file = input("File name\n")
new_file = new_file.lower().replace(".", "").replace(" ", "_")
print(new_file)
open_file = open('D:\Python projects\%s.py' % new_file, 'w')
Also tried like this but not happen
import tkinter as tk
from tkinter import filedialog
root = tk.Tk()
root.withdraw()
file_path = filedialog.askopenfilename()
new_file = input("Name file\n")
open_file = open(f"{file_path}\%s.py" % new_file, 'w')
askdirectory should work. file_path = filedialog.askdirectory
I'm trying to save a csv file as a .json file. But when the following code runs, always opens a small tkinter window, along with the load window. How this can be avoided?
import csv
import json
import pandas as pd
from tkinter import filedialog
# which is used to save file in any extension
from tkinter.filedialog import asksaveasfile
class Manipulate_data:
def convert_csv_to_json(self):
csv_file_path = filedialog.askopenfilename(initialdir = '/Desktop',
title = 'Select a CSV file',
filetypes = (('csv file','*.csv'),
('csv file','*.csv')))
print(csv_file_path)
#create a dictionary
data_dict = {}
#Step 2
#open a csv file handler
with open(csv_file_path, encoding = 'utf-8') as csv_file_handler:
csv_reader = csv.DictReader(csv_file_handler)
#convert each row into a dictionary
#and add the converted data to the data_variable
for rows in csv_reader:
#assuming a column named 'No'
#to be the primary key
key = rows['id']
data_dict[key] = rows
#open a json file handler and use json.dumps
#method to dump the data
#Step 3
files = [('JSON Files', '*.json')]
json_file_path = asksaveasfile(filetypes = files, defaultextension = files)
with open(json_file_path.name, 'w', encoding = 'utf-8') as json_file_handler:
#Step 4
json_file_handler.write(json.dumps(data_dict, indent = 4))
#driver code
#be careful while providing the path of the csv file
#provide the file path relative to your machine
Manipulate_data().convert_csv_to_json()
I'm trying to build a small program that combines csv files. I've made a GUI where a user selects directories of the location of the csv files and where they want the final combined csv file to be outputted. I'm using this script to merge csv files at the moment.
from pathlib import Path
import pandas as pd
def add_dataset(old, new, **kwargs):
if old is None:
return new
else:
return pd.merge(old, new, **kwargs)
combined_csv = None
for csv in Path(r'C:\Users\Personal\Python\Combine').glob('*.csv'):
dataset = pd.read_csv(csv, index_col=0, parse_dates=[0])
combined_csv = add_dataset(combined_csv, dataset, on='DateTime', how='outer')
combined_csv.to_csv(r'C:\Users\Personal\Python\Combine\combined.csv')
The script that I've built for the GUI is this:
from tkinter import *
from tkinter import filedialog
from pathlib import Path
import pandas as pd
import os
root = Tk()
root.geometry("400x200")
# Setting up the 'Browse Directory' dialogs
def selectDirectory():
global dirname
global folder_path
dirname = filedialog.askdirectory(parent=root,initialdir="/",title='Please select a directory')
folder_path.set(dirname)
print(dirname)
def selectOutputDirectory():
global dirname_combine
global folder_pathcombine
dirname_combine = filedialog.askdirectory(parent=root,initialdir="/",title='Please select a directory')
folder_pathcombine.set(dirname_combine)
print(dirname_combine)
# Printing the locations out as a label
folder_path = StringVar()
lbl1 = Label(master=root, textvariable = folder_path)
lbl1.grid(row=0,column=2)
folder_pathcombine = StringVar()
lbl2 = Label(master=root, textvariable = folder_pathcombine)
lbl2.grid(row=1,column=2)
def add_dataset(old, new, **kwargs):
if old is None:
return new
else:
return pd.merge(old, new, **kwargs)
def runscript():
combined_csv = None
path = r'%s' % folder_path
combine = r'%s' % folder_pathcombine
for csv in Path(r'%s' % path).glob('*.csv'):
dataset = pd.read_csv(csv, index_col = 0, parse_dates=[0], delimiter = ',')
combined_csv = add_dataset(combined_csv, dataset, on='DateTime', how='inner')
combined_csv.to_csv(r'%s\combined.csv' % combine)
# Assigning commands to buttons to select folders
selectFolder = Button(root, text = "Select directory", command = selectDirectory)
selectFolder.grid(row=0,column=0)
selectcombine = Button(root, text = "Select output directory", command = selectOutputDirectory)
selectcombine.grid(row=1, column=0)
run = Button(root, text = "Run script", command = runscript)
run.grid(row=3, column=0)
root.mainloop()
The problem I'm having is correctly implementing the script for the merging in the GUI script. The merge script works fine by itself but when I implemented it into the GUI script I get the error "AttributeError: 'NoneType' object has no attribute 'to_csv'". I think my function is setup in correctly in the GUI so I was reading the following documentation. https://anh.cs.luc.edu/python/hands-on/3.1/handsonHtml/functions.html
I read that the "None" error occurs when there is no value being returned. So in this case I think it's not writing the variable "Combined" to a csv because nothing exists it in.
The complete error message is this:
runfile('C:/Users/Personal/Python//test.py', wdir='C:/Users/Personal/Python/Combine')
C:/Users/Personal/Python/Combine
C:/Users/Personal/Python/Combine
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\tkinter\__init__.py", line 1702, in __call__
return self.func(*args)
File "C:/Users/Personal/Python/Combine/gui.py", line 54, in runscript
combined_csv.to_csv(r'%s\combined.csv' % combine)
AttributeError: 'NoneType' object has no attribute 'to_csv'
Any help fixing the error would be greatly appreciated, as well as any advice on how to improve my code. I'm relatively new to Python and looking to improve. Thanks!
The problem is that you are using StringVar in the following statements inside runscript():
path = r'%s' % folder_path
combine = r'%s' % folder_pathcombine
Therefore no file will be found in the for loop below the above statements and combine_csv is not updated.
You should use .get() on the StringVar as below:
path = r'%s' % folder_path.get()
combine = r'%s' % folder_pathcombine.get()
I'm trying to convert a directory full of XLSX files to CSV. Everything is working except I'm encountering an issue with the columns containing time information. The XLSX file is being created by another program that I can not modify. But I want to maintain the same times that show up when I view the XLSX file in Excel as when it is converted to CSV and viewed in any text editor.
My code:
import csv
import xlrd
import os
import fnmatch
import Tkinter, tkFileDialog, tkMessageBox
def main():
root = Tkinter.Tk()
root.withdraw()
print 'Starting .xslx to .csv conversion'
directory = tkFileDialog.askdirectory()
for fileName in os.listdir(directory):
if fnmatch.fnmatch(fileName, '*.xlsx'):
filePath = os.path.join(directory, fileName)
saveFile = os.path.splitext(filePath)[0]+".csv"
savePath = os.path.join(directory, saveFile)
workbook = xlrd.open_workbook(filePath)
sheet = workbook.sheet_by_index(0)
csvOutput = open(savePath, 'wb')
csvWriter = csv.writer(csvOutput, quoting=csv.QUOTE_ALL)
for row in xrange(sheet.nrows):
csvWriter.writerow(sheet.row_values(row))
csvOutput.close()
print '.csv conversion complete'
main()
To add some detail, if I open one file in Excel I see this in a time column:
00:10.3
00:14.2
00:16.1
00:20.0
00:22.0
But after I convert to CSV I see this in the same location:
0.000118981
0.000164005
0.000186227
0.000231597
0.000254861
Thanks to seanmhanson with his answer https://stackoverflow.com/a/25149562/1858351 I was able to figure out that Excel is dumping the times as decimals of a day. While I should try to learn and use xlrd better, for a quick short term fix I was instead able to convert that into seconds and then from seconds back into the time format originally seen of HH:MM:SS. My (probably ugly) code below in case anyone might be able to use it:
import csv
import xlrd
import os
import fnmatch
from decimal import Decimal
import Tkinter, tkFileDialog
def is_number(s):
try:
float(s)
return True
except ValueError:
return False
def seconds_to_hms(seconds):
input = Decimal(seconds)
m, s = divmod(input, 60)
h, m = divmod(m, 60)
hm = "%02d:%02d:%02.2f" % (h, m, s)
return hm
def main():
root = Tkinter.Tk()
root.withdraw()
print 'Starting .xslx to .csv conversion'
directory = tkFileDialog.askdirectory()
for fileName in os.listdir(directory):
if fnmatch.fnmatch(fileName, '*.xlsx'):
filePath = os.path.join(directory, fileName)
saveFile = os.path.splitext(filePath)[0]+".csv"
savePath = os.path.join(directory, saveFile)
workbook = xlrd.open_workbook(filePath)
sheet = workbook.sheet_by_index(0)
csvOutput = open(savePath, 'wb')
csvWriter = csv.writer(csvOutput, quoting=csv.QUOTE_ALL)
rowData = []
for rownum in range(sheet.nrows):
rows = sheet.row_values(rownum)
for cell in rows:
if is_number(cell):
seconds = float(cell)*float(86400)
hms = seconds_to_hms(seconds)
rowData.append((hms))
else:
rowData.append((cell))
csvWriter.writerow(rowData)
rowData = []
csvOutput.close()
print '.csv conversion complete'
main()
Excel stores time as a float in terms of days. You will need to use XLRD to determine if a cell is a date, and then convert it as needed. I'm not great with XLRD, but you might want something akin to this, changing the string formatting if you want to keep leading zeroes:
if cell.ctype == xlrd.XL_CELL_DATE:
try:
cell_tuple = xldate_as_tuple(cell, 0)
return "{hours}:{minutes}:{seconds}".format(
hours=cell_tuple[3], minutes=cell_tuple[4], seconds=cell_tuple[5])
except (any exceptions thrown by xldate_as_tuple):
//exception handling
The XLRD date to tuple method's documentation can be found here: https://secure.simplistix.co.uk/svn/xlrd/trunk/xlrd/doc/xlrd.html?p=4966#xldate.xldate_as_tuple-function
For a similar issue already answered, see also this question: Python: xlrd discerning dates from floats