Creating our ChatWindow class

Before we begin coding our ChatWindow, let's have a brief overview of the design.

The window will contain the following elements:

  • A messages area, showing all messages sent by you to this friend, and sent by this friend to you
  • A scrollbar that allows you to scroll up to view older messages
  • A text area for you to type a message to send to this friend
  • A button that sends the contents of your message to your friend, then clears the text area
  • A button that lets the user pick a smiley to include in their message
  • Your avatar
  • This friend's avatar

From a layout point of view, the message area is the primary part of the application, so it will take up the most space within the window, and be central. 

The text area and buttons will be placed below the messages area so that they are nearby but do not draw the eye away from the messages area.

The avatars will be off to the right-hand side of the window, with your friend's image above yours. This keeps your avatar near your message input, and again prevents stealing focus from the message area.

From this information we can begin to figure out what widgets we will need to achieve this design.

The messages area could be achieved by a few different means, but in this case the best option is a disabled Text widget. We know exactly how to dynamically update these from our text editor application, and we will soon look at how we can add images into one as well.

The scrollbar can be handled by the normal Scrollbar widget, which we have used to control a Text widget in the past.

The user's messages will be typed into a second Text widget, allowing them to send messages longer than the one line allowed by an Entry widget.

The buttons can be handled by the normal Button widget. These can display both text and images, as we will see shortly.

Finally, the avatars will be handled with a PhotoImage and displayed using a Label widget.

With regard to structuring the layout, we have three distinct areas, which can be separated like so:

We can represent each area with its own Frame. This allows us to control the layout very easily.

With these decisions in mind we can begin structuring the ChatWindow class.

Since the FriendsList class inherits from the Tk widget, and we want to be able to have multiple chat windows open at once, the ChatWindow will need to inherit from the Toplevel widget:

import tkinter as tk
import tkinter.ttk as ttk

class ChatWindow(tk.Toplevel):
def __init__(self, master, friend_name, friend_avatar, **kwargs):
super().__init__(**kwargs)
self.master = master
self.title(friend_name)
self.geometry('540x640')
self.minsize(540, 640)

self.right_frame = tk.Frame(self)
self.left_frame = tk.Frame(self)
self.bottom_frame = tk.Frame(self.left_frame)

The minimum information each ChatWindow will need passed from the FriendsList is the friend's name and avatar image. We also accept the standard master argument, and any further keyword arguments, which will be passed back to the Toplevel base class.

The minsize method is used to prevent the user from shrinking the window too small, making their conversations unreadable. This method takes two numbers: the first is the minimum width, and the second is the minimum height. You will notice that these match the default size passed to the geometry method.

We begin our layout by creating three Frame widgets. The right_frame will hold our avatars, the left_frame our messages area, and the bottom_frame our text input and buttons:

 self.messages_area = tk.Text(self.left_frame, bg="white", fg="black", wrap=tk.WORD, width=30)
self.scrollbar = ttk.Scrollbar(self.left_frame, orient='vertical', command=self.messages_area.yview)
self.messages_area.configure(yscrollcommand=self.scrollbar.set)

The first widgets we create are those that handle reading messages. Our messages area, as mentioned, will be a Text widget. As well as some color information, we initialize this with the wrap argument set to the WORD constant, meaning the messages will not break up words as a message reaches the edge of the widget, and the width argument to 30, setting the default size of the widget.

Alongside our messages_area we create a Scrollbar as normal and set it up to affect the messages_area with the usual yview and yscrollcommand arguments:

self.text_area = tk.Text(self.bottom_frame, bg="white", fg="black", height=3, width=30)
self.send_button = ttk.Button(self.bottom_frame, text="Send", command=self.send_message, style="send.TButton")

The widgets that will sit in our bottom_frame come next. Another Text widget is created for the user to type their message into, and a Button is made, which will send their message when clicked:

self.profile_picture = tk.PhotoImage(file="images/avatar.png")
self.friend_profile_picture = tk.PhotoImage(file=friend_avatar)

self.profile_picture_area = tk.Label(self.right_frame, image=self.profile_picture, relief=tk.RIDGE)
self.friend_profile_picture_area = tk.Label(self.right_frame, image=self.friend_profile_picture, relief=tk.RIDGE)

To finish off our widgets, we create the two images that will act as avatars in our right_frame. As we have seen before, we begin by creating PhotoImage objects that Tkinter can use, then we use a Label widget in order to display them.

In order to display a PhotoImage using a Label, all we need to do is pass it as the image argument. We also set the relief argument to the RIDGE constant as a way of placing a small border around the image.

Now that all of our widgets have been defined we can begin packing them into the window:

self.left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
self.scrollbar.pack(side=tk.LEFT, fill=tk.Y)
self.messages_area.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.messages_area.configure(state='disabled')

The left_frame is packed into the window and its widgets are packed into it. The messages_area is then disabled using the state argument:

self.right_frame.pack(side=tk.LEFT, fill=tk.Y)
self.profile_picture_area.pack(side=tk.BOTTOM)
self.friend_profile_picture_area.pack(side=tk.TOP)

The right_frame then follows suit. The user's avatar is added to the bottom of the Frame widget, and the friend's at the top:

self.bottom_frame.pack(side=tk.BOTTOM, fill=tk.X)
self.text_area.pack(side=tk.LEFT, fill=tk.X, expand=1, pady=5)
self.send_button.pack(side=tk.RIGHT, pady=5)

We finish packing with the bottom_frame and its widgets. Some padding around them is added with the pady argument for aesthetics:

self.configure_styles()
self.bind_events()

The __init__ method is finished off by configuring the styles of our ttk widgets and binding any necessary events.

We'll start the bind_events method by allowing the user to send their message with the Return/Enter key as well as by clicking the button:

def bind_events():
self
.bind("<Return>", self.send_message)
self.text_area.bind("<Return>", self.send_message)

Now that we are all set up to send our messages, let's write the send_message method to get things going:

def send_message(self, event=None):
message = self.text_area.get(1.0, tk.END)

if message.strip():
message = "Me: " + message
self.messages_area.configure(state='normal')
self.messages_area.insert(tk.END, message)
self.messages_area.configure(state='disabled')

self.text_area.delete(1.0, tk.END)

return "break"

Since we can call this method from an event binding, we need to catch the event object as a parameter.

The message is extracted from the text_area using its get method. To avoid sending empty messages of just spaces, we first use the strip method on the message, then check that it still has content.

If it does, we add the word Me: to the beginning to indicate that the message was written by us, then enable the messages_area, add our message, and disable it again.

The text_area is then cleared out so that the user does not have to manually delete their last message.

The string break is returned to avoid the default behavior of the Return/Enter key.

One last thing we should do before previewing our ChatWindow is handle adjusting the ttk styling:

def configure_styles(self):
style = ttk.Style()
style.configure("send.TButton", background='#dddddd', foreground="black", padding=16)

This method only affects our send_button at the moment. It gives the button a light gray background with black text, and adds some padding to make it the same height as the text_area.

Now that this is in place, we can take a look at our ChatWindow in action. Add this small block to the bottom of the file and run it:

if __name__ == '__main__':
w = tk.Tk()
c = ChatWindow(w, 'friend', 'images/avatar.png')
c.protocol("WM_DELETE_WINDOW", w.destroy)
w.mainloop()

You should now have a chat window appear. Go ahead and type some messages and send them. You should see them appear in the messages area:

While sending text messages is nice, people often want to send images known as smileys or emojis. Luckily, the Text widget we are using to display messages can support images.

Before we add these to our ChatWindow class, we should create a user-friendly way for the user to select their choice of smiley.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset