跳到主要内容

使用 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