New to Python-- help with Tkinter Phonebook GUI

Hi, it’s my first time posting here. I am new to Python and I have an assignment where we need to make a Phonebook with GUI. This is my code so far, but I cannot figure out how to make the search and sort option work.

Can anyone help? Thank you in advance.


from tkinter import *
root = Tk()
root.geometry('600x400')
root.title ('Guardian Directory')
root.config (bg = 'light grey')
root.resizable (0,0)


GuardianDirectory = [
    ['Drew', 'DeLeon', '4567891', 'address4', 'drew@deleon.com'],
    ['Apple', 'Appleby', '1234567', 'address1', 'apple@appleby.com'],
    ]

FirstName = StringVar()
LastName = StringVar()
ContactNumber = StringVar()
Address = StringVar()
Email = StringVar()

frame = Frame(root)
frame.pack (side = RIGHT)

scroll = Scrollbar(frame, orient = VERTICAL)
select = Listbox (frame, yscrollcommand = scroll.set, height =15,)
scroll.config (command = select.yview)
scroll.pack(side = RIGHT, fill = Y)
select.pack(side = LEFT, fill = BOTH, expand = 1)


def selected():
    return int(select.curselection()[0])

def add():
    GuardianDirectory.append([FirstName.get(), LastName.get(),
    ContactNumber.get(), Address.get(), Email.get()])
    selectset()
    print(f"Contact added.")

def delete ():
    del GuardianDirectory[selected()]
    selectset()

def search():
    FirstName = FirstName_entry.get()
    if FirstName in GuardianDirectory:
        ContactNumber.delete(0,tk.END)
        ContactNumber.insert(0,GuardianDirectory[FirstName])
    else:
        ContactNumber.delete(0, tk.END)

def view():
    FIRSTNAME, LASTNAME, CONTACTNNUMBER, ADDRESS, EMAIL = GuardianDirectory[Selected()]
    FirstName.set(FIRSTNAME)
    LastName.set(LASTNAME)
    ContactNumber.set(CONTACTNNUMBER)
    Address.set(ADDRESS)
    Email.set(EMAIL)
def selectset():
    GuardianDirectory.sort()
    select.delete (0,END)
    for FirstName, LastName, ContactNumber, Address, Email in GuardianDirectory:
        select.insert (END, FirstName)
selectset()

def search():
    GuardianDirectory.search()
    FirstName.get()

def exit():
    root.destroy()

Label(root, text = 'First Name', font = 'arial 12 bold', bg = 'white') .place (x=30, y=20)
Entry(root, textvariable = FirstName).place (x=130, y=20)

Label(root, text = 'Last Name', font = 'arial 12 bold', bg = 'white') .place (x=30, y=70)
Entry(root, textvariable = LastName).place (x=130, y=70)

Label(root, text = 'Contact Number', font = 'arial 12 bold', bg = 'white') .place (x=30, y=120)
Entry(root, textvariable = ContactNumber).place (x=130, y=120)

Label(root, text = 'Address', font = 'arial 12 bold', bg = 'white') .place (x=30, y=170)
Entry(root, textvariable = Address).place (x=130, y=170)

Label(root, text = 'Email', font = 'arial 12 bold', bg = 'white') .place (x=30, y=220)
Entry(root, textvariable = Email).place (x=130, y=220)

Button(root, text = 'Add Contact', font = 'arial 12 bold', bg = 'white', command = add).place(x=70,y=280)
Button(root, text = 'Delete Contact', font = 'arial 12 bold', bg = 'white', command = delete).place(x=200, y=280)
Button(root, text = 'Search Contact', font = 'arial 12 bold', bg = 'white', command = view).place(x=70, y=330)
Button(root, text = 'Sort Contact', font = 'arial 12 bold', bg = 'white', command = selectset).place(x=200, y=330)
Button(root, text = 'Exit', font = 'arial 12 bold', bg = 'grey', command = exit).place(x=470, y=330)

root.mainloop()

I’m a little pushed for time right now, so I’ll simply post a couple of observations:

  1. You seem to have two functions that are named search()
  2. It may be a better option to use a dictionary object for the GuardianDirectory, but I’m not saying that you’re wrong to use a nested list
  3. I am (on the other hand) saying that you’re wrong the use a wildcard import from tkinter

There’s a couple of other things that I see, but I’ll reserve comments for now, as I could be missing something: I’ve only had time to have a brief look at your script.

To add: being “new to Python”, it seems (to me) that you’ve been given an advanced assignment. I’d not expect that someone with limited Python skills (no offense intended; it’s simply a reference to the “new to Python” part of your post) to have been given this kind of a task. So I think that you’ve done a very good job to have got as far as you have with this: well done :slight_smile:

I wouldn’t go that far. Provided it’s the first import, it’s a
perfectly sane thing to do for a module/programme based primarily around
that library (tkinter in this case). It’s certainly an effective way
to get access to all the public names from it.

It won’t be breaking the OP’s programme.

@ztap

I think that the main issue you’re having with this is to do with the first line of the view() function: you’re trying to call the selected() function, but you have [Selected()] in your code: note the uppercase S, which should be lowercase.

Apart from what @Rob wrote:

GuardianDirectory is a list of lists. Lists don’t have a search method.

FirstName in GuardianDirectory won’t work because FirstName contains a string and GuardianDirectory doesn’t contain strings, it contains lists.

FirstName = FirstName_entry.get() won’t work because you don’t define FirstName_entry anywhere.

Hi, I was going through your Tkinter app and its a nice for a start. First of you have duplicate search functionalities. Another thing you have assigned the result of your search query to the first name, which in this case is counterproductive as it overwrites the value of the firstname. So we will store the result of our search query to a search result variable but you can rename feel freely. I’ve addes a clear field function to clear everytime you add…
I’ve also commented out the dictionary way of doing it as @rob42 was pointing out but its too much work you’ll have to do an overhaul but if you ahve time play around with it… as for sorting I see it sorts itself automatically… nywy try the search functionality and good luck

import sqlite3
from logging import Logger
from tkinter import *
root = Tk()
root.geometry('600x400')
root.title ('Guardian Directory')
root.config (bg = 'light grey')
root.resizable (0,0)     


GuardianDirectory = [
    ['Drew', 'DeLeon', '4567891', 'address4', 'drew@deleon.com'],
    ['Apple', 'Appleby', '1234567', 'address1', 'apple@appleby.com'],
    ]

#================== dictionary method to store contacts
# GuardianDirectory = {
#     'Drew': 
#             { 
#             'LastName': 'Deleon',
#             'Contact' :  '4567891',
#             'Address' :  'address4',
#             'Email' : 'drew@deleon.com'

#             },
#     'Apple': 
#             {
#             'Last Name': 'Appleby',
#             'Contact Number': '1234567',
#             'Address': 'address1',
#             'Email': 'apple@appleby.com'
#            }   
# }

FirstName = StringVar()
LastName = StringVar()
ContactNumber = StringVar()
Address = StringVar()
Email = StringVar()

frame = Frame(root)
frame.pack (side = RIGHT)

scroll = Scrollbar(frame, orient = VERTICAL)
select = Listbox (frame, yscrollcommand = scroll.set, height =15,)
scroll.config (command = select.yview)
scroll.pack(side = RIGHT, fill = Y)
select.pack(side = LEFT, fill = BOTH, expand = 1)


def selected():
    return int(select.curselection()[0])

def add():
    GuardianDirectory.append([FirstName.get(), LastName.get(), ContactNumber.get(), Address.get(), Email.get()])
    selectset()
    print("Contact added.")
    clear_fields()

# ======= adding dictionary method
# def add():
#     first_name = FirstName.get()
#     last_name = LastName.get()
#     # feel free to add more fields

#     GuardianDirectory[first_name] = {
#         'LastName': last_name,
#     }
#     selectset()
#     print("Contact added.")
#     clear_fields()


def delete():
    try:
        del GuardianDirectory[selected()]
        selectset()
        clear_fields()
        print("Contact deleted.")
    except IndexError:
        print("No contact selected for deletion.")

def view():
    try:
        FIRSTNAME, LASTNAME, CONTACTNNUMBER, ADDRESS, EMAIL = GuardianDirectory[selected()]
        FirstName.set(FIRSTNAME)
        LastName.set(LASTNAME)
        ContactNumber.set(CONTACTNNUMBER)
        Address.set(ADDRESS)
        Email.set(EMAIL)
    except IndexError:
        print("No contact selected for viewing.")

# function to clear input fields
def clear_fields():
    FirstName.set("")
    LastName.set("")
    ContactNumber.set("")
    Address.set("")
    Email.set("")
    
def selectset():
    GuardianDirectory.sort()
    select.delete (0,END)
    for FirstName, LastName, ContactNumber, Address, Email in GuardianDirectory:
        select.insert (END, FirstName)

