Recently, we got the requirement to create a desktop application for tracking expenses on an individual level. So we have created this expense tracking application using Python tkinter with sqlite3 database.
SQLite3 is a free database software that comes built-in with Python. The following command shows how to import the SQLite3 module:
import sqlite3Expense Tracking Application Using Python Tkinter
In this section, we have explained the creation of the Interface of the application using Python tkinter. The entire code is divided into 12 parts, and for each part, we have mentioned the description in a bold comment.
1. Import Modules
The first step in the creation of the application is importing the necessary modules. Here we have imported all the modules available in tkinter (*). TTK is the new member of the tkinter family that needs to be imported separately. In the end, we have imported a message box module to display the prompt.
2. Create an Object, title, and window size
The next step is to create an object for the tkinter module. In the code, ws is the name assigned to the object. Using this object name, we have further assigned a title and size to the Python tkinter window.
Learn more about Python Tkinter Title
3. Infinite Loop
The main loop used in the last line of the code holds the screen for a user. The user can see and interact with the window because of the mainloop. In the backend, a window appears and disappears immediately in microseconds.
4. Global Variables
We have named them global variables because we will be using these variables in different parts of the program. We were aware of putting the same font for all the widgets in a program, which is why the value with font name and size is stored in a simple string. And later used this letter to assign a font to an application text.
amtvar = IntVar()
dopvar = StringVar()These two are the text variables, one with the integer data type assigned to store the purchase amount and the other one is a string assigned to record the date of purchase.
5. Frame Widgets
The frame widget in Python tkinter is used to hold other widgets within them, and it becomes convenient to move a group of widgets simply by moving the container widget (frame).
Two frames are created with the names f1 and f2, both are positioned using the pack geometry manager. The f1 frame holds all the widgets except the Treeview and the scrollbar widget in Python tkinter. These two widgets are placed on the f2 frame.
6. Label Widgets, 7. Entry Widget, 8 entry grid placement
The Label widget in Python tkinter is used to display text on the application window. Item name, item price, and purchase date are the labels used in this application.
The Entry widget in Python tkinter allows user to provide their input by typing in a rectangular box. We have used the grid geometry manager to position all the widgets inside the f1 frame widget.
9. Action Buttons, 10 Button grid placement
Action buttons are used in the application window that will perform or trigger a function every time they are clicked on the application window. So far, we have created 6 types of buttons on the application.
- Save Record
- Clear Entry
- Exit
- Total Balance
- Update
- Delete
11. Treeview to view the record
Treeview is used to display the output of the record in an organized tabular format. The Treeview creation has three parts. First, we have created a Treeview and then added a header or columns to it after using the style, and we have improved its look and feel.
12. Scrollbar widget
The vertical scrollbar is added to the Treeview widget so that if data exceeds the size of the screen, the user can still view it by scrolling up and down.
#1 import modules
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
#2 create object, title and window size
ws = Tk()
ws.title('Office Expense')
# ws.geometry('600x400')
#4 global variables
f = ('Times new roman', 14)
amtvar = IntVar()
dopvar = StringVar()
#5 frame widgets
f2 = Frame(ws)
f2.pack()
f1 = Frame(
ws,
padx=10,
pady=10,
)
f1.pack(expand=True, fill=BOTH)
#6 Label Widget
Label(f1, text='ITEM NAME', font=f).grid(row=0, column=0, sticky=W)
Label(f1, text='ITEM PRICE', font=f).grid(row=1, column=0, sticky=W)
Label(f1, text='PURCHASE DATE', font=f).grid(row=2, column=0, sticky=W)
#7 Entry Widgets
item_name = Entry(f1, font=f)
item_amt = Entry(f1, font=f, textvariable=amtvar)
transaction_date = Entry(f1, font=f, textvariable=dopvar)
#8 Entry grid placement
item_name.grid(row=0, column=1, sticky=EW, padx=(10, 0))
item_amt.grid(row=1, column=1, sticky=EW, padx=(10, 0))
transaction_date.grid(row=2, column=1, sticky=EW, padx=(10, 0))
#9 Action buttons
cur_date = Button(
f1,
text='Current Date',
font=f,
bg='#04C4D9',
command=None,
width=15
)
submit_btn = Button(
f1,
text='Save Record',
font=f,
command=None,
bg='#42602D',
fg='white'
)
clr_btn = Button(
f1,
text='Clear Entry',
font=f,
command=None,
bg='#D9B036',
fg='white'
)
quit_btn = Button(
f1,
text='Exit',
font=f,
command=None,
bg='#D33532',
fg='white'
)
total_bal = Button(
f1,
text='Total Balance',
font=f,
command=None
)
total_bal = Button(
f1,
text='Total Balance',
font=f,
bg='#486966',
fg='white',
command=None
)
total_spent = Button(
f1,
text='Total Spent',
font=f,
fg='white',
command=None
)
update_btn = Button(
f1,
text='Update',
bg='#C2BB00',
command=None,
fg='white',
font=f
)
del_btn = Button(
f1,
text='Delete',
bg='#BD2A2E',
fg='white',
command=None,
font=f
)
#10 Button grid placement
cur_date.grid(row=3, column=1, sticky=EW, padx=(10, 0))
submit_btn.grid(row=0, column=2, sticky=EW, padx=(10, 0))
clr_btn.grid(row=1, column=2, sticky=EW, padx=(10, 0))
quit_btn.grid(row=2, column=2, sticky=EW, padx=(10, 0))
total_bal.grid(row=0, column=3, sticky=EW, padx=(10, 0))
update_btn.grid(row=1, column=3, sticky=EW, padx=(10, 0))
del_btn.grid(row=2, column=3, sticky=EW, padx=(10, 0))
#11 treeview to view the record
tv = ttk.Treeview(f2, selectmode='browse', columns=(1, 2, 3, 4), show='headings', height=8, )
tv.pack(side="left")
tv.column(1, anchor=CENTER, stretch=NO, width=70)
tv.column(2, anchor=CENTER)
tv.column(3, anchor=CENTER)
tv.column(4, anchor=CENTER)
tv.heading(1, text="Serial no")
tv.heading(2, text="Item Name", )
tv.heading(3, text="Item Price")
tv.heading(4, text="Purchase Date")
style = ttk.Style()
style.theme_use("default")
style.map("Treeview")
#12 scrollbar widget
scrollbar = Scrollbar(f2, orient='vertical')
scrollbar.configure(command=tv.yview)
scrollbar.pack(side="right", fill="y")
tv.config(yscrollcommand=scrollbar.set)
#3 infinite loop
ws.mainloop()Also, check: Python Tkinter Canvas Tutorial
Backend Explanation of the Application
In this section, we will explain to you all the source code related to the database in the expense manager application created using Python, tkinter, and SQLite3 database.
#1 module
import sqlite3
#2 class, constructor and methods
class Database:
def __init__(self, db):
self.conn = sqlite3.connect(db)
self.cur = self.conn.cursor()
self.cur.execute(
"CREATE TABLE IF NOT EXISTS expense_record (item_name text, item_price float, purchase_date date)")
self.conn.commit()
def fetchRecord(self, query):
self.cur.execute(query)
rows = self.cur.fetchall()
return rows
def insertRecord(self, item_name, item_price, purchase_date):
self.cur.execute("INSERT INTO expense_record VALUES (?, ?, ?)",
(item_name, item_price, purchase_date))
self.conn.commit()
def removeRecord(self, rwid):
self.cur.execute("DELETE FROM expense_record WHERE rowid=?", (rwid,))
self.conn.commit()
def updateRecord(self, item_name, item_price, purchase_date, rid):
self.cur.execute("UPDATE expense_record SET item_name = ?, item_price = ?, purchase_date = ? WHERE rowid = ?",
(item_name, item_price, purchase_date, rid))
self.conn.commit()
def __del__(self):
self.conn.close()Read: Python Tkinter Progress bar widget
Function and Other Operations Explanation
In this section, we will discuss the part of the source code other than the GUI and the database. We will discuss the following:
1. Module
In the previous section, we created a module with the name mydb.py, and here we have imported that module and all of its functions.
2. Object for the database
We have created an object for the class Database, and we have named the object data. Also, this object accepts a parameter that will be the name of the database.
3. Global Variable
Variables created inside a function have scope within that function only. Global variables can be accessed through the program, and their values are affected by changes made at any point.
select rowid, * from expense_record;This rowid is the one displayed as a serial number in the GUI interface of the application. We have stored the serial number of the row selected by the user on the interface in the global variable selected_rowid.
4. Functions
Let’s discuss the functions used in this application. Please note that these functions are different than the database class functions. These are mainly focused on using those methods.
The saveRecord() uses the insertRecord() method of the Database class to store the data in the database. We have passed the required parameter being fetched from the entry widget. This function is triggered by the submit_btn Button in Python tkinter.
#1. module
from mydb import *
#2. object for database
data = Database(db='myexpense.db')
#3. global variables
count = 0
selected_rowid = 0
#4. functions
def saveRecord():
global data
data.insertRecord(item_name=item_name.get(), item_price=item_amt.get(), purchase_date=transaction_date.get())
def setDate():
date = dt.datetime.now()
dopvar.set(f'{date:%d %B %Y}')
def clearEntries():
item_name.delete(0, 'end')
item_amt.delete(0, 'end')
transaction_date.delete(0, 'end')
def fetch_records():
f = data.fetchRecord('select rowid, * from expense_record')
global count
for rec in f:
tv.insert(parent='', index='0', iid=count, values=(rec[0], rec[1], rec[2], rec[3]))
count += 1
tv.after(400, refreshData)
def select_record(event):
global selected_rowid
selected = tv.focus()
val = tv.item(selected, 'values')
try:
selected_rowid = val[0]
d = val[3]
namevar.set(val[1])
amtvar.set(val[2])
dopvar.set(str(d))
except Exception as ep:
pass
def update_record():
global selected_rowid
selected = tv.focus()
# Update record
try:
data.updateRecord(namevar.get(), amtvar.get(), dopvar.get(), selected_rowid)
tv.item(selected, text="", values=(namevar.get(), amtvar.get(), dopvar.get()))
except Exception as ep:
messagebox.showerror('Error', ep)
# Clear entry boxes
item_name.delete(0, END)
item_amt.delete(0, END)
transaction_date.delete(0, END)
tv.after(400, refreshData)
def totalBalance():
f = data.fetchRecord(query="Select sum(item_price) from expense_record")
for i in f:
for j in i:
messagebox.showinfo('Current Balance: ', f"Total Expense: ' {j} \nBalance Remaining: {5000 - j}")
def refreshData():
for item in tv.get_children():
tv.delete(item)
fetch_records()
def deleteRow():
global selected_rowid
data.removeRecord(selected_rowid)
refreshData()Read: Python Tkinter Stopwatch
Complete Source Code for main.py
The source code below is for the application created using Python tkinter. Save this file with the name main.py and execute it to run this complete application.
# import modules
from tkinter import *
from tkinter import ttk
import datetime as dt
from mydb import *
from tkinter import messagebox
# object for database
data = Database(db='test.db')
# global variables
count = 0
selected_rowid = 0
# functions
def saveRecord():
global data
data.insertRecord(item_name=item_name.get(), item_price=item_amt.get(), purchase_date=transaction_date.get())
def setDate():
date = dt.datetime.now()
dopvar.set(f'{date:%d %B %Y}')
def clearEntries():
item_name.delete(0, 'end')
item_amt.delete(0, 'end')
transaction_date.delete(0, 'end')
def fetch_records():
f = data.fetchRecord('select rowid, * from expense_record')
global count
for rec in f:
tv.insert(parent='', index='0', iid=count, values=(rec[0], rec[1], rec[2], rec[3]))
count += 1
tv.after(400, refreshData)
def select_record(event):
global selected_rowid
selected = tv.focus()
val = tv.item(selected, 'values')
try:
selected_rowid = val[0]
d = val[3]
namevar.set(val[1])
amtvar.set(val[2])
dopvar.set(str(d))
except Exception as ep:
pass
def update_record():
global selected_rowid
selected = tv.focus()
# Update record
try:
data.updateRecord(namevar.get(), amtvar.get(), dopvar.get(), selected_rowid)
tv.item(selected, text="", values=(namevar.get(), amtvar.get(), dopvar.get()))
except Exception as ep:
messagebox.showerror('Error', ep)
# Clear entry boxes
item_name.delete(0, END)
item_amt.delete(0, END)
transaction_date.delete(0, END)
tv.after(400, refreshData)
def totalBalance():
f = data.fetchRecord(query="Select sum(item_price) from expense_record")
for i in f:
for j in i:
messagebox.showinfo('Current Balance: ', f"Total Expense: ' {j} \nBalance Remaining: {5000 - j}")
def refreshData():
for item in tv.get_children():
tv.delete(item)
fetch_records()
def deleteRow():
global selected_rowid
data.removeRecord(selected_rowid)
refreshData()
# create tkinter object
ws = Tk()
ws.title('Daily Expenses')
# variables
f = ('Times new roman', 14)
namevar = StringVar()
amtvar = IntVar()
dopvar = StringVar()
# Frame widget
f2 = Frame(ws)
f2.pack()
f1 = Frame(
ws,
padx=10,
pady=10,
)
f1.pack(expand=True, fill=BOTH)
# Label widget
Label(f1, text='ITEM NAME', font=f).grid(row=0, column=0, sticky=W)
Label(f1, text='ITEM PRICE', font=f).grid(row=1, column=0, sticky=W)
Label(f1, text='PURCHASE DATE', font=f).grid(row=2, column=0, sticky=W)
# Entry widgets
item_name = Entry(f1, font=f, textvariable=namevar)
item_amt = Entry(f1, font=f, textvariable=amtvar)
transaction_date = Entry(f1, font=f, textvariable=dopvar)
# Entry grid placement
item_name.grid(row=0, column=1, sticky=EW, padx=(10, 0))
item_amt.grid(row=1, column=1, sticky=EW, padx=(10, 0))
transaction_date.grid(row=2, column=1, sticky=EW, padx=(10, 0))
# Action buttons
cur_date = Button(
f1,
text='Current Date',
font=f,
bg='#04C4D9',
command=setDate,
width=15
)
submit_btn = Button(
f1,
text='Save Record',
font=f,
command=saveRecord,
bg='#42602D',
fg='white'
)
clr_btn = Button(
f1,
text='Clear Entry',
font=f,
command=clearEntries,
bg='#D9B036',
fg='white'
)
quit_btn = Button(
f1,
text='Exit',
font=f,
command=lambda:ws.destroy(),
bg='#D33532',
fg='white'
)
total_bal = Button(
f1,
text='Total Balance',
font=f,
bg='#486966',
command=totalBalance
)
total_spent = Button(
f1,
text='Total Spent',
font=f,
command=lambda:data.fetchRecord('select sum(ite)')
)
update_btn = Button(
f1,
text='Update',
bg='#C2BB00',
command=update_record,
font=f
)
del_btn = Button(
f1,
text='Delete',
bg='#BD2A2E',
command=deleteRow,
font=f
)
# grid placement
cur_date.grid(row=3, column=1, sticky=EW, padx=(10, 0))
submit_btn.grid(row=0, column=2, sticky=EW, padx=(10, 0))
clr_btn.grid(row=1, column=2, sticky=EW, padx=(10, 0))
quit_btn.grid(row=2, column=2, sticky=EW, padx=(10, 0))
total_bal.grid(row=0, column=3, sticky=EW, padx=(10, 0))
update_btn.grid(row=1, column=3, sticky=EW, padx=(10, 0))
del_btn.grid(row=2, column=3, sticky=EW, padx=(10, 0))
# Treeview widget
tv = ttk.Treeview(f2, columns=(1, 2, 3, 4), show='headings', height=8)
tv.pack(side="left")
# add heading to treeview
tv.column(1, anchor=CENTER, stretch=NO, width=70)
tv.column(2, anchor=CENTER)
tv.column(3, anchor=CENTER)
tv.column(4, anchor=CENTER)
tv.heading(1, text="Serial no")
tv.heading(2, text="Item Name", )
tv.heading(3, text="Item Price")
tv.heading(4, text="Purchase Date")
# binding treeview
tv.bind("<ButtonRelease-1>", select_record)
# style for treeview
style = ttk.Style()
style.theme_use("default")
style.map("Treeview")
# Vertical scrollbar
scrollbar = Scrollbar(f2, orient='vertical')
scrollbar.configure(command=tv.yview)
scrollbar.pack(side="right", fill="y")
tv.config(yscrollcommand=scrollbar.set)
# calling function
fetch_records()
# infinite loop
ws.mainloop()The output below shows the interface of the Expense Management application.

