使用 Python 创建聊天应用
在本教程中,我们将创建一个简单的内存聊天应用,帮助您了解 Flet 框架的基础知识。这个应用可以作为您自己的更复杂和有用的项目的起点。
在本教程中,您将学习如何:
完整的应用程序如下所示:
您可以在此处查看实时演示:https://flet-chat.fly.dev。
完整的应用程序代码可以在此处找到:https://github.com/flet-dev/examples/blob/main/python/tutorials/chat/chat.py。
入门
让我们从 "Hello, world!" 应用程序开始!
要创建一个多平台应用程序,需要安装 Flet,并且需要基本的 Python 和面向对象编程知识。
在创建第一个 Flet 应用程序之前,您需要 设置开发环境,这需要 Python 3.8 或更高版本和 flet
包。
一旦安装了 Flet,让我们创建一个简单的 hello-world 应用程序。
创建 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(target=main)
当用户单击 "发送" 按钮时,会触发 on_click
事件,该事件调用 send_click
方法。 send_click
方法会将新 Text
控件添加到 Column 的 controls
列表中,并清除 new_message
TextField 的值。
更新控件的任何属性后,应调用控件(或其父控件)的 update()
方法,以使更新生效。
聊天应用程序现在如下所示:
广播聊天消息
在前面的步骤中,我们创建了一个简单的应用程序,接受用户输入并在屏幕上显示聊天消息。如果您在两个 Web 浏览器标签中打开此应用程序,它将创建两个应用程序会话。每个会话都有自己的消息列表。
要在本地打开应用程序的两个 Web 浏览器标签,请运行以下命令:
flet run --web <path_to_your_app>
一旦打开,复制 URL 并粘贴到新标签中。
要构建实时聊天应用程序,我们需要以某种方 式在聊天应用程序会话之间传递消息。当用户发送消息时,它应该广播给所有其他应用程序会话并显示在它们的页面上。
Flet 提供了一个简单的内置 PubSub 机制,用于应用程序会话之间的异步通信。
首先,我们需要订阅用户接收广播消息:
page.pubsub.subscribe(on_message)
pubsub.subscribe()
方法将当前应用程序会话添加到订阅者列表中。它接受 handler
作为参数,该参数将在发布者调用 pubsub.send_all()
方法时被调用。
在 handler
中,我们 将添加新消息(Text
)到聊天 controls
列表中:
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()
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)
用户名对话框
聊天应用程序现在已经具有基本功能,用于在用户会话之间交换消息。然而,它不太用户友好,因为它显示 session_id
,而不是用户名。
让我们改进应用程序,以显示用户名而不是 session_id
。为此,我们将使用 AlertDialog
控件。让我们将其添加到页面:
user_name = ft.TextField(label="输入您的名字")
page.dialog = ft.AlertDialog(
open=True,
modal=True,
title=ft.Text("欢迎!"),
content=ft.Column([user_name], tight=True),
actions=[ft.ElevatedButton(text="加入聊天", 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 = "名字不能为空!"
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} 加入了聊天。", message_type="login_message"))
page.update()
我们使用 页面会话存储 来存储用户名,以便在 send_click
方法中使用。
当我们将对话框的 open
属性设置为 False
并调用 update()
方法时,对话框将关闭。
最后,让我们更新 send_click
方法,以使用 user_name
,我们 之前使用 page.session
存储:
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()
完整的代码可以在此处找到:https://github.com/flet-dev/examples/blob/main/python/tutorials/chat/chat_3.py。
增强用户界面
聊天应用程序现在已经具有基本功能,用于在用户会话之间交换消息。然而,它不太用户友好,因为它没有提供足够的功能。
让我们增强用户界面,以提供更好的用户体验。
可重用控件
我们可以创建一个可重用控件来显示聊天消息。让我们创建一个 ChatMessage
类,继承自 Row
:
class ChatMessage(ft.Row):
def __init__(self, message: Message):
super().__init__()
self.vertical_alignment = ft.CrossAxisAlignment.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
## 增强用户界面
聊天应用程序现在已经具有基本功能,用于在用户会话之间交换消息。然而,它不太用户友好,因为它没有提供足够的功能。
让我们增强用户界面,以提供更好的用户体验。
### 可重用控件
我们可以创建一个可重用控件来显示聊天消息。让我们创建一个 `ChatMessage` 类,继承自 `Row`:
```python
class ChatMessage(ft.Row):
def __init__(self, message: Message):
super().__init__()
self.vertical_alignment = ft.CrossAxisAlignment.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
控件来构建更好的布局:
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 = ft.ListView(
expand=True,
spacing=10,
auto_scroll=True,
)
new_message = ft.TextField(
hint_text="输入消息...",
autofocus=True,
shift_enter=True,
min_lines=1,
max_lines=5,
filled=True,
expand=True,
on_submit=send_message_click,
)
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="发送消息",
on_click=send_message_click,
),
]
),
)
完整的代码可以在此处找到:https://github.com/flet-dev/examples/blob/main/python/tutorials/chat/chat.py。
部署应用程序
恭喜!您已经创建了一个聊天应用程序,并且它看起来很棒!
现在是时候与世界分享您的应用程序了!
请遵循以下指示,将您的 Flet 应用程序部署为 Web 应用程序:https://flet.dev/docs/publish/web/dynamic-website/hosting。
下一步
还有很多功能可以实现来改进这个聊天应用程序:
- 断开连接、重新连接、会话超时
- 上传/下载图像
- 认证、头像
- 使用数据库存储
- 聊天频道、主题
- 全文搜索
- 表情、Markdown
- 机器人
- 移动应用程序
请让我们知道您是否愿意贡献这个应用程序/教程并与其他 Flet 开发者分享。
我们很乐意收到您的反馈!请发送电子邮件至 hello@flet.dev,加入 Discord 讨论:https://discord.gg/dzWXP8SHG8。