Code for How to Build a Spreadsheet App with Tkinter in Python Tutorial


View on Github

spreadsheet_app.py

# Imports
from tkinter import *
import string
import sys
import ctypes

# Increase Dots Per inch so it looks sharper
ctypes.windll.shcore.SetProcessDpiAwareness(True)

# Define X and Y Axis Lists
xAxis = string.ascii_lowercase[0:7]
yAxis = range(0, 12)

# Cells will hold the strings vars and the lables 
cells = {}

# Open the content of the given file
# if one was provided and save it as a two 
# dimensional list.
CsvContent = ''
if len(sys.argv) > 1:
    with open(sys.argv[1], 'r') as f:
        CsvContent = f.read().split('\n')
        for i, layer in enumerate(CsvContent):
            CsvContent[i] = layer.split(',')

# Make a new Top Level Element (Window)
root = Tk()

# Set the the title to also mention the given file name
# if there is one
title = "Spreadsheet App" if CsvContent == '' else f"Spreadsheet App - {sys.argv[1]}"
root.title(title)

# Evaluating a cell
def evaluateCell(cellId, *i):

    # Get the content from the string var
    # and make it lowercase
    content = cells[cellId][0].get()
    content = content.lower()

    # get the reference to the label
    label = cells[cellId][1]

    # if the cell starts with a = it is evaluated
    if content.startswith('='):

        # Loop through all cells ...
        for cell in cells:

            # ... and see if their name appears in this cell
            if cell in content.lower():

                # if it is then replace the name occurences 
                # with the evaluated content from there.
                content = content.replace(cell, str(evaluateCell(cell)))

        # Get the content without the = and try to evaluate it
        content = content[1:]
        try:
            content = eval(content)
        except:
            content = 'NAN'
        label['text'] = content
        return content

    # If not, the label just shows the content
    else:
        label['text'] = content
        return content

# Call the eval function for every cell every ten milliseconds.
def updateAllCells():

    # Call it again
    root.after(10, updateAllCells)

    # Loop through all cells
    for cell in cells:
        evaluateCell(cell)


# Display the Y axis lables
for y in yAxis:
    label = Label(root, text = y, width=5, background='white')
    label.grid(row=y + 1, column=0)

# Display the X axis lables with enumerate
for i, x in enumerate(xAxis):
    label = Label(root, text = x, width=35, background='white')
    label.grid(row=0, column=i + 1, sticky='n')


# Display the Cells, by using a nested loop
for y in yAxis:
    for xcoor, x in enumerate(xAxis):

        # Generate a Unique ID for the cell with the coordinates
        id = f'{x}{y}'

        # Make String Var associated with the Cell
        var = StringVar(root, '', id)

        # Make Entry and label, offset each axis by one because of the lables
        e = Entry(root, textvariable=var, width=30)
        e.grid(row=y + 1, column=xcoor + 1)

        label = Label(root, text = '', width=5)
        label.grid(row=y + 1, column=xcoor + 1, sticky='e')

        # Save the string var and a reference to the lables in the dictionary
        cells[id] = [var, label]

        # Insert CSV content if it possible
        if CsvContent != '':
            try:
                var.set(CsvContent[y][xcoor])
            except:
                pass

# Start the updating Process
updateAllCells()

# Run the Mainloop
root.mainloop()