Python Tkinter Expense Logging Software
Using the Tkinter, Tkcalendar, and SQLite libraries, we’ll create a GUI for an Expense Tracker application in this article. It’s an intermediate-level project that will teach us a lot about databases and graphical user interfaces, as well as how to put them to use in practice.
Well, then, let’s
started.
What is an Expense Tracker?
Many individuals in India are forced to get by on a limited salary, and they often run out of money before the end of the month. While low wages are a contributing factor, bad budgeting is ultimately to blame. A desktop program or software, Expense Tracker is also known as Expense Manager and Money Manager and allows users to keep track of their income and expenditures.
Overspending is common since few people think about the consequences. With the use of a daily cost tracker, we can monitor our spending habits. At the end of the month, we will be able to see how the money is moving. This is a great option for bringing some order to the chaos that is your finances.
finances.
About the Project
The goal of this work is to create a graphical user interface for a budgeting app. We’ll need an intermediate grasp of the Tkinter library, the SQL language, and its commands, as well as an introductory understanding of the Tkinter library’s tkcalendar, among other modules, to construct this.
library.
Prerequisites of the Project
The development of the Python GUI Helper Application necessitates the use of a number of specialized libraries and modules. A quick summary of these components is as follows:
follows:
-
Tkinter:
The
tkinter
module will help us provide the Graphical User Interface (GUI) to the application. -
Tkcalendar:
The
tkcalendar
module will help us to work with the drop-down calendar. -
SQLite:
The
sqlite
module will allow us to connect the Python script to the SQL database. -
Datetime:
The datetime module will allow to work with date and time.
There is no need to explicitly install Tkinter, Datetime, or SQLite since they are already included as modules in Python. Thus, we need simply add the Tkcalendar module to our system.
On a terminal or command prompt, use the following command to install the tkcalendar module through the PIP installer. Syntax:
-
# installing the PyGame library
-
$ pip install tkcalendar
When the package has been installed, we can test its functionality by writing a new Python script and include the tkcalendar import statement.
The relevant code piece is seen below. In the verify.py file.
-
import
tkcalendar
Let’s save the file, then open a terminal or command prompt and type in the following command. Syntax:
-
$ python verify.py
If the software does not report an importing problem, then the library was successfully installed. If an error occurs, you may try reinstalling the library or looking at its documentation.
Let’s go to work on the
project.
Building the Expense Tracker using Tkinter in Python
Create a new folder and label it ” Expense Tracker ” to begin constructing the Expense Tracker in Python. The project’s code will be written in a single Python file named “main.py” within this directory.
To facilitate comprehension, we have broken down the whole of the Python code for the Expense Tracker project into manageable chunks. The first step is importing the required modules, and it is shown below. The second step is to build the database and specify the operations that will be used to change the data. Third, we’ll establish a connection to the data source and build the application’s primary interface. Putting in place event triggers and adding the appropriate widgets to the window constitutes Step 4.
Let’s break down the aforementioned procedures into their
way.
Importing the necessary modules
To begin, we’ll import the necessary modules, which include the datetime module, the sqlite3 module, the tkcalendar library’s DateEntry class, and the tkinter module’s widgets and modules.
Here’s a code sample that illustrates the point. Get the main.py file and open it.
Explanation:
-
# importing the required modules
-
from
tkinter
import
*
# importing all the modules and classes from tkinter
-
from
tkinter
import
ttk as ttk
# importing the ttk module from tkinter
-
from
tkinter
import
messagebox as mb
# importing the messagebox module from tkinter
-
import
datetime
# importing the datetime module
-
import
sqlite3
# importing the sqlite3 module
-
from
tkcalendar
import
DateEntry
# importing the DateEntry class from the tkcalendar module
The preceding code snippet shows how to develop a graphical user interface by importing the necessary classes and modules from the tkinter module. To add tkinter widgets and show any relevant messages, we imported the ttk and messagebox modules. The current date and time have been retrieved when we imported the datetime module. To keep track of all of your entries, we’ve loaded the sqlite3 module. Finally, the tkcalendar module’s DateEntry class has been imported so that the selected date may be inserted.
calendar.
Creating the Database and Defining the necessary Functions
Now that we have all of the required modules imported into the project, we can begin building the database and defining the functions that will be used to carry out the application’s various tasks. These operations include retrieving data from the database and displaying it in a table, viewing a record from the data table, resetting the entry fields, deleting a selected record from the database, deleting all records from the database, creating a new record in the database, updating details of an existing record in the database, and displaying the details of a record in text format.
Let’s go into the specifics of how these features are implemented. Include all Database Costs in the Table
To get started, let’s define the function that will eventually provide an itemized bill. With this method, we may get data from the database and repeatedly put it into a Tkinter table.
Let’s have a look at the code below that exemplifies this. Get the main.py file and open it.
-
# ——————— defining functions ———————
-
-
# function to list all the expenses
-
def
listAllExpenses():
-
”
”’This function will retrieve the data from the database and insert it to the tkinter data table”’
-
-
# using some global variables
-
global
dbconnector, data_table
-
# clearing the table
-
data_table.delete(*data_table.get_children())
-
# executing the SQL SELECT command to retrieve the data from the database table
-
all_data = dbconnector.execute(
‘SELECT * FROM ExpenseTracker’
)
-
-
# listing the data from the table
-
data = all_data.fetchall()
-
-
# inserting the values iteratively in the tkinter data table
-
for
val
in
data:
-
data_table.insert(
”
, END, values = val)
Clarification:
In the above code sample, the method is named listAllExpenses(). Several global variables, including dbconnector and data table, have been utilized inside this method. Afterwards, we cleared up the Tkinter database using the delete() function. We then retrieved the information from the table using the SQL SELECT command and saved it in a variable. Afterwards, we enumerated the table’s contents using the fetchall() function. We then used a for -loop to go through the values, calling insert() on each one as we went. Examining the Details of a Cost
Now that we know what kind of expenditure we want to see, we can construct the corresponding function. This method will retrieve the values from the specified record’s columns and return them as a data frame.
Here’s a code sample that illustrates the point. Get the main.py file and open it.
Explanation:
-
# function to view an expense information
-
def
viewExpenseInfo():
-
”
”’This function will display the expense information in the data frame”’
-
-
# using some global variables
-
global
data_table
-
global
dateField, payee, description, amount, modeOfPayment
-
-
# return a message box displaying error if no row is selected from the table
-
if
not
data_table.selection():
-
mb.showerror(
‘No expense selected’
,
‘Please select an expense from the table to view its details’
)
-
-
# collecting the data from the selected row in dictionary format
-
currentSelectedExpense = data_table.item(data_table.focus())
-
-
# defining a variable to store the values from the collected data in list
-
val = currentSelectedExpense[
‘values’
]
-
-
# retrieving the date of expenditure from the list
-
expenditureDate = datetime.date(int(val[
1
][:
4
]), int(val[
1
][
5
:
7
]), int(val[
1
][
8
:]))
-
-
# setting the listed data in their respective entry fields
-
dateField.set_date(expenditureDate) ; payee.set(val[
2
]) ; description.set(val[
3
]) ; amount.set(val[
4
]) ; modeOfPayment.set(val[
5
])
The method is called viewExpenseInfo() in the above code sample. With this function, we’ve made use of global variables and utilized the showerror() method of the tkinter’s messagebox module to return a message box showing an error if no row is chosen from the table. Next, using the item() technique, we compiled the row’s information into a dictionary. We then created a new variable and added its data to an existing list. Finally, the list data has been obtained and entered into the corresponding fields of the data input frame. Clearing the data frame’s entries
Next, we’ll develop a method for erasing the contents of the input fields in the data frame.
Let’s have a look at the following code snippet that exemplifies this. Get the main.py file and open it.
-
# function to clear the entries from the entry fields
-
def
clearFields():
-
”
”’This function will clear all the entries from the entry fields”’
-
-
# using some global variables
-
global
description, payee, amount, modeOfPayment, dateField, data_table
-
-
# defining a variable to store today’s date
-
todayDate = datetime.datetime.now().date()
-
-
# setting the values in entry fields back to initial
-
description.set(
”
) ; payee.set(
”
) ; amount.set(
0.0
) ; modeOfPayment.set(
‘Cash’
), dateField.set_date(todayDate)
-
# removing the specified item from the selection
-
data_table.selection_remove(*data_table.selection())
Clarification:
The method is called clearFields() in the above code sample. We have made use of global variables inside this function. The current date has been assigned to a variable that we have specified. At long last, we’ve reverted the input fields to their default values and unselected the requested item. Selecting a record from the table, and then deleting it.
Now that we know how to remove the record, we can create the corresponding function.
Let’s have a look at the following code snippet that exemplifies this. Get the main.py file and open it.
Explanation:
-
# function to delete the selected record
-
def
removeExpense():
-
”
”’This function will remove the selected record from the table”’
-
-
# returning the message box displaying error if no row is selected
-
if
not
data_table.selection():
-
mb.showerror(
‘No record selected!’
,
‘Please select a record to delete!’
)
-
return
-
-
# collecting the data from the selected row in dictionary format
-
currentSelectedExpense = data_table.item(data_table.focus())
-
-
# defining a variable to store the values from the collected data in list
-
valuesSelected = currentSelectedExpense[
‘values’
]
-
-
# displaying a message box asking for confirmation
-
confirmation = mb.askyesno(
‘Are you sure?’
, f
‘Are you sure that you want to delete the record of {valuesSelected[2]}’
)
-
-
# if the user say YES, executing the SQL DELETE FROM command
-
if
confirmation:
-
dbconnector.execute(
‘DELETE FROM ExpenseTracker WHERE ID=%d’
% valuesSelected[
0
])
-
dbconnector.commit()
-
-
# calling the listAllExpenses() function
-
listAllExpenses()
-
-
# returning the message box displaying the information
-
mb.showinfo(
‘Record deleted successfully!’
,
‘The record you wanted to delete has been deleted successfully’
)
In the above code sample, the method is named removeExpense(). Here, we use tkinter’s messagebox module’s showerror() method to check whether any rows are chosen and deliver an error message if they are. After that, we built a new variable to hold the values gleaned from the dictionary representation of the chosen row’s data. The confirmation dialog box was then shown with the askyesnow() function of tkinter’s messagebox module. The SQL DELETE query was then run to remove the specified record. Afterwards, we got the dialog box back that said we were successful after calling listAllExpenses(). Clearing the table of all data
In this section, you will learn how to construct a function that will empty a table and its associated database.
Let’s have a look at the following code snippet that exemplifies this. Get the main.py file and open it.
Explanation:
-
# function to delete all the entries
-
def
removeAllExpenses():
-
”
”’This function will remove all the entries from the table”’
-
-
# displaying a message box asking for confirmation
-
confirmation = mb.askyesno(
‘Are you sure?’
,
‘Are you sure that you want to delete all the expense items from the database?’
, icon=
‘warning’
)
-
-
# if the user say YES, deleting the entries from the table and executing the SQL DELETE FROM command to delete all the entries
-
if
confirmation:
-
data_table.delete(*data_table.get_children())
-
-
dbconnector.execute(
‘DELETE FROM ExpenseTracker’
)
-
dbconnector.commit()
-
-
# calling the clearFields() function
-
clearFields()
-
-
# calling the listAllExpenses() function
-
listAllExpenses()
-
-
# returning the message box displaying the information
-
mb.showinfo(
‘All Expenses deleted’
,
‘All the expenses were successfully deleted’
)
-
else
:
-
# returning the message box, if the operation is aborted
-
mb.showinfo(
‘Ok then’
,
‘The task was aborted and no expense was deleted!’
)
The method is called removeAllExpenses() in the above code sample. In this function, we utilize the askyesno() method to show a confirmation dialog box and get the user’s answer. Afterwards, we cleared the whole table using the delete() function. In addition, we have used the SQL DELETE command to remove every record from the table. We have also invoked the clearFields() and listAllExpenses() methods, returning the message box with the data shown using the showinfo() method of the messagebox. Creating a new table entry
A procedure for inserting new rows into the table and the database will be defined immediately.
Let’s have a look at the following code snippet that exemplifies this. Get the main.py file and open it.
Explanation:
-
# function to add an expense
-
def
addAnotherExpense():
-
”
”’This function will add an expense to the table and database”’
-
-
# using some global variables
-
global
dateField, payee, description, amount, modeOfPayment
-
global
dbconnector
-
-
# if any of the field is empty, return the message box displaying error
-
if
not
dateField.get()
or
not
payee.get()
or
not
description.get()
or
not
amount.get()
or
not
modeOfPayment.get():
-
mb.showerror(
‘Fields empty!’
,
“Please fill all the missing fields before pressing the add button!”
)
-
else
:
-
# executing the SQL INSERT INTO command
-
dbconnector.execute(
-
‘INSERT INTO ExpenseTracker (Date, Payee, Description, Amount, ModeOfPayment) VALUES (?, ?, ?, ?, ?)’
,
-
(dateField.get_date(), payee.get(), description.get(), amount.get(), modeOfPayment.get())
-
)
-
dbconnector.commit()
-
-
# calling the clearFields() function
-
clearFields()
-
-
# calling the listAllExpenses() function
-
listAllExpenses()
-
-
# returning the message box displaying information
-
mb.showinfo(
‘Expense added’
,
‘The expense whose details you just entered has been added to the database’
)
The method is called addAnotherExpense() in the above code sample. We have made use of global variables inside this function. We then used the showerror() function of the messagebox module to produce a message box that displays an error if any of the fields are left blank. We then added the record to the table by executing the SQL INSERT query. We have used the showinfo() method of the messagebox to return the box showing the SUCCESS message after first using the clearFields() and listAllExpenses() routines. Altering Specific Expenditure Information
Now that we know what kind of expenses we’ll be dealing with, we can build a function to change the information in the table and the database accordingly.
Let’s have a look at the following code snippet that exemplifies this. Get the main.py file and open it.
Explanation:
-
# function to edit the details of an expense
-
def
editExpense():
-
”
”’This function will allow user to edit the details of the selected expense”’
-
-
# using some global variables
-
global
data_table
-
-
# defining a nested to update the details of the selected expense
-
def
editExistingExpense():
-
”
”’This function will update the details of the selected expense in the database and table”’
-
-
# using some global variables
-
global
dateField, amount, description, payee, modeOfPayment
-
global
dbconnector, data_table
-
-
# collecting the data from the selected row in dictionary format
-
currentSelectedExpense = data_table.item(data_table.focus())
-
-
# defining a variable to store the values from the collected data in list
-
content = currentSelectedExpense[
‘values’
]
-
-
# executing the SQL UPDATE command to update the record in database table
-
dbconnector.execute(
-
‘UPDATE ExpenseTracker SET Date = ?, Payee = ?, Description = ?, Amount = ?, ModeOfPayment = ? WHERE ID = ?’
,
-
(dateField.get_date(), payee.get(), description.get(), amount.get(), modeOfPayment.get(), content[
0
])
-
)
-
dbconnector.commit()
-
-
# calling the clearFields() function
-
clearFields()
-
-
# calling the listAllExpenses() function
-
listAllExpenses()
-
-
# returning a message box displaying the message
-
mb.showinfo(
‘Data edited’
,
‘We have updated the data and stored in the database as you wanted’
)
-
# destroying the edit button
-
editSelectedButton.destroy()
-
-
# returning a message box displaying error if no record is selected
-
if
not
data_table.selection():
-
mb.showerror(
‘No expense selected!’
,
‘You have not selected any expense in the table for us to edit; please do that!’
)
-
return
-
-
# calling the viewExpenseInfo() method
-
viewExpenseInfo()
-
-
# adding the Edit button to edit the selected record
-
editSelectedButton = Button(
-
frameL3,
-
text =
“Edit Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
30
,
-
bg =
“#90EE90”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#008000”
,
-
activeforeground =
“#98FB98”
,
-
command = editExistingExpense
-
)
-
-
# using the grid() method to set the position of the above button on the main window screen
-
editSelectedButton.grid(row =
0
, column =
0
, sticky = W, padx =
50
, pady =
10
)
The method is named “editExpense()” in the above code. We have made use of a few global variables inside this function. Then, an inner method called editExistingExpense() was created. The item() method was used to save the data in the dictionary format of the chosen record inside this nested function, which also made use of several global variables. We then extracted the values from the gathered data, stored them in a separate variable, and updated the information of the chosen records in the database table using the SQL UPDATE command. After invoking clearFields() and listAllExpenses(), we returned the message box showing the SUCCESS message using the showinfo() method of the messagebox. When no rows are chosen, an error is generated using the showerror() method of the messagebox, which is returned outside the nested function. After updating the record’s contents in the database, we accessed it using the viewExpenseInfo() method and a button we made with the Button() widget. Using the grid() function, we have successfully positioned the button on the window’s main screen. Word-Based Display of Selected Record Information
Next, we’ll build a method to get back the text box showing record information.
Here’s a code sample that illustrates the point. Get the main.py file and open it.
Explanation:
-
# function to display the details of selected expense into words
-
def
selectedExpenseToWords():
-
”
”’This function will display the details of the selected expense from the table into words”’
-
-
# using some global variables
-
global
data_table
-
-
# returning a message box displaying error, if no record is selected from the table
-
if
not
data_table.selection():
-
mb.showerror(
‘No expense selected!’
,
‘Please select an expense from the table for us to read’
)
-
return
-
-
# collecting the data from the selected row in dictionary format
-
currentSelectedExpense = data_table.item(data_table.focus())
-
-
# defining a variable to store the values from the collected data in list
-
val = currentSelectedExpense[
‘values’
]
-
-
# defining the message to be displayed in the message box
-
msg = f
‘Your expense can be read like: \n”You paid {val[4]} to {val[2]} for {val[3]} on {val[1]} via {val[5]}”‘
-
-
# returning the message box displaying the message
-
mb.showinfo(
‘Here\’s how to read your expense’
, msg)
We have defined the method in the above code snippet as selectedExpenseToWords(). This function makes use of global variables, determines whether or not the row is chosen, and then returns the message box after showing an error message through the showerror() method of the messagebox. We then established a new variable to hold the dictionary’s contents after using the item() function to gather them from the specified row. Using the messagebox module’s showinfo() function, we have specified the shown message and returned it in the message box. Word-Based Expenditure Descriptions Added Before the Table
Before adding the data into the table and the database, we will now construct the final function to show the spending information in words.
Let’s have a look at the following code snippet that exemplifies this. Get the main.py file and open it.
Explicit
-
# function to display the expense details into words before adding it to the table
-
def
expenseToWordsBeforeAdding():
-
”
”’This function will display the details of the expense into words before adding it to the table”’
-
-
# using some global variables
-
global
dateField, description, amount, payee, modeOfPayment
-
-
# if any of the field is empty, return the message box displaying error
-
if
not
dateField.get()
or
not
payee.get()
or
not
description.get()
or
not
amount.get()
or
not
modeOfPayment.get():
-
mb.showerror(
‘Incomplete data’
,
‘The data is incomplete, meaning fill all the fields first!’
)
-
else
:
-
# defining the message to be displayed in the message box
-
msg = f
‘Your expense can be read like: \n”You paid {amount.get()} to {payee.get()} for {description.get()} on {dateField.get_date()} via {modeOfPayment.get()}”‘
-
-
# displaying a message box asking for confirmation
-
addQuestion = mb.askyesno(
‘Read your record like: ‘
, f
‘{msg}\n\nShould I add it to the database?’
)
-
-
# if the user say YES, calling the addAnotherExpense() function
-
if
addQuestion:
-
addAnotherExpense()
-
else
:
-
# returning a message box displaying information
-
mb.showinfo(
‘Ok’
,
‘Please take your time to add this record’
)
:
In the above code sample, the function is defined as expenseToWordsBeforeAdding(). We have made use of a few global variables inside this function. We then sent back a dialog box that displays an error if any of the required fields are left blank. The text for the message box has been established. The askyesno() function of the messagebox module displays the message and asks the user for confirmation before inserting the data into the database. Next, we used the showinfo method on the messagebox to display the data after using the addAnotherExpense() function ()
method.
Connecting to the database and creating the main window of the application
It is time to connect the program to the database and build the main window now that we have specified all the required functions.
We’ll begin by exploring the sqlite3 module’s several options for establishing a connection between the application and the database.
Here’s a code sample that illustrates the point. Get the main.py file and open it.
Explanation:
-
# main function
-
if
__name__ ==
“__main__”
:
-
-
# connecting to the Database
-
dbconnector = sqlite3.connect(
“Expense_Tracker.db”
)
-
dbcursor = dbconnector.cursor()
-
-
# specifying the function to execute whenever the application runs
-
dbconnector.execute(
-
‘CREATE TABLE IF NOT EXISTS ExpenseTracker (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, Date DATETIME, Payee TEXT, Description TEXT, Amount FLOAT, ModeOfPayment TEXT)’
-
)
-
# committing the above command
-
dbconnector.commit()
The preceding code sample shows how to save data from a Python script in a SQLite database. The sqlite3.connect(databaseName>) function was used to build the dbconnector object and establish a connection to the database. Now that the database exists and is running, we can conduct operations on it using the execute() and commit() methods.
Thus, the database connection has been made. Now we’ll use the Tk() class from the tkinter library to create the app’s primary window. We’ll also provide the app’s title, size, and location on the display. We’ll set the background color and choose an icon, too. Get the main.py file and open it.
Explanation:
-
# creating the main window of the application
-
-
# creating an instance of the Tk() class
-
main_win = Tk()
-
# setting the title of the application
-
main_win.title(
“EXPENSE TRACKER – JAVATPOINT”
)
-
# setting the size and position of the window
-
main_win.geometry(
“1415×650+400+100”
)
-
# disabling the resizable option for better UI
-
main_win.resizable(
0
,
0
)
-
# configuring the background color to #FFFAF0
-
main_win.config(bg =
“#FFFAF0”
)
-
# setting the icon of the application
-
main_win.iconbitmap(
“./piggyBank.ico”
)
In the above code snippet, the main window is produced by instantiating the Tk() class with the name main win. The window’s title was then changed using the title() technique. For improved UI, we have additionally adjusted the window’s size and position using the geometry() method and disabled the resizable option by setting the values of the resizable() method’s arguments to 0. In order to change the window’s background color, we used the config() function. The icon of the has been successfully set using the iconbitmap() function.
window.
Adding the necessary widgets to the window and setting the event triggers
Once the application’s main window has been properly constructed, we can proceed to populating it with widgets and triggering events. You may use widgets like labels, input fields, buttons, and frames to organize and display content, enter data, and activate predefined actions.
Now, let’s go into exactly why these widgets were included. Combining Pictures
The first step is to install the window casings around the primary opening. These scaffolds will provide a framework for the remaining widgets. The tkinter module’s Frame() widget may be used to create such frames.
Let’s have a look at the following code snippet that exemplifies this. Get the main.py file and open it.
Explanation:
-
# adding frames to the window to provide structure to the other widgets
-
frameLeft = Frame(main_win, bg =
“#FFF8DC”
)
-
frameRight = Frame(main_win, bg =
“#DEB887”
)
-
frameL1 = Frame(frameLeft, bg =
“#FFF8DC”
)
-
frameL2 = Frame(frameLeft, bg =
“#FFF8DC”
)
-
frameL3 = Frame(frameLeft, bg =
“#FFF8DC”
)
-
frameR1 = Frame(frameRight, bg =
“#DEB887”
)
-
frameR2 = Frame(frameRight, bg =
“#DEB887”
)
-
-
# using the pack() method to set the position of the above frames
-
frameLeft.pack(side=LEFT, fill =
“both”
)
-
frameRight.pack(side = RIGHT, fill =
“both”
, expand =
True
)
-
frameL1.pack(fill =
“both”
)
-
frameL2.pack(fill =
“both”
)
-
frameL3.pack(fill =
“both”
)
-
frameR1.pack(fill =
“both”
)
-
frameR2.pack(fill =
“both”
, expand =
True
)
To add the frames to the window in the above code snippet, we made use of the Frame() widgets. The left and right master parameters of the first two frames have likewise been set to main win. Next, we’ve designated frameLeft as the master parameter for the following three frames, which represents the leftmost subframes. To indicate the smaller right-hand frame segments, we’ve set frameRight as the master parameter for the other two frames. By assigning a value to the bg option, we have established the background color. At the end, we utilized the pack() function to determine where these images would go.
When the frames have been inserted, the remaining widgets such as labels, input fields, an options menu, buttons, and a table may be placed inside them. Including the controls in frameL1
Now, we’ll label the first frame (hence referred to as “frameL1”). The application’s title and a subtitle designating the data input area will both appear on these labels. The necessary label will be generated using the Label() widget.
Let’s have a look at the following code snippet that exemplifies this. Get the main.py file and open it.
Explanation:
-
# —————- Adding widgets to the frameL1 frame —————-
-
-
# adding the label to display the heading
-
headingLabel = Label(
-
frameL1,
-
text =
“EXPENSE TRACKER”
,
-
font = (
“Bahnschrift Condensed”
,
“25”
),
-
width =
20
,
-
bg =
“#8B4513”
,
-
fg =
“#FFFAF0”
-
)
-
-
# adding the label to display the subheading
-
subheadingLabel = Label(
-
frameL1,
-
text =
“Data Entry Frame”
,
-
font = (
“Bahnschrift Condensed”
,
“15”
),
-
width =
20
,
-
bg =
“#F5DEB3”
,
-
fg =
“#000000”
-
)
-
-
# using the pack() method to set the position of the above labels
-
headingLabel.pack(fill =
“both”
)
-
subheadingLabel.pack(fill =
“both”
)
In the above code, the Label() widget was used to generate the necessary labels. Then, we directed these labels’ master parameter to the frameL1 frame. We’ve also detailed the text’s font, size, color, and background/foreground pairing. The labels up above have been placed using the pack() technique. Including the controls in the frameL2 layout
We’ll start by populating the second frame (frameL2 ) with various widgets. These widgets have text input areas, a drop-down menu, and a labeled data display area. To make the labels, the Entry fields, and the drop-down menu, we’ll use the Label(), Entry(), and OptionMenu() widgets, respectively. The DateEntry() widget will be used to generate a calendar picker for the user.
Here’s a code sample that illustrates the point. Get the main.py file and open it.
Explanation:
-
# —————- Adding widgets to the frameL2 frame —————-
-
-
# creating some labels to ask user to enter the required data
-
# date label
-
dateLabel = Label(
-
frameL2,
-
text =
“Date:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# description label
-
descriptionLabel = Label(
-
frameL2,
-
text =
“Description:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# amount label
-
amountLabel = Label(
-
frameL2,
-
text =
“Amount:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# payee label
-
payeeLabel = Label(
-
frameL2,
-
text =
“Payee:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# mode of payment label
-
modeLabel = Label(
-
frameL2,
-
text =
“Mode of \nPayment:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# using the grid() method to set the position of the above labels in the grid format
-
dateLabel.grid(row =
0
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
descriptionLabel.grid(row =
1
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
amountLabel.grid(row =
2
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
payeeLabel.grid(row =
3
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
modeLabel.grid(row =
4
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
-
# instantiating the StringVar() class to retrieve the data in the string format from the user
-
description = StringVar()
-
payee = StringVar()
-
modeOfPayment = StringVar(value =
“Cash”
)
-
# instantiating the DoubleVar() class to retrieve the amount detail in double datatype
-
amount = DoubleVar()
-
-
# creating a drop-down calendar for the user to enter the date
-
dateField = DateEntry(
-
frameL2,
-
date = datetime.datetime.now().date(),
-
font = (
“consolas”
,
“11”
),
-
relief = GROOVE
-
)
-
-
# creating entry fields to enter the labelled data
-
# field to enter description
-
descriptionField = Entry(
-
frameL2,
-
text = description,
-
width =
20
,
-
font = (
“consolas”
,
“11”
),
-
bg =
“#FFFFFF”
,
-
fg =
“#000000”
,
-
relief = GROOVE
-
)
-
-
# field to enter the amount
-
amountField = Entry(
-
frameL2,
-
text = amount,
-
width =
20
,
-
font = (
“consolas”
,
“11”
),
-
bg =
“#FFFFFF”
,
-
fg =
“#000000”
,
-
relief = GROOVE
-
)
-
-
# field to enter payee information
-
payeeField = Entry(
-
frameL2,
-
text = payee,
-
width =
20
,
-
font = (
“consolas”
,
“11”
),
-
bg =
“#FFFFFF”
,
-
fg =
“#000000”
,
-
relief = GROOVE
-
)
-
-
# creating a drop-down menu to enter the mode of payment
-
modeField = OptionMenu(
-
frameL2,
-
modeOfPayment,
-
*[
‘Cash’
,
‘Cheque’
,
‘Credit Card’
,
‘Debit Card’
,
‘UPI’
,
‘Paytm’
,
‘Google Pay’
,
‘PhonePe’
,
‘Razorpay’
]
-
)
-
# using the config() method to configure the width, font style, and background color of the option menu
-
modeField.config(
-
width =
15
,
-
font = (
“consolas”
,
“10”
),
-
relief = GROOVE,
-
bg =
“#FFFFFF”
-
)
-
-
# using the grid() method to set the position of the above widgets in the grid format
-
dateField.grid(row =
0
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
descriptionField.grid(row =
1
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
amountField.grid(row =
2
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
payeeField.grid(row =
3
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
modeField.grid(row =
4
, column =
1
, sticky = W, padx =
10
, pady =
10
)
Using the Label() widget, we added labels to the above code that prompt the user for the date, description, amount, payee’s name, and method of payment when creating an expense report. One of the frames we created, frameL2, is now the master parameter of these labels. The grid() function was then used to assign a location for these labels inside the grid. Next, to get the information back in String form, we created an instance of the StringVar() class. To get the value as a double, we’ve additionally created an instance of the DoubleVar() class. The DateEntry() class was then used to include a calendar picker into the form. By utilizing the Entry() widget and directing its master settings to the frameL2 frame, we’ve added a few input fields for the user to fill in, such as the expense’s description, amount, and payee’s name. We’ve also included a drop-down menu for choosing payment method using the OptionMenu() widget and have it configured to have frameL2 as its master argument. Finally, the aforementioned widgets’ positions on the main window screen have been established using the grid() function once again. Integrating the widgets into the frameL3
Frame L3 (the third frame) will now have some buttons added to it. Using these options, the user may enter the cost into the table, transform the cost into text before entering it, or clear the entry entirely. The Button() widget will be used to make the buttons.
Here’s a code sample that illustrates the point. Get the main.py file and open it.
Explanation:
-
# —————- Adding widgets to the frameL3 frame —————-
-
-
# creating buttons to manipulate data
-
# insert button
-
insertButton = Button(
-
frameL3,
-
text =
“Add Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
30
,
-
bg =
“#90EE90”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#008000”
,
-
activeforeground =
“#98FB98”
,
-
command = addAnotherExpense
-
)
-
-
# convert button
-
convertButton = Button(
-
frameL3,
-
text =
“Convert to Text before Adding”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
30
,
-
bg =
“#90EE90”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#008000”
,
-
activeforeground =
“#98FB98”
,
-
command = expenseToWordsBeforeAdding
-
)
-
-
# reset button
-
resetButton = Button(
-
frameL3,
-
text =
“Reset the fields”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
30
,
-
bg =
“#FF0000”
,
-
fg =
“#FFFFFF”
,
-
relief = GROOVE,
-
activebackground =
“#8B0000”
,
-
activeforeground =
“#FFB4B4”
,
-
command = clearFields
-
)
-
-
# using the grid() method to set the position of the above buttons
-
insertButton.grid(row =
0
, column =
0
, sticky = W, padx =
50
, pady =
10
)
-
convertButton.grid(row =
1
, column =
0
, sticky = W, padx =
50
, pady =
10
)
-
resetButton.grid(row =
2
, column =
0
, sticky = W, padx =
50
, pady =
10
)
The buttons in the above code snippet were added using the Button() widget. One of the frames we made, frameL3, is now set as the master parameter for these buttons. We have also built other functions to modify the data and supplied their command arguments. Finally, the grid() function was used to arrange the buttons in a grid. Including the controls in frameR1
The fourth frame, designated frameR1, will now have additional controls added to it. The user may use these buttons to see the chosen cost’s information, change the selected expense’s details, see the expense details in text format, remove the selected record from the table, and remove all records from the table. The Button() widget will be used once again to make the buttons.
Here’s a code sample that illustrates the point. Get the main.py file and open it.
Explanation:
-
# —————- Adding widgets to the frameR1 frame —————-
-
-
# creating buttons to manipulate data
-
# view button
-
viewButton = Button(
-
frameR1,
-
text =
“View Selected Expense\’s Details”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = viewExpenseInfo
-
)
-
-
# edit button
-
editButton = Button(
-
frameR1,
-
text =
“Edit Selected Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = editExpense
-
)
-
-
# convert button
-
convertSelectedButton = Button(
-
frameR1,
-
text =
“Convert Selected Expense to a Sentence”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = selectedExpenseToWords
-
)
-
-
# delete button
-
deleteButton = Button(
-
frameR1,
-
text =
“Delete Selected Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = removeExpense
-
)
-
-
# delete all button
-
deleteAllButton = Button(
-
frameR1,
-
text =
“Delete All Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = removeAllExpenses
-
)
-
-
# using the grid() method to set the position of the above buttons
-
viewButton.grid(row =
0
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
editButton.grid(row =
0
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
convertSelectedButton.grid(row =
0
, column =
2
, sticky = W, padx =
10
, pady =
10
)
-
deleteButton.grid(row =
1
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
deleteAllButton.grid(row =
1
, column =
1
, sticky = W, padx =
10
, pady =
10
)
The buttons in the above code snippet were added using the Button() widget. One of the frames we made, frameR1, is now set as the master parameter for these switches. We have also built other functions to modify the data and supplied their command arguments. Finally, the grid() function was used to arrange the buttons in a grid. The frameR2 was updated to include the widgets.
In this step, we’ll modify the fifth frame (frameR2) by inserting a table. Every record in the database will be listed here. To make a table out of the information, we’ll utilize the ttk module’s Treeview() widget. With the aid of the Scrollbar() widget, we’ll add horizontal and vertical scrollbars to this layout so that users can easily navigate the data. In addition, we will format it as a table with headers and columns.
Here’s a code sample that illustrates the point. Get the main.py file and open it.
-
# —————- Adding widgets to the frameR2 frame —————-
-
-
# creating a table to display all the entries
-
data_table = ttk.Treeview(
-
frameR2,
-
selectmode = BROWSE,
-
columns = (
‘ID’
,
‘Date’
,
‘Payee’
,
‘Description’
,
‘Amount’
,
‘Mode of Payment’
)
-
)
-
-
# creating a horizontal scrollbar to the table
-
Xaxis_Scrollbar = Scrollbar(
-
data_table,
-
orient = HORIZONTAL,
-
command = data_table.xview
-
)
-
-
# creating a vertical scrollbar to the table
-
Yaxis_Scrollbar = Scrollbar(
-
data_table,
-
orient = VERTICAL,
-
command = data_table.yview
-
)
-
-
# using the pack() method to set the position of the scrollbars
-
Xaxis_Scrollbar.pack(side = BOTTOM, fill = X)
-
Yaxis_Scrollbar.pack(side = RIGHT, fill = Y)
-
-
# configuring the horizontal and vertical scrollbars on the table
-
data_table.config(yscrollcommand = Yaxis_Scrollbar.set, xscrollcommand = Xaxis_Scrollbar.set)
-
-
# adding different headings to the table
-
data_table.heading(
‘ID’
, text =
‘S No.’
, anchor = CENTER)
-
data_table.heading(
‘Date’
, text =
‘Date’
, anchor = CENTER)
-
data_table.heading(
‘Payee’
, text =
‘Payee’
, anchor = CENTER)
-
data_table.heading(
‘Description’
, text =
‘Description’
, anchor = CENTER)
-
data_table.heading(
‘Amount’
, text =
‘Amount’
, anchor = CENTER)
-
data_table.heading(
‘Mode of Payment’
, text =
‘Mode of Payment’
, anchor = CENTER)
-
-
# adding different columns to the table
-
data_table.column(
‘#0’
, width =
0
, stretch = NO)
-
data_table.column(
‘#1’
, width =
50
, stretch = NO)
-
data_table.column(
‘#2’
, width =
95
, stretch = NO)
-
data_table.column(
‘#3’
, width =
150
, stretch = NO)
-
data_table.column(
‘#4’
, width =
450
, stretch = NO)
-
data_table.column(
‘#5’
, width =
135
, stretch = NO)
-
data_table.column(
‘#6’
, width =
140
, stretch = NO)
-
-
# using the place() method to set the position of the table on the main window screen
-
data_table.place(relx =
0
, y =
0
, relheight =
1
, relwidth =
1
)
Clarification:
In the above code snippet, we use the Treeview() widget from the ttk module to construct a tabular structure and assign frameR2, one of our previously specified frames, as the master parameter of this widget. With this widget, we have also set the selection mode and named the columns. We then used the Scrollbar() widget to create two scrollbars for the table, with the master argument set to the tabular structure (data table) we’d made before. Both the horizontal and vertical settings for these scrollbars have been activated. The location of these scrollbars on the table was then determined using the pack() technique. The table’s scrollbars were also customized using the config() function. The table was then expanded with new rows and columns. Finally, we have positioned the table in the main window by using the put() function. Initiating the Program
To begin the program’s execution, we’ll utilize the mainloop() function on a Tk() object.
Here’s a code sample that illustrates the point. Get the main.py file and open it.
Explanation:
-
# using mainloop() method to run the application
-
main_win.mainloop()
To execute the program, the above code uses the mainloop() function on the main win object of the Tk() class.
Hence, the project’s code has been finalized. To check the results, we’ll save this python program file and then type the following command into the terminal. Syntax:
-
$ python main.py
First, the source code for the whole “Expense Tracker Application using Tkinter” project is shown.
below.
The Complete Project Code
Code for “Expense Tracker Application” built using Python’s Tkinter is provided below. Get the main.py file and open it.
Product:
-
# importing the required modules
-
from
tkinter
import
*
# importing all the modules and classes from tkinter
-
from
tkinter
import
ttk as ttk
# importing the ttk module from tkinter
-
from
tkinter
import
messagebox as mb
# importing the messagebox module from tkinter
-
import
datetime
# importing the datetime module
-
import
sqlite3
# importing the sqlite3 module
-
from
tkcalendar
import
DateEntry
# importing the DateEntry class from the tkcalendar module
-
-
# ——————— defining functions ———————
-
-
# function to list all the expenses
-
def
listAllExpenses():
-
”
”’This function will retrieve the data from the database and insert it to the tkinter data table”’
-
-
# using some global variables
-
global
dbconnector, data_table
-
# clearing the table
-
data_table.delete(*data_table.get_children())
-
# executing the SQL SELECT command to retrieve the data from the database table
-
all_data = dbconnector.execute(
‘SELECT * FROM ExpenseTracker’
)
-
-
# listing the data from the table
-
data = all_data.fetchall()
-
-
# inserting the values iteratively in the tkinter data table
-
for
val
in
data:
-
data_table.insert(
”
, END, values = val)
-
-
# function to view an expense information
-
def
viewExpenseInfo():
-
”
”’This function will display the expense information in the data frame”’
-
-
# using some global variables
-
global
data_table
-
global
dateField, payee, description, amount, modeOfPayment
-
-
# return a message box displaying error if no row is selected from the table
-
if
not
data_table.selection():
-
mb.showerror(
‘No expense selected’
,
‘Please select an expense from the table to view its details’
)
-
-
# collecting the data from the selected row in dictionary format
-
currentSelectedExpense = data_table.item(data_table.focus())
-
-
# defining a variable to store the values from the collected data in list
-
val = currentSelectedExpense[
‘values’
]
-
-
# retrieving the date of expenditure from the list
-
expenditureDate = datetime.date(int(val[
1
][:
4
]), int(val[
1
][
5
:
7
]), int(val[
1
][
8
:]))
-
-
# setting the listed data in their respective entry fields
-
dateField.set_date(expenditureDate) ; payee.set(val[
2
]) ; description.set(val[
3
]) ; amount.set(val[
4
]) ; modeOfPayment.set(val[
5
])
-
-
# function to clear the entries from the entry fields
-
def
clearFields():
-
”
”’This function will clear all the entries from the entry fields”’
-
-
# using some global variables
-
global
description, payee, amount, modeOfPayment, dateField, data_table
-
-
# defining a variable to store today’s date
-
todayDate = datetime.datetime.now().date()
-
-
# setting the values in entry fields back to initial
-
description.set(
”
) ; payee.set(
”
) ; amount.set(
0.0
) ; modeOfPayment.set(
‘Cash’
), dateField.set_date(todayDate)
-
# removing the specified item from the selection
-
data_table.selection_remove(*data_table.selection())
-
-
# function to delete the selected record
-
def
removeExpense():
-
”
”’This function will remove the selected record from the table”’
-
-
# returning the message box displaying error if no row is selected
-
if
not
data_table.selection():
-
mb.showerror(
‘No record selected!’
,
‘Please select a record to delete!’
)
-
return
-
-
# collecting the data from the selected row in dictionary format
-
currentSelectedExpense = data_table.item(data_table.focus())
-
-
# defining a variable to store the values from the collected data in list
-
valuesSelected = currentSelectedExpense[
‘values’
]
-
-
# displaying a message box asking for confirmation
-
confirmation = mb.askyesno(
‘Are you sure?’
, f
‘Are you sure that you want to delete the record of {valuesSelected[2]}’
)
-
-
# if the user say YES, executing the SQL DELETE FROM command
-
if
confirmation:
-
dbconnector.execute(
‘DELETE FROM ExpenseTracker WHERE ID=%d’
% valuesSelected[
0
])
-
dbconnector.commit()
-
-
# calling the listAllExpenses() function
-
listAllExpenses()
-
-
# returning the message box displaying the information
-
mb.showinfo(
‘Record deleted successfully!’
,
‘The record you wanted to delete has been deleted successfully’
)
-
-
# function to delete all the entries
-
def
removeAllExpenses():
-
”
”’This function will remove all the entries from the table”’
-
-
# displaying a message box asking for confirmation
-
confirmation = mb.askyesno(
‘Are you sure?’
,
‘Are you sure that you want to delete all the expense items from the database?’
, icon=
‘warning’
)
-
-
# if the user say YES, deleting the entries from the table and executing the SQL DELETE FROM command to delete all the entries
-
if
confirmation:
-
data_table.delete(*data_table.get_children())
-
-
dbconnector.execute(
‘DELETE FROM ExpenseTracker’
)
-
dbconnector.commit()
-
-
# calling the clearFields() function
-
clearFields()
-
-
# calling the listAllExpenses() function
-
listAllExpenses()
-
-
# returning the message box displaying the information
-
mb.showinfo(
‘All Expenses deleted’
,
‘All the expenses were successfully deleted’
)
-
else
:
-
# returning the message box, if the operation is aborted
-
mb.showinfo(
‘Ok then’
,
‘The task was aborted and no expense was deleted!’
)
-
-
# function to add an expense
-
def
addAnotherExpense():
-
”
”’This function will add an expense to the table and database”’
-
-
# using some global variables
-
global
dateField, payee, description, amount, modeOfPayment
-
global
dbconnector
-
-
# if any of the field is empty, return the message box displaying error
-
if
not
dateField.get()
or
not
payee.get()
or
not
description.get()
or
not
amount.get()
or
not
modeOfPayment.get():
-
mb.showerror(
‘Fields empty!’
,
“Please fill all the missing fields before pressing the add button!”
)
-
else
:
-
# executing the SQL INSERT INTO command
-
dbconnector.execute(
-
‘INSERT INTO ExpenseTracker (Date, Payee, Description, Amount, ModeOfPayment) VALUES (?, ?, ?, ?, ?)’
,
-
(dateField.get_date(), payee.get(), description.get(), amount.get(), modeOfPayment.get())
-
)
-
dbconnector.commit()
-
-
# calling the clearFields() function
-
clearFields()
-
-
# calling the listAllExpenses() function
-
listAllExpenses()
-
-
# returning the message box displaying information
-
mb.showinfo(
‘Expense added’
,
‘The expense whose details you just entered has been added to the database’
)
-
-
# function to edit the details of an expense
-
def
editExpense():
-
”
”’This function will allow user to edit the details of the selected expense”’
-
-
# using some global variables
-
global
data_table
-
-
# defining a nested to update the details of the selected expense
-
def
editExistingExpense():
-
”
”’This function will update the details of the selected expense in the database and table”’
-
-
# using some global variables
-
global
dateField, amount, description, payee, modeOfPayment
-
global
dbconnector, data_table
-
-
# collecting the data from the selected row in dictionary format
-
currentSelectedExpense = data_table.item(data_table.focus())
-
-
# defining a variable to store the values from the collected data in list
-
content = currentSelectedExpense[
‘values’
]
-
-
# executing the SQL UPDATE command to update the record in database table
-
dbconnector.execute(
-
‘UPDATE ExpenseTracker SET Date = ?, Payee = ?, Description = ?, Amount = ?, ModeOfPayment = ? WHERE ID = ?’
,
-
(dateField.get_date(), payee.get(), description.get(), amount.get(), modeOfPayment.get(), content[
0
])
-
)
-
dbconnector.commit()
-
-
# calling the clearFields() function
-
clearFields()
-
-
# calling the listAllExpenses() function
-
listAllExpenses()
-
-
# returning a message box displaying the message
-
mb.showinfo(
‘Data edited’
,
‘We have updated the data and stored in the database as you wanted’
)
-
# destroying the edit button
-
editSelectedButton.destroy()
-
-
# returning a message box displaying error if no record is selected
-
if
not
data_table.selection():
-
mb.showerror(
‘No expense selected!’
,
‘You have not selected any expense in the table for us to edit; please do that!’
)
-
return
-
-
# calling the viewExpenseInfo() method
-
viewExpenseInfo()
-
-
# adding the Edit button to edit the selected record
-
editSelectedButton = Button(
-
frameL3,
-
text =
“Edit Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
30
,
-
bg =
“#90EE90”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#008000”
,
-
activeforeground =
“#98FB98”
,
-
command = editExistingExpense
-
)
-
-
# using the grid() method to set the position of the above button on the main window screen
-
editSelectedButton.grid(row =
0
, column =
0
, sticky = W, padx =
50
, pady =
10
)
-
-
# function to display the details of selected expense into words
-
def
selectedExpenseToWords():
-
”
”’This function will display the details of the selected expense from the table into words”’
-
-
# using some global variables
-
global
data_table
-
-
# returning a message box displaying error, if no record is selected from the table
-
if
not
data_table.selection():
-
mb.showerror(
‘No expense selected!’
,
‘Please select an expense from the table for us to read’
)
-
return
-
-
# collecting the data from the selected row in dictionary format
-
currentSelectedExpense = data_table.item(data_table.focus())
-
-
# defining a variable to store the values from the collected data in list
-
val = currentSelectedExpense[
‘values’
]
-
-
# defining the message to be displayed in the message box
-
msg = f
‘Your expense can be read like: \n”You paid {val[4]} to {val[2]} for {val[3]} on {val[1]} via {val[5]}”‘
-
-
# returning the message box displaying the message
-
mb.showinfo(
‘Here\’s how to read your expense’
, msg)
-
-
# function to display the expense details into words before adding it to the table
-
def
expenseToWordsBeforeAdding():
-
”
”’This function will display the details of the expense into words before adding it to the table”’
-
-
# using some global variables
-
global
dateField, description, amount, payee, modeOfPayment
-
-
# if any of the field is empty, return the message box displaying error
-
if
not
dateField.get()
or
not
payee.get()
or
not
description.get()
or
not
amount.get()
or
not
modeOfPayment.get():
-
mb.showerror(
‘Incomplete data’
,
‘The data is incomplete, meaning fill all the fields first!’
)
-
else
:
-
# defining the message to be displayed in the message box
-
msg = f
‘Your expense can be read like: \n”You paid {amount.get()} to {payee.get()} for {description.get()} on {dateField.get_date()} via {modeOfPayment.get()}”‘
-
-
# displaying a message box asking for confirmation
-
addQuestion = mb.askyesno(
‘Read your record like: ‘
, f
‘{msg}\n\nShould I add it to the database?’
)
-
-
# if the user say YES, calling the addAnotherExpense() function
-
if
addQuestion:
-
addAnotherExpense()
-
else
:
-
# returning a message box displaying information
-
mb.showinfo(
‘Ok’
,
‘Please take your time to add this record’
)
-
-
# main function
-
if
__name__ ==
“__main__”
:
-
-
# connecting to the Database
-
dbconnector = sqlite3.connect(
“Expense_Tracker.db”
)
-
dbcursor = dbconnector.cursor()
-
-
# specifying the function to execute whenever the application runs
-
dbconnector.execute(
-
‘CREATE TABLE IF NOT EXISTS ExpenseTracker (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, Date DATETIME, Payee TEXT, Description TEXT, Amount FLOAT, ModeOfPayment TEXT)’
-
)
-
# committing the above command
-
dbconnector.commit()
-
-
# creating the main window of the application
-
-
# creating an instance of the Tk() class
-
main_win = Tk()
-
# setting the title of the application
-
main_win.title(
“EXPENSE TRACKER – JAVATPOINT”
)
-
# setting the size and position of the window
-
main_win.geometry(
“1415×650+400+100”
)
-
# disabling the resizable option for better UI
-
main_win.resizable(
0
,
0
)
-
# configuring the background color to #FFFAF0
-
main_win.config(bg =
“#FFFAF0”
)
-
# setting the icon of the application
-
main_win.iconbitmap(
“./piggyBank.ico”
)
-
-
# adding frames to the window to provide structure to the other widgets
-
frameLeft = Frame(main_win, bg =
“#FFF8DC”
)
-
frameRight = Frame(main_win, bg =
“#DEB887”
)
-
frameL1 = Frame(frameLeft, bg =
“#FFF8DC”
)
-
frameL2 = Frame(frameLeft, bg =
“#FFF8DC”
)
-
frameL3 = Frame(frameLeft, bg =
“#FFF8DC”
)
-
frameR1 = Frame(frameRight, bg =
“#DEB887”
)
-
frameR2 = Frame(frameRight, bg =
“#DEB887”
)
-
-
# using the pack() method to set the position of the above frames
-
frameLeft.pack(side=LEFT, fill =
“both”
)
-
frameRight.pack(side = RIGHT, fill =
“both”
, expand =
True
)
-
frameL1.pack(fill =
“both”
)
-
frameL2.pack(fill =
“both”
)
-
frameL3.pack(fill =
“both”
)
-
frameR1.pack(fill =
“both”
)
-
frameR2.pack(fill =
“both”
, expand =
True
)
-
-
# —————- Adding widgets to the frameL1 frame —————-
-
-
# adding the label to display the heading
-
headingLabel = Label(
-
frameL1,
-
text =
“EXPENSE TRACKER”
,
-
font = (
“Bahnschrift Condensed”
,
“25”
),
-
width =
20
,
-
bg =
“#8B4513”
,
-
fg =
“#FFFAF0”
-
)
-
-
# adding the label to display the subheading
-
subheadingLabel = Label(
-
frameL1,
-
text =
“Data Entry Frame”
,
-
font = (
“Bahnschrift Condensed”
,
“15”
),
-
width =
20
,
-
bg =
“#F5DEB3”
,
-
fg =
“#000000”
-
)
-
-
# using the pack() method to set the position of the above labels
-
headingLabel.pack(fill =
“both”
)
-
subheadingLabel.pack(fill =
“both”
)
-
-
# —————- Adding widgets to the frameL2 frame —————-
-
-
# creating some labels to ask user to enter the required data
-
# date label
-
dateLabel = Label(
-
frameL2,
-
text =
“Date:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# description label
-
descriptionLabel = Label(
-
frameL2,
-
text =
“Description:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# amount label
-
amountLabel = Label(
-
frameL2,
-
text =
“Amount:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# payee label
-
payeeLabel = Label(
-
frameL2,
-
text =
“Payee:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# mode of payment label
-
modeLabel = Label(
-
frameL2,
-
text =
“Mode of \nPayment:”
,
-
font = (
“consolas”
,
“11”
,
“bold”
),
-
bg =
“#FFF8DC”
,
-
fg =
“#000000”
-
)
-
-
# using the grid() method to set the position of the above labels in the grid format
-
dateLabel.grid(row =
0
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
descriptionLabel.grid(row =
1
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
amountLabel.grid(row =
2
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
payeeLabel.grid(row =
3
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
modeLabel.grid(row =
4
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
-
# instantiating the StringVar() class to retrieve the data in the string format from the user
-
description = StringVar()
-
payee = StringVar()
-
modeOfPayment = StringVar(value =
“Cash”
)
-
# instantiating the DoubleVar() class to retrieve the amount detail in double datatype
-
amount = DoubleVar()
-
-
# creating a drop-down calendar for the user to enter the date
-
dateField = DateEntry(
-
frameL2,
-
date = datetime.datetime.now().date(),
-
font = (
“consolas”
,
“11”
),
-
relief = GROOVE
-
)
-
-
# creating entry fields to enter the labelled data
-
# field to enter description
-
descriptionField = Entry(
-
frameL2,
-
text = description,
-
width =
20
,
-
font = (
“consolas”
,
“11”
),
-
bg =
“#FFFFFF”
,
-
fg =
“#000000”
,
-
relief = GROOVE
-
)
-
-
# field to enter the amount
-
amountField = Entry(
-
frameL2,
-
text = amount,
-
width =
20
,
-
font = (
“consolas”
,
“11”
),
-
bg =
“#FFFFFF”
,
-
fg =
“#000000”
,
-
relief = GROOVE
-
)
-
-
# field to enter payee information
-
payeeField = Entry(
-
frameL2,
-
text = payee,
-
width =
20
,
-
font = (
“consolas”
,
“11”
),
-
bg =
“#FFFFFF”
,
-
fg =
“#000000”
,
-
relief = GROOVE
-
)
-
-
# creating a drop-down menu to enter the mode of payment
-
modeField = OptionMenu(
-
frameL2,
-
modeOfPayment,
-
*[
‘Cash’
,
‘Cheque’
,
‘Credit Card’
,
‘Debit Card’
,
‘UPI’
,
‘Paytm’
,
‘Google Pay’
,
‘PhonePe’
,
‘Razorpay’
]
-
)
-
# using the config() method to configure the width, font style, and background color of the option menu
-
modeField.config(
-
width =
15
,
-
font = (
“consolas”
,
“10”
),
-
relief = GROOVE,
-
bg =
“#FFFFFF”
-
)
-
-
# using the grid() method to set the position of the above widgets in the grid format
-
dateField.grid(row =
0
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
descriptionField.grid(row =
1
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
amountField.grid(row =
2
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
payeeField.grid(row =
3
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
modeField.grid(row =
4
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
-
# —————- Adding widgets to the frameL3 frame —————-
-
-
# creating buttons to manipulate data
-
# insert button
-
insertButton = Button(
-
frameL3,
-
text =
“Add Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
30
,
-
bg =
“#90EE90”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#008000”
,
-
activeforeground =
“#98FB98”
,
-
command = addAnotherExpense
-
)
-
-
# convert button
-
convertButton = Button(
-
frameL3,
-
text =
“Convert to Text before Adding”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
30
,
-
bg =
“#90EE90”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#008000”
,
-
activeforeground =
“#98FB98”
,
-
command = expenseToWordsBeforeAdding
-
)
-
-
# reset button
-
resetButton = Button(
-
frameL3,
-
text =
“Reset the fields”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
30
,
-
bg =
“#FF0000”
,
-
fg =
“#FFFFFF”
,
-
relief = GROOVE,
-
activebackground =
“#8B0000”
,
-
activeforeground =
“#FFB4B4”
,
-
command = clearFields
-
)
-
-
# using the grid() method to set the position of the above buttons
-
insertButton.grid(row =
0
, column =
0
, sticky = W, padx =
50
, pady =
10
)
-
convertButton.grid(row =
1
, column =
0
, sticky = W, padx =
50
, pady =
10
)
-
resetButton.grid(row =
2
, column =
0
, sticky = W, padx =
50
, pady =
10
)
-
-
# —————- Adding widgets to the frameR1 frame —————-
-
-
# creating buttons to manipulate data
-
# view button
-
viewButton = Button(
-
frameR1,
-
text =
“View Selected Expense\’s Details”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = viewExpenseInfo
-
)
-
-
# edit button
-
editButton = Button(
-
frameR1,
-
text =
“Edit Selected Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = editExpense
-
)
-
-
# convert button
-
convertSelectedButton = Button(
-
frameR1,
-
text =
“Convert Selected Expense to a Sentence”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = selectedExpenseToWords
-
)
-
-
# delete button
-
deleteButton = Button(
-
frameR1,
-
text =
“Delete Selected Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = removeExpense
-
)
-
-
# delete all button
-
deleteAllButton = Button(
-
frameR1,
-
text =
“Delete All Expense”
,
-
font = (
“Bahnschrift Condensed”
,
“13”
),
-
width =
35
,
-
bg =
“#FFDEAD”
,
-
fg =
“#000000”
,
-
relief = GROOVE,
-
activebackground =
“#A0522D”
,
-
activeforeground =
“#FFF8DC”
,
-
command = removeAllExpenses
-
)
-
-
# using the grid() method to set the position of the above buttons
-
viewButton.grid(row =
0
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
editButton.grid(row =
0
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
convertSelectedButton.grid(row =
0
, column =
2
, sticky = W, padx =
10
, pady =
10
)
-
deleteButton.grid(row =
1
, column =
0
, sticky = W, padx =
10
, pady =
10
)
-
deleteAllButton.grid(row =
1
, column =
1
, sticky = W, padx =
10
, pady =
10
)
-
-
# —————- Adding widgets to the frameR2 frame —————-
-
-
# creating a table to display all the entries
-
data_table = ttk.Treeview(
-
frameR2,
-
selectmode = BROWSE,
-
columns = (
‘ID’
,
‘Date’
,
‘Payee’
,
‘Description’
,
‘Amount’
,
‘Mode of Payment’
)
-
)
-
-
# creating a horizontal scrollbar to the table
-
Xaxis_Scrollbar = Scrollbar(
-
data_table,
-
orient = HORIZONTAL,
-
command = data_table.xview
-
)
-
-
# creating a vertical scrollbar to the table
-
Yaxis_Scrollbar = Scrollbar(
-
data_table,
-
orient = VERTICAL,
-
command = data_table.yview
-
)
-
-
# using the pack() method to set the position of the scrollbars
-
Xaxis_Scrollbar.pack(side = BOTTOM, fill = X)
-
Yaxis_Scrollbar.pack(side = RIGHT, fill = Y)
-
-
# configuring the horizontal and vertical scrollbars on the table
-
data_table.config(yscrollcommand = Yaxis_Scrollbar.set, xscrollcommand = Xaxis_Scrollbar.set)
-
-
# adding different headings to the table
-
data_table.heading(
‘ID’
, text =
‘S No.’
, anchor = CENTER)
-
data_table.heading(
‘Date’
, text =
‘Date’
, anchor = CENTER)
-
data_table.heading(
‘Payee’
, text =
‘Payee’
, anchor = CENTER)
-
data_table.heading(
‘Description’
, text =
‘Description’
, anchor = CENTER)
-
data_table.heading(
‘Amount’
, text =
‘Amount’
, anchor = CENTER)
-
data_table.heading(
‘Mode of Payment’
, text =
‘Mode of Payment’
, anchor = CENTER)
-
-
# adding different columns to the table
-
data_table.column(
‘#0’
, width =
0
, stretch = NO)
-
data_table.column(
‘#1’
, width =
50
, stretch = NO)
-
data_table.column(
‘#2’
, width =
95
, stretch = NO)
-
data_table.column(
‘#3’
, width =
150
, stretch = NO)
-
data_table.column(
‘#4’
, width =
450
, stretch = NO)
-
data_table.column(
‘#5’
, width =
135
, stretch = NO)
-
data_table.column(
‘#6’
, width =
140
, stretch = NO)
-
-
# using the place() method to set the position of the table on the main window screen
-
data_table.place(relx =
0
, y =
0
, relheight =
1
, relwidth =
1
)
-
-
# using mainloop() method to run the application
-
main_win.mainloop()