跳到主要内容

在Python中创建实时聊天应用

在本教程中,我们将创建一个简单的内存中聊天应用,帮助您了解 Flet 框架的基础知识。这个应用可以作为创建更复杂、更有用的项目的起点。

在本教程中,您将学习如何:

完整的应用程序将如下所示:

您可以在这里尝试在线演示。

聊天应用的完整代码可以在这里找到。

开始使用 Flet

我们惯例上从 "Hello, world!" 应用开始!

Flet 需要 Python 3.8 或更高版本。要在 Python 中创建一个 Flet 应用,您需要先安装 flet 模块:

pip install flet
升级 Flet

要升级 flet 模块,请运行:

pip install flet --upgrade

创建一个名为 hello.py 的文件,内容如下:

hello.py
import flet as ft

def main(page: ft.Page):
page.add(ft.Text(value="Hello, world!"))

ft.app(target=main)

运行这个应用程序,您将看到一个带有问候语的新窗口:

添加页面控件并处理事件

首先,我们希望能够接受用户输入(聊天消息)并在屏幕上显示消息历史记录。这一步的布局可能如下所示:

为了实现这个布局,我们将使用以下 Flet 控件:

  • Column - 用于垂直显示聊天消息(Text 控件)的容器。
  • Text - 在聊天 Column 中显示的聊天消息。
  • TextField - 用于从用户接受新消息输入的输入控件。
  • ElevatedButton - "发送" 按钮,将新消息添加到聊天 Column 中。
  • Row - 用于水平显示 TextField 和 ElevatedButton 的容器。

创建一个名为 chat.py 的文件,内容如下:

import flet as ft

def main(page: ft.Page):
chat = ft.Column()
new_message = ft.TextField()

def send_click(e):
chat.controls.append(ft.Text(new_message.value))
new_message.value = ""
page.update()

page.add(
chat, ft.Row(controls=[new_message, ft.ElevatedButton("Send", on_click=send_click)])
)

ft.app(main, view=ft.AppView.WEB_BROWSER)

当用户点击"发送"按钮时,它会触发on_click事件,调用send_click方法。send_click方法会将新的Text控件添加到Column控件的控件列表中,并清除new_message文本框的值。

备注

在更新控件的任何属性之后,应调用该控件(或其父控件)的update()方法才能使更新生效。

现在聊天应用的界面如下所示:

广播聊天消息

在上一步中,我们创建了一个简单的Web应用程序,它接收用户输入并在屏幕上显示聊天消息。如果您在两个Web浏览器选项卡中打开此应用程序,它将创建两个应用程序会话。每个会话都有自己的消息列表。

要构建实时聊天应用程序,您需要以某种方式在聊天应用程序会话之间传递消息。当用户发送消息时,它应该被广播到所有其他应用程序会话,并显示在它们的页面上。

Flet提供了一个简单的内置PubSub机制,用于页面会话之间的异步通信。

首先,我们需要订阅用户以接收广播消息:

    page.pubsub.subscribe(on_message)

pubsub.subscribe()方法将当前应用程序会话添加到订阅者列表中。它接受handler作为参数,在发布者调用pubsub.send_all()方法时将被调用。

handler中,我们将向聊天controls列表中添加新的消息(Text):

    def on_message(message: Message):
chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
page.update()

最后,当用户点击"发送"按钮时,您需要调用pubsub.send_all()方法:

    def send_click(e):
page.pubsub.send_all(Message(user=page.session_id, text=new_message.value))
new_message.value = ""
page.update()

page.add(chat, ft.Row([new_message, ft.ElevatedButton("Send", on_click=send_click)]))

pubsub.send_all()将调用on_message()并将Message对象传递给它。

以下是这一步的完整代码:

import flet as ft

class Message():
def __init__(self, user: str, text: str):
self.user = user
self.text = text

def main(page: ft.Page):

chat = ft.Column()
new_message = ft.TextField()

def on_message(message: Message):
chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
page.update()

page.pubsub.subscribe(on_message)

def send_click(e):
page.pubsub.send_all(Message(user=page.session_id, text=new_message.value))
new_message.value = ""
page.update()

page.add(chat, ft.Row([new_message, ft.ElevatedButton("Send", on_click=send_click)]))

ft.app(target=main, view=ft.AppView.WEB_BROWSER)

