Back to: Python Programming
Write to a TXT file
Some code to make a “Contacts” list using Tkinter to open a window that asks the user to enter their Firstname, Lastname and Mobile.
A submit button saves the data entered to a txt file in the location: c:/data/python/contacts.txt.
A display button lists the entered contacts on the page.
When opening a file for reading or writing, there are several options available:
| Mode | Name | Behaviour | What happens to existing data? |
| “r” | Read | Opens the file for reading only. | Nothing; it’s safe. |
| “w” | Write | Creates a new file or truncates (wipes) an existing one. | Deleted immediately. |
| “a” | Append | Opens for writing, but starts at the end of the file. | Preserved. New data is added to the end. |
First – to add form data to the TXT file:
from tkinter import *
# Provides functions for interacting with the operating system
import os
# File path
file_path = "c://data//python//contacts.txt"
# Function to save contact
def save_contact():
first_name = entry_first.get()
last_name = entry_last.get()
mobile = entry_mobile.get()
with open(file_path, "a") as file: # "a" append content at the bottom of the file
file.write(f"{first_name},{last_name},{mobile}\n")
# Clear fields
entry_first.delete(0, END)
entry_last.delete(0, END)
entry_mobile.delete(0, END)
# Main window
window = Tk()
window.title("Contact Form")
window.geometry("350x250")
# Labels and Entries
Label(window, text="First Name").pack(pady=5)
entry_first = Entry(window, width=30)
entry_first.pack()
Label(window, text="Last Name").pack(pady=5)
entry_last = Entry(window, width=30)
entry_last.pack()
Label(window, text="Mobile Number").pack(pady=5)
entry_mobile = Entry(window, width=30)
entry_mobile.pack()
# Buttons
Button(window, text="Submit", command=save_contact).pack(pady=10)
window.mainloop()