Read: How to make a calculator in Python
Complete Source code Database mydb.py
The below source code is for creating a database in SQLite3 using Python tkinter. Save the file with the name mydb.py in the same directory where the main program is saved.
import sqlite3
class Database:
def __init__(self, db):
self.conn = sqlite3.connect(db)
self.cur = self.conn.cursor()
self.cur.execute(
"CREATE TABLE IF NOT EXISTS expense_record (item_name text, item_price float, purchase_date date)")
self.conn.commit()
def fetchRecord(self, query):
self.cur.execute(query)
rows = self.cur.fetchall()
return rows
def insertRecord(self, item_name, item_price, purchase_date):
self.cur.execute("INSERT INTO expense_record VALUES (?, ?, ?)",
(item_name, item_price, purchase_date))
self.conn.commit()
def removeRecord(self, rwid):
self.cur.execute("DELETE FROM expense_record WHERE rowid=?", (rwid,))
self.conn.commit()
def updateRecord(self, item_name, item_price, purchase_date, rid):
self.cur.execute("UPDATE expense_record SET item_name = ?, item_price = ?, purchase_date = ? WHERE rowid = ?",
(item_name, item_price, purchase_date, rid))
self.conn.commit()
def __del__(self):
self.conn.close()The image below shows a representation of data on the SQLite3 database. We are viewing this data using DB Browser (SQLite) software.

In this Python tutorial, I discussed how to build an Expense Tracking Application Using Python Tkinter.
You may like to read:
- Create an OptionMenu in Python Tkinter
- Create Responsive Layouts with Python Tkinter’s Grid Geometry Manager
- Create Layouts with Python Tkinter Frame

Bijay Kumar is an experienced Python and AI professional who enjoys helping developers learn modern technologies through practical tutorials and examples. His expertise includes Python development, Machine Learning, Artificial Intelligence, automation, and data analysis using libraries like Pandas, NumPy, TensorFlow, Matplotlib, SciPy, and Scikit-Learn. At PythonGuides.com, he shares in-depth guides designed for both beginners and experienced developers. More about us.