# # ======== select for dictionary
# def selectset():
#     sorted_names = sorted(GuardianDirectory.keys())
#     select.delete(0, END)
#     for name in sorted_names:
#         select.insert(END, name)

selectset()

# search for a specified contact
def search():
    search_result = FirstName.get() # searching the contact based on the first name and storing the search result 
    for contact in GuardianDirectory:
        if search_result == contact[0]:  # since names are in the first positon, we are geetting items pos 0, feel free to change to variable you want to search with
            print(f'{search_result} found') 

            # if contact is prsent we start retrieving and setting the variable to the associated values 
            FirstName.set(contact[0])
            LastName.set(contact[1])    # lastname is 2nd position in the lsit recall we are using index positioning
            ContactNumber.set(contact[2])
            # add the rest of the fields
            return
    clear_fields()
    print(f'{search_result} Not found')

        

def exit():
    root.destroy()

Label(root, text = 'First Name', font = 'arial 12 bold', bg = 'white') .place (x=30, y=20)
Entry(root, textvariable = FirstName).place (x=130, y=20)

Label(root, text = 'Last Name', font = 'arial 12 bold', bg = 'white') .place (x=30, y=70)
Entry(root, textvariable = LastName).place (x=130, y=70)

Label(root, text = 'Contact Number', font = 'arial 12 bold', bg = 'white') .place (x=30, y=120)
Entry(root, textvariable = ContactNumber).place (x=130, y=120)

Label(root, text = 'Address', font = 'arial 12 bold', bg = 'white') .place (x=30, y=170)
Entry(root, textvariable = Address).place (x=130, y=170)

Label(root, text = 'Email', font = 'arial 12 bold', bg = 'white') .place (x=30, y=220)
Entry(root, textvariable = Email).place (x=130, y=220)

Button(root, text = 'Add Contact', font = 'arial 12 bold', bg = 'white', command = add).place(x=70,y=280)
Button(root, text = 'Delete Contact', font = 'arial 12 bold', bg = 'white', command = delete).place(x=200, y=280)
Button(root, text = 'Search Contact', font = 'arial 12 bold', bg = 'white', command = search).place(x=70, y=330)
Button(root, text = 'View Contact', font = 'arial 12 bold', bg = 'white', command = view).place(x=70, y=380)
Button(root, text = 'Reset', font = 'arial 12 bold', bg = 'white', command = clear_fields).place(x=280, y=350)
Button(root, text = 'Sort Contact', font = 'arial 12 bold', bg = 'white', command = selectset).place(x=200, y=330)
Button(root, text = 'Exit', font = 'arial 12 bold', bg = 'grey', command = exit).place(x=470, y=330)

root.mainloop()
1 Like

Hi Rob,

Thank you for taking the time to look through my code despite being pressed for time earlier.

  1. Oops, I know I tried two different ways of making a search function since I was desperate in trying to make it work.
  2. I am honestly unaware of the difference, thanks for pointing this out. I will read up on this some more.
  3. When I looked up what GUI to use, I read tkinter was the most common one. This is why I used it.
  4. Re: the selected() function in your next comment, this is noted.

Thank you for all your help!
And thank you so much for your encouraging words. I’ve been feeling so stupid trying to figure everything out on my own, so that’s very nice of you to say. Lucky to have stumbled upon these forums to learn from experts like you.

Have a good day, Rob.

1 Like

Thanks for your comment, Cameron.

Hi Matthew,

Thanks for letting me know that lists don’t have a search method. Now I know that this is where the problem lies… I will try to figure out how to mend this problem.

Have a great day!

Hi Kyle,

Your kind words mean a lot. Thank you!

And thank you as well for taking time to explain in detail where my mistakes lie in this code.
-Sorry, yes I did make duplicate search functionalities.
-I will try to figure out how to store the result of the search query as a variable.
-Adding a clear field is a smart suggestion.
-Right, it does sort automatically. Is there a way to make the user select sort, and actually execute the process, instead of it being an automatic function? I am not sure if I am making sense.
-The search function finally works!

Thank you so much you have been such a big help. Really appreciate it, Kyle.
Have a good day!

Hey yeah it totally makes sense…but I thought I put everything in there did you check?.. like I said some I have commented out for you to try and play about with it later… especially the dictionary one(I’ve commented using ====== just remember if you enable the dictionary functions to use you must disable the methods associated with a list)… nywho here is the code for sorting

# sorting function
def sorting():
    
    GuardianDirectory.sort(reverse=True)  # Sort the list in descending order
    select.delete(0, END)  # Clear the Listbox
    for FirstName, LastName, ContactNumber, Address, Email in GuardianDirectory:
        select.insert(END, FirstName)  # Insert the sorted contacts
    print("Contacts sorted in descending order.")

yeah it sorts automatically coz recall everytime the selectset() is called either when you start the app and add new contacts… check that function and you’ll see it the sort method is called.

so another thing recall I imported the sqlite database at the top you can store the contacst there to make the app more dynamic but thats a whole another route… if interested I can give you the code for another desktop app I wrote a while back but am using PyQt5… but its not hard to follow…

Hi Kyle,

Thanks for your reply and thanks for your further clarifications.

With my low skill level, I do need to further play with the code that you sent. But yes, I did see your comments. I’m just a slow a learner. But your code comments are very clear and concise. They are such good guidance as I study the code! I will keep working on this in the next few days.

So far, I feel like the list method works better for me since there is less overhaul. But I want to try comparing it with the dictionary functions and method as well.

Yeah sure, if you don’t mind sharing it, please do. It might be something I can practice with/learn from down the line, as I continue studying computer science. I am not familiar with PyQt5 yet, but it might be something I will encounter in the future.

Also, how would you like me to credit you in my assignment? Since you have been such a big help with the coding part. Kindly let me know how you want to be referenced/acknowledged.

Thank you!

Hey yeah take it easy but not so easy… don’t rush just learn function by function the usual (CRUD) adding, deleting, viewing and updating. The other searching and even exporting the contacts as CSV/PDF come later.

Yeah PyQt5 is just similar to Tkinter both are just frameworks to create GUI apps. The approach is still the same with just a slight difference but its easier to create prettier and customized with PyQt5… so I’ll look for it… it can be overwhelming but step by step you’ll get the hang of it cheers.

Hey PyQt5 is just an alternative to Tkinter for creating GUIs and there are others… so I found the source code and put it on github… GitHub - michael305y/Desktop_payroll_app
so its a little overwhelming and you have to use it first to get the hang of it. Just run it and see how the app looks and works first. But I find PyQt5 way more customizable and easy to create prettier apps. The logics of CRUD are the same just replace the variables with your variables. Creating the labels, btns, text boxes is similar with a slight syntax. At your own pace, function by function then you’ll get the hang of it.
This is for Tkinter much easier… just focus on adding customers then tailor to your app by changing the name of the fields and variables.

import sqlite3
import tkinter as tk
from tkinter import ttk
import pandas as pd

def connect():
    pass
    

# Create the Tkinter GUI
root = tk.Tk()
root.title("Payments")
root.geometry("500x500")

# #Create a listbox to display the data
# listbox = tk.Listbox(root)
# listbox.grid(row=15, column=0, padx=5, columnspan=6)

# #Creating a treeview 1
# tree = ttk.Treeview(root, columns=("AdmNo", "Name", "Class"))
# tree.heading("#0", text="AdmNO")
# tree.heading("#1", text="Name")
# tree.heading("#2", text="Class")

# tree.column("#0", minwidth=15, width=50, stretch=tk.NO)
# tree.column("#1", minwidth=15, width=50, stretch=tk.YES)
# tree.column("#2", minwidth=15, width=50, stretch=tk.NO)
# tree.grid(row=8)

# #creating a frame
# frame = tk.LabelFrame(root, width=50)
# scrollbar = tk.Scrollbar(frame)

# #creating treeview2
# style = ttk.Style()  #adding style
# style.theme_use('default')

# #configure treeview colours
# style.configure("Treeview",
#     background="#D3D3D3",
#     foreground="black",
#     rowheight=25,
#     fieldbackground="#D3D3D3")
# style.map('Treeview',
#    background=[('selected', "#347083")])

# # creating treeview frame
# tree_frame = frame(root)
# tree_frame.grid()

# #creating tree scrollbar
# tree_scroll = scrollbar(tree_frame)
# tree_scroll.pack(side="right", fill="y")

# #creating our treeview
# my_tree = ttk.Treeview(tree_frame, yscrollcommand=tree_scroll.set, selectmode="extended")
# my_tree.grid()

# #configure scrollbar
# tree_scroll.config(command=my_tree.yview)

# #define columns
# my_tree['columns'] = ("AdmNo", "Name", "Class")

# #Format columns
# my_tree.column("#0", width=0)
# my_tree.column("AdmNo", anchor="center", width=50)
# my_tree.column("Name", anchor="w", width=140)
# my_tree.column("Class", anchor="center", width=0)

# #column headings
# my_tree.heading("#0", text="", anchor="w")
# my_tree.heading("AdmNo", text="AdmNo", anchor="center")
# my_tree.heading("Name", text="Name", anchor="w")
# my_tree.heading("Class", text="Class", anchor="w")

# Create a Treeview widget to display the data
tree = ttk.Treeview(root, columns=("AdmNo", "Name", "Class"))
tree.heading("#0", text="AdmNo")
tree.heading("#1", text="Name")
tree.heading("#2", text="Class")
tree.column("#0", minwidth=0, width=100, stretch=tk.NO)
tree.column("#1", minwidth=0, width=100, stretch=tk.NO)
tree.column("#2", minwidth=0, width=100, stretch=tk.NO)
tree.grid()

# Place the Treeview widget at the bottom of the window
tree.grid(row=5, column=0, columnspan=2, sticky="nsew")
root.rowconfigure(1, weight=1)
root.columnconfigure(0, weight=1)

#Creating  textboxes
AdmNo = tk.Entry(root, width=30)
AdmNo.grid(row=0, column=1, padx=30)

Name = tk.Entry(root, width=30)
Name.grid(row=1, column=1)

Class = tk.Entry(root, width=30)
Class.grid(row=2, column=1)

#Creating  labels
AdmNo_label = tk.Label(root, text="Admission Number")
AdmNo_label.grid(row=0, column=0)

Name_label = tk.Label(root, text="Name")
Name_label.grid(row=1, column=0)

Class_label = tk.Label(root, text="Class")
Class_label.grid(row=2, column=0)

# Gets the value entered by the user in the textboxes
#====== here replace with your fields and you can add more
AdmNo_val = AdmNo.get()  # e.g. Phone Number
Name_val = Name.get()   # Furst name name
Class_val = Class.get()

textboxes = [AdmNo, Name, Class]

def connectAndAdd():
    # Connects to the database and adds  data to the database
    conn = sqlite3.connect('Payments.db')
    cursor = conn.cursor()  # creating a cursor object to execute commands

    # Create a table in the database
    cursor.execute('''CREATE TABLE IF NOT EXISTS Payments (AdmNo INT PRIMARY KEY, Name TEXT, Class INT)''')
    # =====cursor.execute('''CREATE TABLE IF NOT EXISTS Guardian_Contacts (Phone_No INT PRIMARY KEY, First_Name TEXT, Last_name TEXT)''')

    # Prepared insert stmt
    insert_query = "INSERT INTO Payments VALUES (?,?,?)"
    #====== insert_query = "INSERT INTO Guardian_Directory VALUES (?,?,?)" # this will depend with number of fields you have

    # Inserting data into table
    cursor.execute(insert_query, (AdmNo.get(), Name.get(), Class.get()))
    #====== replace with your variables
    # cursor.execute(insert_query, (Phone_No.get(), First_Name.get(), Class.get()))

    conn.commit()   #commits any chages made

    cursor.close()
    conn.close()   #closes the DB connection

# Function to submit the data
def submit():
    connectAndAdd()
    
    print("Data submitted")
    
    #this clears data in evry tetxboxes after submitting data
    for tb in textboxes:
        tb.delete(0, "end")

def query():
    # Connects to the database and adds  data to the database
    conn = sqlite3.connect('Payments.db')
    cursor = conn.cursor()  # creating a cursor object to execute commands

    # Fetch all records from the table
    cursor.execute("SELECT * FROM Payments")

    # Fetch all the records as a list of tuples
    records = cursor.fetchall()

    # Insert the records into the Treeview
    for record in records:
        tree.insert("", "end", values=record)

    conn.commit()   #commits any chages made

    cursor.close()
    conn.close()   #closes the DB connection
#Creating Buttons
# Create Submit button
submit_btn = tk.Button(root, text="Add", command=submit)
submit_btn.grid(row=3)

# Create Query button
query_btn = tk.Button(root, text="Show records", command=query)
query_btn.grid(row=4, column=0, columnspan=2, pady=10, padx=10, ipadx=137)

root.mainloop()

on reference you can may be use my Github name…

Hi Kyle,

Thank you for sharing your code. I will definitely take a look at it.

And thanks again so much for your help. I made the phonebook work the way I want to, all thanks to your help. I will make sure to reference your GitHub name in my assignment.

Have a great day!