Read from a TXT file
To display the contents of our file we need to read the TXT file and display it in a window:
There are some new concepts here that I will provide more detail about as time permits:-(
from tkinter import *
# module containing functions for displaying popup dialog boxes
# such as information, warnings, errors, and confirmation prompts.
from tkinter import messagebox
# Provides functions for interacting with the operating system
import os
# -----File path-----
file_path = "c://data//python//contacts.txt"
# -----Ensure directory exists-----
os.makedirs(os.path.dirname(file_path), exist_ok=True)
# -----Function to save contact to our TXT file-----
def save_contact():
first_name = entry_first.get().strip() # .strip() removes spaces (good practice)
last_name = entry_last.get().strip()
mobile = entry_mobile.get().strip()
if not first_name or not last_name or not mobile:
messagebox.showwarning("Input Error", "All fields are required.")
return
with open(file_path, "a") as file:
file.write(f"{first_name},{last_name},{mobile}\n")
messagebox.showinfo("Success", "Contact saved successfully!")
# Clear fields
entry_first.delete(0, END)
entry_last.delete(0, END)
entry_mobile.delete(0, END)
# -----Function to display contacts in a window-----
def display_contacts():
if not os.path.exists(file_path):
messagebox.showinfo("No Data", "No contacts found.")
return
with open(file_path, "r") as file:
records = file.readlines()
if not records:
messagebox.showinfo("No Data", "No contacts found.")
return
display_window = Toplevel(window)
display_window.title("Saved Contacts")
text_area = Text(display_window, width=50, height=15)
text_area.pack(padx=10, pady=10)
for record in records:
first_name, last_name, mobile = record.strip().split(",")
text_area.insert(END, f"First Name: {first_name}\n")
text_area.insert(END, f"Last Name: {last_name}\n")
text_area.insert(END, f"Mobile: {mobile}\n")
text_area.insert(END, "-" * 40 + "\n")
text_area.config(state="disabled")
# -----Main window-----
window = Tk()
window.title("Contact Form")
window.geometry("350x250")
# -----Labels and Entries----
Label(window, text="First Name").pack(pady=5)
entry_first = Entry(window, width=30)
entry_first.pack()
Label(window, text="Last Name").pack(pady=5)
entry_last = Entry(window, width=30)
entry_last.pack()
Label(window, text="Mobile Number").pack(pady=5)
entry_mobile = Entry(window, width=30)
entry_mobile.pack()
# -----Buttons-----
Button(window, text="Submit", command=save_contact).pack(pady=10)
Button(window, text="Display Records", command=display_contacts).pack(pady=5)
window.mainloop()

A better solution to display records in the same window
The tkinter.ttk.Treeview widget in Python is a versatile UI component used to display data either in a hierarchical tree structure (like a file manager) or in a tabular format (like a table with rows and columns). It is part of the themed Tkinter (ttk) module, providing a modern appearance compared to standard Tkinter widgets.
Here’s a short example of how it works:
from tkinter import *
from tkinter import ttk
window = Tk()
window.title("Treeview Table")
# -----Define columns-----
columns = ('#1', '#2', '#3')
tree = ttk.Treeview(window, columns=columns, show='headings')
# -----Define headings-----
tree.heading('#1', text='ID')
tree.heading('#2', text='Name')
tree.heading('#3', text='Salary')
# -----Insert data------
tree.insert('', END, values=('1', 'Denzel', '$30000'))
tree.insert('', END, values=('2', 'Hanna', '$260000'))
tree.pack()
window.mainloop()
Now we’ll use it in out Contact Management program
from tkinter import *
from tkinter import ttk, messagebox
import os
file_path = "c:/data/python/contacts.txt"
os.makedirs(os.path.dirname(file_path), exist_ok=True)
# ---------- File Functions ----------
def load_contacts():
contacts = []
if os.path.exists(file_path):
with open(file_path, "r") as file:
for line in file:
parts = line.strip().split(",")
if len(parts) == 3:
contacts.append(parts)
return contacts
def save_all_contacts(contacts):
with open(file_path, "w") as file:
for contact in contacts:
file.write(",".join(contact) + "\n")
# ---------- GUI Functions ----------
def refresh_tree(): # Clears text out of any form fields
tree.delete(*tree.get_children())
for contact in load_contacts():
tree.insert("", END, values=contact)
def add_contact():
first = entry_first.get().strip()
last = entry_last.get().strip()
mobile = entry_mobile.get().strip()
if not first or not last or not mobile:
messagebox.showwarning("Input Error", "All fields are required.")
return
with open(file_path, "a") as file:
file.write(f"{first},{last},{mobile}\n")
clear_fields()
refresh_tree()
def select_record(event):
selected = tree.focus()
if not selected:
return
values = tree.item(selected, "values")
entry_first.delete(0, END)
entry_last.delete(0, END)
entry_mobile.delete(0, END)
entry_first.insert(0, values[0])
entry_last.insert(0, values[1])
entry_mobile.insert(0, values[2])
def update_contact():
selected = tree.focus()
if not selected:
messagebox.showwarning("Selection Error", "Select a record to edit.")
return
contacts = load_contacts()
index = tree.index(selected)
contacts[index] = [
entry_first.get().strip(),
entry_last.get().strip(),
entry_mobile.get().strip()
]
save_all_contacts(contacts)
clear_fields()
refresh_tree()
def delete_contact():
selected = tree.focus()
if not selected:
messagebox.showwarning("Selection Error", "Select a record to delete.")
return
confirm = messagebox.askyesno("Confirm Delete", "Delete selected contact?")
if not confirm:
return
contacts = load_contacts()
index = tree.index(selected)
contacts.pop(index)
save_all_contacts(contacts)
clear_fields()
refresh_tree()
def clear_fields():
entry_first.delete(0, END)
entry_last.delete(0, END)
entry_mobile.delete(0, END)
# ---------- Main Window ----------
window = Tk()
window.title("Contact Manager")
window.geometry("600x450")
# ---------- Form Frame ----------
form_frame = Frame(window)
form_frame.pack(pady=10)
Label(form_frame, text="First Name").grid(row=0, column=0, padx=5, pady=5)
entry_first = Entry(form_frame)
entry_first.grid(row=0, column=1, padx=5)
Label(form_frame, text="Last Name").grid(row=1, column=0, padx=5, pady=5)
entry_last = Entry(form_frame)
entry_last.grid(row=1, column=1, padx=5)
Label(form_frame, text="Mobile Number").grid(row=2, column=0, padx=5, pady=5)
entry_mobile = Entry(form_frame)
entry_mobile.grid(row=2, column=1, padx=5)
# ---------- Buttons ----------
button_frame = Frame(window)
button_frame.pack(pady=10)
Button(button_frame, text="Add", width=12, command=add_contact).grid(row=0, column=0, padx=5)
Button(button_frame, text="Update", width=12, command=update_contact).grid(row=0, column=1, padx=5)
Button(button_frame, text="Delete", width=12, command=delete_contact).grid(row=0, column=2, padx=5)
Button(button_frame, text="Clear", width=12, command=clear_fields).grid(row=0, column=3, padx=5)
# ---------- Treeview Table ----------
tree_frame = Frame(window)
tree_frame.pack(pady=10, fill="both", expand=True)
scrollbar = Scrollbar(tree_frame)
scrollbar.pack(side=RIGHT, fill=Y)
tree = ttk.Treeview(
tree_frame,
columns=("First Name", "Last Name", "Mobile"),
show="headings",
yscrollcommand=scrollbar.set
)
scrollbar.config(command=tree.yview)
# Once the Treeview is created, you use specific methods to style its columns:
# - .heading(cid, text="Name"): Sets the display text for the header of column
# - .column(cid, width=100, anchor='center'): Sets physical width & text alignment for column cid
tree.heading("First Name", text="First Name")
tree.heading("Last Name", text="Last Name")
tree.heading("Mobile", text="Mobile Number")
tree.column("First Name", width=150)
tree.column("Last Name", width=150)
tree.column("Mobile", width=150)
tree.pack(fill="both", expand=True)
tree.bind("<<TreeviewSelect>>", select_record)
# Load existing records at startup
refresh_tree()
window.mainloop()

Key Improvements from Using Classes
| Feature | Benefit |
|---|---|
ContactManager class | Groups related code into one object |
self.entry_first etc | Widgets become class attributes accessible anywhere in the class |
self.method() | Methods belong to the class |
__init__() | Automatically builds the interface |
| Cleaner structure | Easier to maintain and expand |
Program Structure (Object-Oriented)
ContactManager
│
├── __init__()
│
├── File Methods
│ ├── load_contacts()
│ └── save_all_contacts()
│
├── GUI Creation
│ ├── create_form()
│ ├── create_buttons()
│ └── create_treeview()
│
└── Logic Methods
├── add_contact()
├── update_contact()
├── delete_contact()
├── select_record()
├── refresh_tree()
└── clear_fields()
Here is the code:
import tkinter as tk
from tkinter import ttk, messagebox
import os
class ContactManager:
"""
ContactManager Class
--------------------
This class creates a Tkinter GUI application that allows a user
to add, edit, delete and view contacts stored in a text file.
"""
def __init__(self, window):
"""
Constructor method.
This runs automatically when the class is created.
It builds the GUI and prepares the data file.
"""
self.window = window
self.window.title("Contact Manager")
self.window.geometry("600x450")
# File location for storing contacts
self.file_path = "c:/data/python/contacts.txt"
# Ensure the directory exists
os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
# Build the user interface
self.create_form()
self.create_buttons()
self.create_treeview()
# Load contacts into the table when the program starts
self.refresh_tree()
# ----------------------------------------------------------
# File Handling Methods - CRUD
# Letter Meaning What it does in your program
# C Create Add a new contact
# R Read View or load contacts
# U Update Edit an existing contact
# D Delete Remove a contact
# --------------------------------------------------------
def load_contacts(self):
"""
Reads contacts from the text file and returns them as a list.
Each contact is stored as [first_name, last_name, mobile].
"""
contacts = []
if os.path.exists(self.file_path):
with open(self.file_path, "r") as file:
for line in file:
parts = line.strip().split(",")
# Only accept correctly formatted lines
if len(parts) == 3:
contacts.append(parts)
return contacts
def save_all_contacts(self, contacts):
# Overwrites the contact file with the updated list.
with open(self.file_path, "w") as file:
for contact in contacts:
file.write(",".join(contact) + "\n")
# ----------------------------------------------------------
# GUI Creation Methods
# ----------------------------------------------------------
def create_form(self):
# Creates the entry fields used to input contact information.
form_frame = tk.Frame(self.window)
form_frame.pack(pady=10)
tk.Label(form_frame, text="First Name").grid(row=0, column=0, padx=5, pady=5)
self.entry_first = tk.Entry(form_frame)
self.entry_first.grid(row=0, column=1, padx=5)
tk.Label(form_frame, text="Last Name").grid(row=1, column=0, padx=5, pady=5)
self.entry_last = tk.Entry(form_frame)
self.entry_last.grid(row=1, column=1, padx=5)
tk.Label(form_frame, text="Mobile Number").grid(row=2, column=0, padx=5, pady=5)
self.entry_mobile = tk.Entry(form_frame)
self.entry_mobile.grid(row=2, column=1, padx=5)
def create_buttons(self):
# Creates buttons for Add, Update, Delete and Clear actions.
button_frame = tk.Frame(self.window)
button_frame.pack(pady=10)
tk.Button(button_frame, text="Add", width=12,
command=self.add_contact).grid(row=0, column=0, padx=5)
tk.Button(button_frame, text="Update", width=12,
command=self.update_contact).grid(row=0, column=1, padx=5)
tk.Button(button_frame, text="Delete", width=12,
command=self.delete_contact).grid(row=0, column=2, padx=5)
tk.Button(button_frame, text="Clear", width=12,
command=self.clear_fields).grid(row=0, column=3, padx=5)
def create_treeview(self):
# Creates the Treeview table used to display contacts.
tree_frame = tk.Frame(self.window)
tree_frame.pack(pady=10, fill="both", expand=True)
# Scrollbar for the table
scrollbar = tk.Scrollbar(tree_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Treeview widget
self.tree = ttk.Treeview(
tree_frame,
columns=("First Name", "Last Name", "Mobile"),
show="headings",
yscrollcommand=scrollbar.set
)
scrollbar.config(command=self.tree.yview)
# Column headings
self.tree.heading("First Name", text="First Name")
self.tree.heading("Last Name", text="Last Name")
self.tree.heading("Mobile", text="Mobile Number")
# Column widths
self.tree.column("First Name", width=150)
self.tree.column("Last Name", width=150)
self.tree.column("Mobile", width=150)
self.tree.pack(fill="both", expand=True)
# Bind row selection event
self.tree.bind("<<TreeviewSelect>>", self.select_record)
# ----------------------------------------------------------
# GUI Logic Methods
# ----------------------------------------------------------
def refresh_tree(self):
# Clears the table and reloads all contacts from the file.
self.tree.delete(*self.tree.get_children())
for contact in self.load_contacts():
self.tree.insert("", tk.END, values=contact)
def add_contact(self):
# Adds a new contact to the file and refreshes the table.
first = self.entry_first.get().strip()
last = self.entry_last.get().strip()
mobile = self.entry_mobile.get().strip()
# Validate input fields
if not first or not last or not mobile:
messagebox.showwarning("Input Error", "All fields are required.")
return
# Append new contact to file
with open(self.file_path, "a") as file:
file.write(f"{first},{last},{mobile}\n")
self.clear_fields()
self.refresh_tree()
def select_record(self, event):
"""
When a user clicks a row in the table,
populate the entry fields with that data.
"""
selected = self.tree.focus()
if not selected:
return
values = self.tree.item(selected, "values")
self.clear_fields()
self.entry_first.insert(0, values[0])
self.entry_last.insert(0, values[1])
self.entry_mobile.insert(0, values[2])
def update_contact(self):
# Updates the selected contact in the file.
selected = self.tree.focus()
if not selected:
messagebox.showwarning("Selection Error", "Select a record to edit.")
return
contacts = self.load_contacts()
index = self.tree.index(selected)
# Replace the selected contact with updated values
contacts[index] = [
self.entry_first.get().strip(),
self.entry_last.get().strip(),
self.entry_mobile.get().strip()
]
self.save_all_contacts(contacts)
self.clear_fields()
self.refresh_tree()
def delete_contact(self):
# Deletes the selected contact from the file.
selected = self.tree.focus()
if not selected:
messagebox.showwarning("Selection Error", "Select a record to delete.")
return
confirm = messagebox.askyesno("Confirm Delete", "Delete selected contact?")
if not confirm:
return
contacts = self.load_contacts()
index = self.tree.index(selected)
contacts.pop(index)
self.save_all_contacts(contacts)
self.clear_fields()
self.refresh_tree()
def clear_fields(self):
# Clears all input fields.
self.entry_first.delete(0, tk.END)
self.entry_last.delete(0, tk.END)
self.entry_mobile.delete(0, tk.END)
# ----------------------------------------------------------
# Main Program Entry Point
# ----------------------------------------------------------
if __name__ == "__main__":
window = tk.Tk()
app = ContactManager(window) # Create object of the class
window.mainloop()
Below is a more professional architecture of your program. It introduces several software design improvements:
Improvements
- Two Classes (Separation of Concerns)
ContactRepository→ Handles file storage and data logicContactManagerGUI→ Handles Tkinter interface
@dataclass- A
Contactobject represents each contact record.
- A
- Search
- Filters contacts by first name, last name, or mobile.
- Column Sorting
- Click any column header to sort ascending/descending.
- Cleaner Object-Oriented Design
- GUI code never directly manipulates files.
The structure below is very close to how real desktop applications are designed (Model–View separation).
from tkinter import *
from tkinter import ttk, messagebox
from dataclasses import dataclass
import os
# -----------------------------------------------------------
# DATA MODEL
# -----------------------------------------------------------
@dataclass
class Contact:
"""
Data class representing a contact record.
"""
first: str
last: str
mobile: str
# -----------------------------------------------------------
# DATA REPOSITORY CLASS (Handles file operations)
# -----------------------------------------------------------
class ContactRepository:
"""
Responsible for loading, saving, searching, and modifying contacts.
This class deals ONLY with the data layer.
"""
def __init__(self, file_path):
self.file_path = file_path
os.makedirs(os.path.dirname(file_path), exist_ok=True)
def load_contacts(self):
"""
Reads contacts from file and returns a list of Contact objects.
"""
contacts = []
if os.path.exists(self.file_path):
with open(self.file_path, "r") as file:
for line in file:
parts = line.strip().split(",")
if len(parts) == 3:
contacts.append(Contact(*parts))
return contacts
def save_contacts(self, contacts):
"""
Writes all contacts back to the text file.
"""
with open(self.file_path, "w") as file:
for c in contacts:
file.write(f"{c.first},{c.last},{c.mobile}\n")
def add_contact(self, contact):
"""
Adds a new contact.
"""
contacts = self.load_contacts()
contacts.append(contact)
self.save_contacts(contacts)
def update_contact(self, index, contact):
"""
Updates a contact at a given index.
"""
contacts = self.load_contacts()
if 0 <= index < len(contacts):
contacts[index] = contact
self.save_contacts(contacts)
def delete_contact(self, index):
"""
Deletes a contact by index.
"""
contacts = self.load_contacts()
if 0 <= index < len(contacts):
contacts.pop(index)
self.save_contacts(contacts)
def search(self, term):
"""
Searches contacts by name or mobile number.
"""
term = term.lower()
results = []
for c in self.load_contacts():
if (term in c.first.lower() or
term in c.last.lower() or
term in c.mobile):
results.append(c)
return results
# -----------------------------------------------------------
# GUI CLASS
# -----------------------------------------------------------
class ContactManagerGUI:
"""
Handles the Tkinter interface and user interactions.
"""
def __init__(self, window, repository):
self.window = window
self.repo = repository
self.sort_reverse = False
window.title("Professional Contact Manager")
window.geometry("700x500")
self.create_search_bar()
self.create_form()
self.create_buttons()
self.create_treeview()
self.refresh_tree()
# -------------------------------------------------------
# GUI BUILDING
# -------------------------------------------------------
def create_search_bar(self):
frame = Frame(self.window)
frame.pack(pady=5)
Label(frame, text="Search").pack(side=LEFT)
self.search_entry = Entry(frame, width=30)
self.search_entry.pack(side=LEFT, padx=5)
Button(frame, text="Search",
command=self.search_contacts).pack(side=LEFT)
Button(frame, text="Show All",
command=self.refresh_tree).pack(side=LEFT, padx=5)
def create_form(self):
frame = Frame(self.window)
frame.pack(pady=10)
Label(frame, text="First Name").grid(row=0, column=0, padx=5)
self.first_entry = Entry(frame)
self.first_entry.grid(row=0, column=1)
Label(frame, text="Last Name").grid(row=1, column=0, padx=5)
self.last_entry = Entry(frame)
self.last_entry.grid(row=1, column=1)
Label(frame, text="Mobile").grid(row=2, column=0, padx=5)
self.mobile_entry = Entry(frame)
self.mobile_entry.grid(row=2, column=1)
def create_buttons(self):
frame = Frame(self.window)
frame.pack(pady=10)
Button(frame, text="Add", width=12,
command=self.add_contact).grid(row=0, column=0, padx=5)
Button(frame, text="Update", width=12,
command=self.update_contact).grid(row=0, column=1, padx=5)
Button(frame, text="Delete", width=12,
command=self.delete_contact).grid(row=0, column=2, padx=5)
Button(frame, text="Clear", width=12,
command=self.clear_fields).grid(row=0, column=3, padx=5)
def create_treeview(self):
frame = Frame(self.window)
frame.pack(fill="both", expand=True, pady=10)
scrollbar = Scrollbar(frame)
scrollbar.pack(side=RIGHT, fill=Y)
self.tree = ttk.Treeview(
frame,
columns=("first", "last", "mobile"),
show="headings",
yscrollcommand=scrollbar.set
)
scrollbar.config(command=self.tree.yview)
self.tree.heading("first", text="First Name",
command=lambda: self.sort_column("first"))
self.tree.heading("last", text="Last Name",
command=lambda: self.sort_column("last"))
self.tree.heading("mobile", text="Mobile",
command=lambda: self.sort_column("mobile"))
self.tree.pack(fill="both", expand=True)
self.tree.bind("<<TreeviewSelect>>", self.select_record)
# -------------------------------------------------------
# DATA DISPLAY
# -------------------------------------------------------
def refresh_tree(self):
self.tree.delete(*self.tree.get_children())
for contact in self.repo.load_contacts():
self.tree.insert("", END,
values=(contact.first,
contact.last,
contact.mobile))
def search_contacts(self):
term = self.search_entry.get()
self.tree.delete(*self.tree.get_children())
for c in self.repo.search(term):
self.tree.insert("", END,
values=(c.first, c.last, c.mobile))
# -------------------------------------------------------
# SORTING
# -------------------------------------------------------
def sort_column(self, column):
data = [(self.tree.set(child, column), child)
for child in self.tree.get_children("")]
data.sort(reverse=self.sort_reverse)
for index, (_, child) in enumerate(data):
self.tree.move(child, "", index)
self.sort_reverse = not self.sort_reverse
# -------------------------------------------------------
# CRUD OPERATIONS
# Letter Meaning What it does in your program
# C Create Add a new contact
# R Read View or load contacts
# U Update Edit an existing contact
# D Delete Remove a contact
# --------------------------------------------------------
def add_contact(self):
contact = Contact(
self.first_entry.get(),
self.last_entry.get(),
self.mobile_entry.get()
)
if not contact.first or not contact.last or not contact.mobile:
messagebox.showwarning("Error", "All fields required")
return
self.repo.add_contact(contact)
self.refresh_tree()
self.clear_fields()
def update_contact(self):
selected = self.tree.focus()
if not selected:
messagebox.showwarning("Error", "Select a contact")
return
index = self.tree.index(selected)
contact = Contact(
self.first_entry.get(),
self.last_entry.get(),
self.mobile_entry.get()
)
self.repo.update_contact(index, contact)
self.refresh_tree()
def delete_contact(self):
selected = self.tree.focus()
if not selected:
messagebox.showwarning("Error", "Select a contact")
return
confirm = messagebox.askyesno("Confirm", "Delete contact?")
if confirm:
index = self.tree.index(selected)
self.repo.delete_contact(index)
self.refresh_tree()
# -------------------------------------------------------
# FORM UTILITIES
# -------------------------------------------------------
def select_record(self, event):
selected = self.tree.focus()
if not selected:
return
values = self.tree.item(selected, "values")
self.clear_fields()
self.first_entry.insert(0, values[0])
self.last_entry.insert(0, values[1])
self.mobile_entry.insert(0, values[2])
def clear_fields(self):
self.first_entry.delete(0, END)
self.last_entry.delete(0, END)
self.mobile_entry.delete(0, END)
# -----------------------------------------------------------
# MAIN PROGRAM
# -----------------------------------------------------------
if __name__ == "__main__":
FILE_PATH = "c:/data/python/contacts.txt"
window = Tk()
repository = ContactRepository(FILE_PATH)
app = ContactManagerGUI(window, repository)
window.mainloop()
Outputs:

Architecture Diagram
ContactManagerGUI
│
│ uses
▼
ContactRepository
│
│ stores
▼
contacts.txt
Contact (dataclass)
├─ first
├─ last
└─ mobile
Why This Is More Professional
| Feature | Benefit |
|---|---|
Contact dataclass | Clear structured data model |
ContactRepository | Data layer separated from UI |
ContactManagerGUI | GUI logic isolated |
| Search function | Real usability improvement |
| Column sorting | More interactive UI |
| OOP design | Easier to maintain and scale |
To finish this VERY LONG module, here is a near commercial version introducing features you’d typically see in a production-quality desktop tool.
New Features Added
✔ Live Search – filters as you type
✔ Phone Number Validation – regex validation for mobile numbers
✔ Duplicate Detection – prevents identical contacts
✔ Column Auto-Width + Resizable Columns
✔ Efficient Loading – supports thousands of records smoothly
✔ Export to CSV – for Excel or data sharing
✔ Cleaner architecture (Model → Repository → GUI)
from tkinter import *
from tkinter import ttk, messagebox, filedialog
from dataclasses import dataclass
import os
import csv
import re
# ------------------------------------------------------------
# DATA MODEL
# ------------------------------------------------------------
@dataclass
class Contact:
first: str
last: str
mobile: str
# ------------------------------------------------------------
# DATA LAYER
# ------------------------------------------------------------
class ContactRepository:
def __init__(self, file_path):
self.file_path = file_path
os.makedirs(os.path.dirname(file_path), exist_ok=True)
# -----------------------------
# LOAD CONTACTS
# -----------------------------
def load_contacts(self):
contacts = []
if os.path.exists(self.file_path):
with open(self.file_path, "r") as file:
for line in file:
parts = line.strip().split(",")
if len(parts) == 3:
contacts.append(Contact(*parts))
return contacts
# -----------------------------
# SAVE CONTACTS
# -----------------------------
def save_contacts(self, contacts):
with open(self.file_path, "w") as file:
for c in contacts:
file.write(f"{c.first},{c.last},{c.mobile}\n")
# -----------------------------
# ADD CONTACT
# -----------------------------
def add_contact(self, contact):
contacts = self.load_contacts()
# Duplicate detection
for c in contacts:
if (c.first == contact.first and
c.last == contact.last and
c.mobile == contact.mobile):
return False
contacts.append(contact)
self.save_contacts(contacts)
return True
# -----------------------------
def update_contact(self, index, contact):
contacts = self.load_contacts()
if 0 <= index < len(contacts):
contacts[index] = contact
self.save_contacts(contacts)
# -----------------------------
def delete_contact(self, index):
contacts = self.load_contacts()
if 0 <= index < len(contacts):
contacts.pop(index)
self.save_contacts(contacts)
# ------------------------------------------------------------
# GUI LAYER
# ------------------------------------------------------------
class ContactManagerGUI:
PHONE_REGEX = re.compile(r"^[0-9+\-\s]{8,15}$")
def __init__(self, window, repo):
self.window = window
self.repo = repo
self.contacts_cache = []
window.title("Advanced Contact Manager")
window.geometry("750x520")
self.create_search_bar()
self.create_form()
self.create_buttons()
self.create_tree()
self.load_contacts()
# --------------------------------------------------------
# SEARCH BAR (LIVE SEARCH)
# --------------------------------------------------------
def create_search_bar(self):
frame = Frame(self.window)
frame.pack(pady=5)
Label(frame, text="Search").pack(side=LEFT)
self.search_var = StringVar()
entry = Entry(frame, textvariable=self.search_var, width=30)
entry.pack(side=LEFT, padx=5)
# Live search trigger
self.search_var.trace_add("write", self.live_search)
Button(frame, text="Export CSV",
command=self.export_csv).pack(side=LEFT, padx=5)
# --------------------------------------------------------
# FORM
# --------------------------------------------------------
def create_form(self):
frame = Frame(self.window)
frame.pack(pady=10)
Label(frame, text="First Name").grid(row=0, column=0)
self.first_entry = Entry(frame)
self.first_entry.grid(row=0, column=1)
Label(frame, text="Last Name").grid(row=1, column=0)
self.last_entry = Entry(frame)
self.last_entry.grid(row=1, column=1)
Label(frame, text="Mobile").grid(row=2, column=0)
self.mobile_entry = Entry(frame)
self.mobile_entry.grid(row=2, column=1)
# --------------------------------------------------------
# BUTTONS
# --------------------------------------------------------
def create_buttons(self):
frame = Frame(self.window)
frame.pack(pady=10)
Button(frame, text="Add", width=12,
command=self.add_contact).grid(row=0, column=0, padx=5)
Button(frame, text="Update", width=12,
command=self.update_contact).grid(row=0, column=1, padx=5)
Button(frame, text="Delete", width=12,
command=self.delete_contact).grid(row=0, column=2, padx=5)
Button(frame, text="Clear", width=12,
command=self.clear_fields).grid(row=0, column=3, padx=5)
# --------------------------------------------------------
# TREEVIEW
# --------------------------------------------------------
def create_tree(self):
frame = Frame(self.window)
frame.pack(fill="both", expand=True)
self.tree = ttk.Treeview(
frame,
columns=("first", "last", "mobile"),
show="headings"
)
self.tree.heading("first", text="First Name")
self.tree.heading("last", text="Last Name")
self.tree.heading("mobile", text="Mobile")
self.tree.pack(fill="both", expand=True)
scrollbar = ttk.Scrollbar(frame, command=self.tree.yview)
scrollbar.pack(side=RIGHT, fill=Y)
self.tree.config(yscrollcommand=scrollbar.set)
self.tree.bind("<<TreeviewSelect>>", self.select_record)
# --------------------------------------------------------
# LOAD CONTACTS (Efficient)
# --------------------------------------------------------
def load_contacts(self):
self.contacts_cache = self.repo.load_contacts()
self.refresh_tree(self.contacts_cache)
# --------------------------------------------------------
def refresh_tree(self, contacts):
self.tree.delete(*self.tree.get_children())
for c in contacts:
self.tree.insert("", END,
values=(c.first, c.last, c.mobile))
self.auto_resize_columns()
# --------------------------------------------------------
# AUTO RESIZE COLUMNS
# --------------------------------------------------------
def auto_resize_columns(self):
for col in self.tree["columns"]:
max_len = len(col)
for item in self.tree.get_children():
value = str(self.tree.set(item, col))
max_len = max(max_len, len(value))
self.tree.column(col, width=max_len * 10)
# --------------------------------------------------------
# LIVE SEARCH
# --------------------------------------------------------
def live_search(self, *args):
term = self.search_var.get().lower()
filtered = []
for c in self.contacts_cache:
if (term in c.first.lower() or
term in c.last.lower() or
term in c.mobile):
filtered.append(c)
self.refresh_tree(filtered)
# --------------------------------------------------------
# VALIDATION
# --------------------------------------------------------
def validate_phone(self, phone):
return bool(self.PHONE_REGEX.match(phone))
# --------------------------------------------------------
# CRUD
# Letter Meaning What it does in your program
# C Create Add a new contact
# R Read View or load contacts
# U Update Edit an existing contact
# D Delete Remove a contact
# --------------------------------------------------------
def add_contact(self):
first = self.first_entry.get().strip()
last = self.last_entry.get().strip()
mobile = self.mobile_entry.get().strip()
if not first or not last or not mobile:
messagebox.showwarning("Error", "All fields required")
return
if not self.validate_phone(mobile):
messagebox.showwarning("Invalid Phone",
"Phone number format invalid")
return
success = self.repo.add_contact(Contact(first, last, mobile))
if not success:
messagebox.showwarning("Duplicate", "Contact already exists")
return
self.load_contacts()
self.clear_fields()
# --------------------------------------------------------
def update_contact(self):
selected = self.tree.focus()
if not selected:
return
index = self.tree.index(selected)
contact = Contact(
self.first_entry.get(),
self.last_entry.get(),
self.mobile_entry.get()
)
self.repo.update_contact(index, contact)
self.load_contacts()
# --------------------------------------------------------
def delete_contact(self):
selected = self.tree.focus()
if not selected:
return
confirm = messagebox.askyesno("Confirm", "Delete contact?")
if confirm:
index = self.tree.index(selected)
self.repo.delete_contact(index)
self.load_contacts()
# --------------------------------------------------------
def select_record(self, event):
selected = self.tree.focus()
if not selected:
return
values = self.tree.item(selected, "values")
self.clear_fields()
self.first_entry.insert(0, values[0])
self.last_entry.insert(0, values[1])
self.mobile_entry.insert(0, values[2])
# --------------------------------------------------------
def clear_fields(self):
self.first_entry.delete(0, END)
self.last_entry.delete(0, END)
self.mobile_entry.delete(0, END)
# --------------------------------------------------------
# EXPORT CSV
# --------------------------------------------------------
def export_csv(self):
path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV Files", "*.csv")]
)
if not path:
return
contacts = self.repo.load_contacts()
with open(path, "w", newline="") as file:
writer = csv.writer(file)
writer.writerow(["First Name", "Last Name", "Mobile"])
for c in contacts:
writer.writerow([c.first, c.last, c.mobile])
messagebox.showinfo("Export Complete", "Contacts exported successfully")
# ------------------------------------------------------------
# MAIN PROGRAM
# ------------------------------------------------------------
if __name__ == "__main__":
FILE = "c:/data/python/contacts.txt"
window = Tk()
repo = ContactRepository(FILE)
app = ContactManagerGUI(window, repo)
window.mainloop()
Architecture (Senior-Level)
GUI Layer
┌────────────────────────┐
│ ContactManagerGUI │
└────────────┬───────────┘
│
▼
Data Access Layer
┌────────────────────────┐
│ ContactRepository │
└────────────┬───────────┘
│
▼
contacts.txt
Data Model
────────────
Contact (dataclass)
- first
- last
- mobile
Why This Version Handles 10,000+ Contacts Better
| Feature | Benefit |
|---|---|
contacts_cache | avoids repeated file reads |
| Live search uses cache | extremely fast filtering |
| Single tree refresh | efficient UI updates |
| auto column sizing | adapts to long values |
| repository layer | file operations isolated |
Output:
