Tying it all together

Let's begin with the ability to add a friend, since we need this to make the rest of our changes work properly.

We already have a Friends menu in the top menu bar of our FriendsList class containing an Add Friend command, which seems appropriate for this functionality. We'll replace the placeholder method with a functioning one:

def __init__(self, **kwargs):
...
self.friends_menu.add_command(label="Add Friend", command=self.show_add_friend_window)
...

def show_add_friend_window(self):
AddFriendWindow(self)

In order to take the information, we need to add a friend and we will require a new Toplevel window. Go ahead and create a file named addfriendwindow.py alongside your friendslist.py file:

import tkinter as tk
import tkinter.ttk as ttk

class AddFriendWindow(tk.Toplevel):
def __init__(self, master):
super().__init__()
self.master = master

self.transient(master)
self.geometry("250x100")
self.title("Add a Friend")

This class will be a fairly standard Toplevel subclass. It needs to contain an Entry widget for gathering the friend's username and a Button widget to submit the data:

main_frame = ttk.Frame(self)

username_label = ttk.Label(main_frame, text="Username")
self.username_entry = ttk.Entry(main_frame)

add_button = ttk.Button(main_frame, text="Add", command=self.add_friend)

username_label.grid(row=0, column=0)
self.username_entry.grid(row=0, column=1)
self.username_entry.focus_force()

As well as the Entry and Button widgets, we will also use a Label widget to signal to the user what they need to type into the Entry widget.

When the window opens, we will force the focus to the username_entry. This means the user will not have to click inside the widget in order to begin typing into it:

add_button.grid(row=1, column=0, columnspan=2)

for i in range(2):
tk.Grid.columnconfigure(main_frame, i, weight=1)
tk.Grid.rowconfigure(main_frame, i, weight=1)

main_frame.pack(fill=tk.BOTH, expand=1)

We are using the grid geometry manager to add the widgets to their Frame, and then pack to add the frame to the window. As before, we use columnconfigure and rowconfigure to set all cells to the same weight:

    def add_friend(self):
username = self.username_entry.get()

if username:
if self.master.add_friend(username):
self.username_entry.delete(0, tk.END)

When adding a friend, we first need to get the value of our Entry widget. If there is something typed into it, we will pass it over to the add_friend method of the FriendsList class. Should this return a positive response, we will clear the Entry widget, so that more friends can quickly be added if necessary.

This completes the functionality of this class. We can now move back to our FriendsList class and implement its add_friend method:

def add_friend(self, username):
if self.requester.add_friend(self.username, username):
msg.showinfo("Friend Added", "Friend Added")
success = True
self.reload_friends()
else:
msg.showerror("Add Failed", "Friend was not found")
success = False

return success

If our friend was successfully added, we use a showinfo box to alert the user, then reload our friends list to show them in it.

If the added user does not exist, we will receive a negative response from our Requester. We will relay this message to the user with a showerror box:

def reload_friends(self):
for child in self.canvas_frame.winfo_children():
child.pack_forget()
self.load_friends()

In order to reload our friends list, we first need to loop over all of the frames we have added to our scrollable window and remove them.

To get the children of a widget in Tkinter, we can use the winfo_children method. This will return an iterable of all widgets that have been added to that widget via a geometry manager.

Once we have the child widgets, we need to remove them to avoid duplicates. We do this using the pack_forget method. This method is a way of undoing a call to pack and will remove them from display.

With our canvas_frame emptied, we now need to put back the list of friends. We do this by calling the load_friends method once again, which will now include any new friends.

On the topic of our load_friends method, we need to change this to call the new endpoint we have written to get all friends who are not blocked:

 def load_friends(self):
my_friends = self.requester.get_friends(self.username)
for user in my_friends["friends"]:
if user['username'] != self.username:
friend_frame = ttk.Frame(self.canvas_frame)
...
block_this_friend = partial(self.block_friend,
username=user["username"])
block_button = ttk.Button(friend_frame, text="Block",
command=block_this_friend)
...
profile_photo_label.pack(side=tk.LEFT)
friend_name.pack(side=tk.LEFT)
message_button.pack(side=tk.RIGHT)
block_button.pack(side=tk.RIGHT, padx=(0, 30))
...

At the beginning of this method, we call the get_friends method of our Requester, passing it our username attribute. This will then return any friends we have added and not blocked. We then loop over them as before, creating a Frame for each.

Included in our widgets now is a second button which we can use to block a friend. We again use a partial function to freeze the username argument for each friend, and map it to the block_friend method.

We pack the button to the right after packing our message button, so that it appears to the left of our Chat button.

The final update we need to do to this class is to create the block_friend method:

def block_friend(self, username):
self.requester.block_friend(self.username, username)
self.reload_friends()

This method passes our username and the friend's username to the block_friend method of our Requester, then reloads the friends list to remove them.

That wraps up everything for our FriendsList class. Give it a run and check out its new functionality. Try creating multiple accounts and adding, and then blocking, some friends:

This is where we will leave our chat application. We have now written a server, which handles matching people to their friends and allowing them to send messages back and forth, as well as choosing their own avatars for all of their friends to see.

Our GUI has been updated to make requests to our server and offer a nice interface to each new endpoint. We also have a separate thread running, which will continually fetch new messages very shortly after they have been sent.

Upon finishing this chapter, you can now boast that you have written three different GUI applications using Python and Tkinter. The first was a game of blackjack featuring animated images and text. The second was a powerful text editor with syntax highlighting and dynamic line numbers. Now, you have also added a chat application to your arsenal, as well as some knowledge on how to hook a GUI application up to a web service to create the ability to share information with others.

In the final chapter of this book, we will learn how to package these applications up for others to install. Currently, running our applications on a new machine requires installing Python and executing scripts from the command line. For non-programmers, this is a barrier to entry for our applications and we can benefit from learning some tools which remove this need.

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

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