在之前的步骤中,您创建的聊天应用具有在用户会话之间交换消息所需的基本功能。但是,它并不是非常用户友好,因为它显示了发送消息的session_id,这并不能告诉您正在与谁进行通信。

让我们改进我们的应用程序,以便显示每条消息的用户名称,而不是session_id。为了捕获用户名称,我们将使用AlertDialog控件。让我们将其添加到页面中:

    user_name = ft.TextField(label="Enter your name")

page.dialog = ft.AlertDialog(
open=True,
modal=True,
title=ft.Text("Welcome!"),
content=ft.Column([user_name], tight=True),
actions=[ft.ElevatedButton(text="Join chat", on_click=join_click)],
actions_alignment="end",
)
备注

由于我们将其open属性设置为True,因此在程序启动时将打开对话框。

当用户点击“加入聊天”按钮时,它将调用join_click方法,该方法应向所有订阅者发送一条消息,通知他们用户已加入聊天。这条消息应该与常规的聊天消息不同,例如像这样:

让我们为Message类添加message_type属性以区分登录和聊天消息:

class Message():
def __init__(self, user: str, text: str, message_type: str):
self.user = user
self.text = text
self.message_type = message_type

我们将在on_message方法中检查message_type

def on_message(message: Message):
if message.message_type == "chat_message":
chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
elif message.message_type == "login_message":
chat.controls.append(
ft.Text(message.text, italic=True, color=ft.Colors.BLACK45, size=12)
)
page.update()

现在,"login_message"和"chat_message"类型的消息将在两个事件上发送:用户加入聊天时和用户发送消息时。

让我们创建join_click方法:

def join_click(e):
if not user_name.value:
user_name.error_text = "Name cannot be blank!"
user_name.update()
else:
page.session.set("user_name", user_name.value)
page.dialog.open = False
page.pubsub.send_all(Message(user=user_name.value, text=f"{user_name.value} has joined the chat.", message_type="login_message"))
page.update()

我们使用页面会话存储来存储user_name,以便在send_click方法中将其用于发送聊天消息。

备注

一旦将用户名称对话框的open属性设置为False并调用update()方法,对话框就会关闭。

最后,让我们更新send_click方法,使用之前使用page.session保存的user_name

def send_click(e):
page.pubsub.send_all(Message(user=page.session.get('user_name'), text=new_message.value, message_type="chat_message"))
new_message.value = ""
page.update()

这一步的完整代码可以在这里找到。

增强用户界面

在上一步中创建的聊天应用已经具备了基本的登录功能,可以实现用户之间的消息交流。

部署应用之前,我们建议为应用添加一些额外的功能,以提高用户体验并使应用看起来更专业。

可重用的用户控件

您可能希望以不同的格式显示消息,例如:

聊天消息现在将是一个包含用户名首字母的CircleAvatar和包含两个Text控件的ColumnRow

我们将在聊天应用中显示相当多的聊天消息,因此创建一个可重用的控件是有意义的。让我们创建一个名为ChatMessage的新类,该类将继承自Row

在创建ChatMessage类的实例时,我们将传递一个Message对象作为参数,然后ChatMessage将根据message.user_namemessage.text显示自己:

class ChatMessage(ft.Row):
def __init__(self, message: Message):
super().__init__()
self.vertical_alignment="start"
self.controls=[
ft.CircleAvatar(
content=ft.Text(self.get_initials(message.user_name)),
color=ft.Colors.WHITE,
bgcolor=self.get_avatar_color(message.user_name),
),
ft.Column(
[
ft.Text(message.user_name, weight="bold"),
ft.Text(message.text, selectable=True),
],
tight=True,
spacing=5,
),
]

def get_initials(self, user_name: str):
return user_name[:1].capitalize()

def get_avatar_color(self, user_name: str):
colors_lookup = [
ft.Colors.AMBER,
ft.Colors.BLUE,
ft.Colors.BROWN,
ft.Colors.CYAN,
ft.Colors.GREEN,
ft.Colors.INDIGO,
ft.Colors.LIME,
ft.Colors.ORANGE,
ft.Colors.PINK,
ft.Colors.PURPLE,
ft.Colors.RED,
ft.Colors.TEAL,
ft.Colors.YELLOW,
]
return colors_lookup[hash(user_name) % len(colors_lookup)]

ChatMessage控件从用户名中提取首字母并通过算法推导出头像的颜色。 稍后,如果您决定改进控件布局或其逻辑,不会影响程序的其余部分 - 这就是封装的优势!

布局控件

现在,您可以使用全新的ChatMessage来为聊天应用构建更好的布局:

on_message方法中,将创建ChatMessage的实例,而不是简单的Text聊天消息:

    def on_message(message: Message):
if message.message_type == "chat_message":
m = ChatMessage(message)
elif message.message_type == "login_message":
m = ft.Text(message.text, italic=True, color=ft.Colors.BLACK45, size=12)
chat.controls.append(m)
page.update()

使用新布局提出的其他改进包括:

  • 使用ListView而不是Column来显示消息,以便稍后可以滚动查看消息
  • 使用Container显示ListView周围的边框
  • 使用IconButton而不是ElevatedButton发送消息
  • 使用控件的expand属性来填充可用空间

以下是您可以实现此布局的方法:

    # Chat messages
chat = ft.ListView(
expand=True,
spacing=10,
auto_scroll=True,
)

# A new message entry form
new_message = ft.TextField(
hint_text="Write a message...",
autofocus=True,
shift_enter=True,
min_lines=1,
max_lines=5,
filled=True,
expand=True,
on_submit=send_message_click,
)

# Add everything to the page
page.add(
ft.Container(
content=chat,
border=ft.border.all(1, ft.Colors.OUTLINE),
border_radius=5,
padding=10,
expand=True,
),
ft.Row(
[
new_message,
ft.IconButton(
icon=ft.Icons.SEND_ROUNDED,
tooltip="Send message",
on_click=send_message_click,
),
]
),
)

本教程提供了使用Flet框架创建实时聊天应用的Python代码。以下是我们所做的增强功能的详细信息。

键盘支持

控件聚焦

所有数据输入控件都有autofocus属性,当设置为True时,将初始焦点设置在控件上。如果页面上有多个设置了autofocus的控件,则首先添加到页面的控件将获得焦点。

我们在对话框中的用户名TextField上设置了autofocus=True,然后在输入聊天消息的TextField上设置了autofocus=True,以在关闭对话框时将初始焦点设置在该控件上。

当用户点击“发送”按钮或按下回车键提交聊天消息时,TextField将失去焦点。要以编程方式设置控件焦点,我们使用了TextField.focus()方法。

使用“Enter”键提交表单

只需按下键盘上的“Enter”按钮即可提交表单,这真是太方便了!在对话框中输入您的姓名,按下“Enter”,输入新消息,再按下“Enter”,再输入另一条消息,再按下“Enter” - 完全不需要鼠标参与! 🚀

Flet通过提供TextField.on_submit事件处理程序来支持这一点,当焦点在TextField上时,用户按下“Enter”按钮时触发该事件。

输入多行消息

对于需要使用“Enter”键换行的多行TextField,我们也考虑到了这一点!TextField控件有一个shift_enter属性,当设置为True时,启用类似Discord的行为:用户按下Shift+Enter换行,而只按下Enter提交表单。

滚动到最后一条消息的动画效果

注意到聊天窗口中滚动到最后一条消息的漂亮动画了吗?通过将ListView.auto_scroll属性设置为True,可以启用该功能。作为一个可滚动的容器,最顶层的Page类也支持auto_scroll

页面标题

最后一步 - 页面标题可以简单地更改为:

page.title = "Flet Chat"
page.update()

部署应用

恭喜!您已经使用Flet在Python中创建了聊天应用,看起来很棒!

现在是时候与世界分享您的应用了!

按照这些说明将您的Flet应用部署为Fly.io或Replit的Web应用。

下一步

我们可以实现许多功能来改进这个聊天应用:

  • 断开连接、重新连接、会话超时
  • 上传/下载图片
  • 身份验证、头像
  • 使用数据库进行存储
  • 聊天频道、主题
  • 全文搜索
  • 表情符号、Markdown
  • 机器人
  • 移动应用

如果您愿意为应用/教程做出贡献并与其他Flet开发人员分享,请告诉我们。

总结

在本教程中,您学会了:

  • 创建一个简单的Flet应用;
  • 添加页面控件并处理事件;
  • 使用内置的PubSub库;
  • 使用AlertDialog输入用户名;
  • 使用可重复使用的控件构建页面布局;
  • 将Flet应用部署到Web。

如果想进一步了解,请查阅控件示例仓库