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()
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()