pythontkintertkinter-entrytkinter-layouttkinter-button

How do I destroy frames and make it appear the way it was before in tkinter?


enter image description here

In the code below, I'm destroying frame1 and frame2, so that the messages can make use of the extra space after destroying frame1 and frame2.

The functionality I need is:

When a message is sent, frame1 and frame2 must get destroyed automatically.

The messages must make use of the extra space but the messages must be below new_note_button.

When new_note_button is clicked, I want all messages to be cleared and frame1, frame2 to appear how it was before.

I tried to recreate frame1 and frame2 when new_note_button is clicked but the frames are in different places and multiple frames are getting created (check the image I've attached).

Here's my code:

import tkinter as tk
from tkinter import ttk

frames_loaded = True

def clear_chat_log():
    global frames_loaded
    if not frames_loaded:
        # Create frames to group the boxes
        frame1 = tk.Frame(root, bg="white")
        frame1.pack(side=tk.TOP, padx=(0, 40), pady=(100, 1))

        frame2 = tk.Frame(root, bg="white")
        frame2.pack(side=tk.TOP, padx=(0, 40), pady=(5, 20))

        style.configure("PDF.TButton", font=('Sans-serif', 12,), background="white", fg="black", highlightthickness=1, highlightbackground="gray", borderwidth=1)
        chat_with_pdf_button = ttk.Button(frame1, text="Teyxxxxxxxxxxxx", style="PDF.TButton")
        chat_with_pdf_button.pack(side=tk.LEFT, ipadx=(135), padx=(0, 10), ipady=14)

        option2_label = tk.Label(frame1, text="testxxxxxxxxxxxxxxxxx", font=('Sans-serif', 12,), bg="white", fg="black", highlightthickness=1, highlightbackground="gray", padx=95, pady=20)
        option2_label.pack(side=tk.LEFT)

        # Create labels for the options in the second frame
        option3_label = tk.Label(frame2, text="testzzzzzzzzzzzzzzzz", font=('Sans-serif', 12,), bg="white", fg="black", highlightthickness=1, highlightbackground="gray", padx=116, pady=20)
        option3_label.pack(side=tk.LEFT, padx=(0, 10))

        option4_label = tk.Label(frame2, text="testqqqqqqqqqqqqqqqqq", font=('Sans-serif', 12,), bg="white", fg="black", highlightthickness=1, highlightbackground="gray", padx=115, pady=20)
        option4_label.pack(side=tk.LEFT)
        frames_loaded = True
        return

root = tk.Tk()
# Adjust the placement of other widgets accordingly
root.title("Test")
# Maximize the window
root.attributes('-zoomed', True)
style = ttk.Style()
style.theme_use("clam")
chat_frame = tk.Frame(root, bg="white")
chat_frame.pack(expand=True, fill=tk.BOTH)

chat_log = tk.Text(chat_frame, state='disabled', wrap='word', width=70, height=15, font=('Sans-serif', 12), bg="white", fg="black", highlightthickness=0, borderwidth=0)
chat_log.pack(side=tk.LEFT, padx=(500,0), pady=10, expand = True, fill = tk.BOTH)

# Create frames to group the boxes
frame1 = tk.Frame(root, bg="white")
frame1.pack(side=tk.TOP, padx=(0, 40), pady=(100, 1))

frame2 = tk.Frame(root, bg="white")
frame2.pack(side=tk.TOP, padx=(0, 40), pady=(5, 20))

style.configure("PDF.TButton", font=('Sans-serif', 12,), background="white", fg="black", highlightthickness=1, highlightbackground="gray", borderwidth=1)
chat_with_pdf_button = ttk.Button(frame1, text="Teyxxxxxxxxxxxx", style="PDF.TButton")
chat_with_pdf_button.pack(side=tk.LEFT, ipadx=(135), padx=(0, 10), ipady=14)

option2_label = tk.Label(frame1, text="testxxxxxxxxxxxxxxxxx", font=('Sans-serif', 12,), bg="white", fg="black", highlightthickness=1, highlightbackground="gray", padx=95, pady=20)
option2_label.pack(side=tk.LEFT)

# Create labels for the options in the second frame
option3_label = tk.Label(frame2, text="testzzzzzzzzzzzzzzzz", font=('Sans-serif', 12,), bg="white", fg="black", highlightthickness=1, highlightbackground="gray", padx=116, pady=20)
option3_label.pack(side=tk.LEFT, padx=(0, 10))

option4_label = tk.Label(frame2, text="testqqqqqqqqqqqqqqqqq", font=('Sans-serif', 12,), bg="white", fg="black", highlightthickness=1, highlightbackground="gray", padx=115, pady=20)
option4_label.pack(side=tk.LEFT)

def append_to_chat_log(sender=None, message=None):
  
    chat_log.config(state=tk.NORMAL)
    if sender:
        chat_log.insert("end", f"{sender}\n\n", "sender")
        
        #chat_log.insert("end",'\n\n')
    if message:
        chat_log.insert("end", message)
    chat_log.tag_config("sender", font=('Arial', 12, 'bold'), foreground="black")
    chat_log.config(state=tk.DISABLED)
    chat_log.see("end")
    chat_log.update()

def send_message(event=None):
    global frames_loaded
    if frame1:
        frame1.destroy()
    if frame2:
        frame2.destroy()
    frames_loaded = False
    message = message_entry.get(1.0, "end-1c") 
    message = message.strip()
    message_entry.delete(1.0, tk.END)
    message_entry.update()
    
    if not message:
        pass 
    else:
        canvas1.place(x=495,y=80)
        canvas1.update()
        
        append_to_chat_log("User")
        append_to_chat_log(message=message)
        append_to_chat_log(message="\n")
        canvas1.place_forget()
        canvas1.update()

new_note_button = ttk.Button(chat_frame, style="Toggle.TButton", text=" New Note", compound="left", command=clear_chat_log)
new_note_button.place(x=490,y=40)

canvas1 = tk.Canvas(root, width=250, height=70, bg="white", borderwidth=0, highlightthickness=0)
# Display "Loading" text
loading_text = canvas1.create_text(70, 50, text="Loading...", anchor="e")

message_entry = tk.Text(root, padx=17, insertbackground='white', bg="white", fg="black", width=70, height=1, spacing1=20, spacing3=20, font=('Open Sans', 14))
message_entry.pack(side=tk.LEFT, padx=(500, 0), pady=(0, 70))  # Adjust pady to move it slightly above the bottom
#message_entry.insert(0, "Ask me anything...")
message_entry.insert(1.0, "Type")
message_entry.mark_set("insert", "%d.%d" % (0,0))
message_entry.bind("<Return>", send_message)
root.mainloop()

Solution

  • I took the liberty of turning your code into an object-oriented approach. Instead of deleting the two frames, you can simply unpack them with pack_forget(). This way you don't have to recreate them, but can simply call pack() again with the appropriate parameters.

    For the placement of the button in the chat I did not use the place but the grid() manager and configured it with weight on the rows so that the second row with the chat expands accordingly.

    As a little goodie, I have written you a small class that provides the text widget with a placeholder.

    Here is your adapted code:

    import tkinter as tk
    from tkinter import ttk
    
    
    class EntryWithPlaceholder(tk.Text):
        """Entry with placeholder so user does not accidentally send 'Type'"""
        def __init__(self, master=None, placeholder="PLACEHOLDER", color='grey', *args, **kwargs):
            super().__init__(master, *args, **kwargs)
    
            self.placeholder = placeholder
            self.placeholder_color = color
            self.default_fg_color = self['fg']
    
            self.bind("<FocusIn>", self.foc_in)
            self.bind("<FocusOut>", self.foc_out)
    
            self.put_placeholder()
    
        def put_placeholder(self):
            self.insert('1.0', self.placeholder)
            self['fg'] = self.placeholder_color
    
        def foc_in(self, *args):
            if self['fg'] == self.placeholder_color:
                self.delete('1.0', 'end')
                self['fg'] = self.default_fg_color
    
        def foc_out(self, *args):
            if len(self.get('1.0', 'end')) <= 1:
                self.put_placeholder()
    
    
    class Chat(tk.Tk):
        def __init__(self):
            super().__init__()  # instead of "root = tk.Tk()" → root is now self
            # overall GUI settings
            self.title("Test")
            # Maximize the window
            self.state('zoomed')  # changed from root.attributes('-zoomed', True)
            self.style = ttk.Style()
            self.style.theme_use("clam")
            # declare own style "PDF.TButton"
            self.style.configure("PDF.TButton", font=('Sans-serif', 12,), background="white", fg="black",
                                 highlightthickness=1, highlightbackground="gray", borderwidth=1)
            # global variables as instance variables to make them accessible inside the class methods
            self.frames_loaded = True
            # create widget frames
            self._create_chat_frame()
            self._create_frame1()
            self._create_frame2()
            # create widgets in root
            self.canvas1 = tk.Canvas(self, width=250, height=70, bg="white", borderwidth=0, highlightthickness=0)
            # Display "Loading" text
            loading_text = self.canvas1.create_text(70, 50, text="Loading...", anchor="e")
            # own Entry class with placeholder
            self.message_entry = EntryWithPlaceholder(self, padx=17, insertbackground='white', bg="white", fg="black",
                                                      width=70, height=1, spacing1=20, spacing3=20, font=('Open Sans', 14),
                                                      placeholder='Type...')
            # Adjust pady to move it slightly above the bottom
            self.message_entry.pack(side=tk.LEFT, padx=(500, 0), pady=(0, 70))
            # Binding of entry → placeholder is set in instantiation
            self.message_entry.bind("<Return>", self.send_message)
    
        def _create_chat_frame(self):
            self.chat_frame = tk.Frame(self, bg="white")
            self.chat_frame.pack(expand=True, fill=tk.BOTH)
    
            # set weights to expand the row with the chat
            self.chat_frame.grid_rowconfigure(0, weight=0)
            self.chat_frame.grid_rowconfigure(1, weight=1)
    
            new_note_button = ttk.Button(self.chat_frame, style="Toggle.TButton", text=" New Note", compound="left",
                                         command=self.clear_chat_log)
            new_note_button.grid(row=0, column=0, sticky='W', padx=490, pady=40)
    
            self.chat_log = tk.Text(self.chat_frame, state='disabled', wrap='word', width=70, height=15,
                                    font=('Sans-serif', 12), bg="white", fg="black", highlightthickness=0, borderwidth=0)
            self.chat_log.grid(row=1, column=0, sticky='NESW', padx=(500, 0), pady=10)
    
        def _create_frame1(self):
            self.frame1 = tk.Frame(self, bg="white")
            self.frame1.pack(padx=(0, 40), pady=(100, 1))
    
            self.chat_with_pdf_button = ttk.Button(self.frame1, text="Teyxxxxxxxxxxxx", style="PDF.TButton")
            self.chat_with_pdf_button.pack(side=tk.LEFT, ipadx=135, padx=(0, 10), ipady=14)
    
            self.option2_label = tk.Label(self.frame1, text="testxxxxxxxxxxxxxxxxx", font=('Sans-serif', 12,), bg="white",
                                          fg="black", highlightthickness=1, highlightbackground="gray", padx=95, pady=20)
            self.option2_label.pack(side=tk.LEFT)
    
        def _create_frame2(self):
            self.frame2 = tk.Frame(self, bg="white")
            self.frame2.pack(padx=(0, 40), pady=(5, 20))
    
            self.option3_label = tk.Label(self.frame2, text="testzzzzzzzzzzzzzzzz", font=('Sans-serif', 12,),
                                          bg="white", fg="black", highlightthickness=1, highlightbackground="gray",
                                          padx=116, pady=20)
            self.option3_label.pack(side=tk.LEFT, padx=(0, 10))
    
            self.option4_label = tk.Label(self.frame2, text="testqqqqqqqqqqqqqqqqq", font=('Sans-serif', 12,),
                                          bg="white", fg="black", highlightthickness=1, highlightbackground="gray",
                                          padx=115, pady=20)
            self.option4_label.pack(side=tk.LEFT)
    
        def clear_chat_log(self):
            if not self.frames_loaded:
                # you have to restore the order, so first forget the entry text widget
                self.message_entry.pack_forget()
                # then just pack the parent frames again
                self.frame1.pack(padx=(0, 40), pady=(100, 1))
                self.frame2.pack(padx=(0, 40), pady=(5, 20))
                # and then the message entry again
                self.message_entry.pack(side=tk.LEFT, padx=(500, 0), pady=(0, 70))
                self.frames_loaded = True
    
        def append_to_chat_log(self, sender=None, message=None):
            self.chat_log.config(state=tk.NORMAL)
            if sender:
                self.chat_log.insert("end", f"{sender}\n\n", "sender")
    
                # chat_log.insert("end",'\n\n')
            if message:
                self.chat_log.insert("end", message)
            self.chat_log.tag_config("sender", font=('Arial', 12, 'bold'), foreground="black")
            self.chat_log.config(state=tk.DISABLED)
            self.chat_log.see("end")
            self.chat_log.update()
    
        def send_message(self, event=None):
            if self.frames_loaded:
                # just pack_forget() the frames, no need to destroy if you want to reuse them
                self.frame1.pack_forget()
                self.frame2.pack_forget()
                self.frames_loaded = False
    
            message = self.message_entry.get(1.0, "end-1c")
            message = message.strip()
            self.message_entry.delete('1.0', 'end')
            self.message_entry.update()
    
            if not message:
                pass
            else:
                self.canvas1.place(x=495, y=80)
                self.canvas1.update()
    
                self.append_to_chat_log("User")
                self.append_to_chat_log(message=message)
                self.append_to_chat_log(message="\n")
                self.canvas1.place_forget()
                self.canvas1.update()
    
    
    if __name__ == '__main__':
        app = Chat()
        app.mainloop()