工作原理与交互流程
元素和控件
在 NiceGUI中:
- 元素 (Elements) 是构成用户界面 (UI) 的基本构建块。
- 控件 (Controls) 是元素的一种,特指那些用户可以直接与之交互的元素。
官方提供了非常多的元素和控件,包含各种使用场景,这一部分直接在官方文档中查找并选择我们需要的即可。
布局
with会把后面的..作为一个容器:
最基础的布局工具
默认情况下,我们创建的每个元素都会像写文章一样,从上到下一个接一个地排列。ui.row 和 ui.column 可以改变这个默认行为。
ui.column: 元素在容器内从上到下垂直排列(这是默认行为,但显式使用ui.column可以更好地控制一组元素)。ui.row: 元素在容器内从左到右水平排列。
Important
在 NiceGUI 中,我们使用 Python 的 with 语句来定义一个容器的范围。所有在 with 代码块内创建的元素,都会被自动放入这个容器中(容器中的元素可以是任意的)。
下面是一个简单的例子:
from nicegui import ui
with ui.row():
with ui.column():
ui.button('1')
ui.button('2')
with ui.column():
ui.button('3')
ui.button('4')
ui.run()
常用的几个布局工具
官方文档中有非常多的布局工具,我们只要知道最常见的几个就可以,其他的要用到时候查一查文档。
ui.card: 它本质上是一个带有边框、阴影和内边距的容器,能让界面更有层次感。
绑定属性
NiceGUI 能够直接将 UI 元素绑定到模型(这里的模型必须是对象或者字典,不能是普通变量)。每个元素都提供类似bind_value和bind_visibility的方法,用于与相应的属性创建双向绑定。要定义单向绑定,请使用这些方法的_from和_to变体。
from nicegui import ui
class Demo:
def __init__(self):
self.number = 1
demo = Demo()
v = ui.checkbox('visible', value=True)
with ui.column().bind_visibility_from(v, 'value'):
ui.slider(min=1, max=3).bind_value(demo, 'number')
ui.toggle({1: 'A', 2: 'B', 3: 'C'}).bind_value(demo, 'number')
ui.number().bind_value(demo, 'number')
ui.run()绑定到变量
如果需要绑定到变量,我们可以先用globals()获取一个包含所有全局变量的字典,然后再绑定。
from nicegui import ui
date = '2025-09-26'
print(globals())
ui.label().bind_text_from(globals(), 'date')
ui.run()转换函数
- backward: 当数据从数据模型流向 UI元素 时,backward函数会被调用。它接收来自模型的值,并返回一个处理过的新值以更新UI元素。
- forward: 当数据从 UI元素 流向数据模型 时,forward函数会被调用。它接收来自UI元素的值,并返回一个处理过的新值以更新数据模型。
from nicegui import ui
i = ui.input(value='Lorem ipsum')
ui.label().bind_text_from(i, 'value',
backward=lambda text: f'{len(text)} characters')
ui.run()from nicegui import ui
data = {'text': 'Hello'}
ui.input(label='Enter some text').bind_value_to(
data, 'text',
forward=lambda text: text.upper()
)
ui.label().bind_text_from(data, 'text', backward=lambda text: f"Model value is: {text}")
ui.run()绑定到存储
绑定也适用于app.storage。
行动与事件
预定义事件
对于预定义事件,比如说on_click等,我们直接给它传入一个可调用对象。
Error
一个关键的错误观念要避免:
你传递给 on_click 的不是函数执行后的结果,而是函数本身。
看一个例子:
from nicegui import ui
def show_message():
ui.notify('你好!')
# 这会立刻执行show_message()函数,显示通知,然后把函数的返回值 None 传给 on_click
# 按钮点击时什么都不会发生,因为它的“任务”是 None
ui.button('错误的按钮', on_click=show_message())
# 这里我们传递的是 show_message 这个函数对象本身
# 按钮收到了这个“任务”,并会在被点击时去执行它
ui.button('正确的按钮', on_click=show_message)
ui.run()如果你的任务非常简单,只有一行代码,我们更应该使用lambda表达式。
from nicegui import ui
# 直接在 on_click 参数里定义一个临时的、匿名的任务
ui.button('hello', on_click=lambda: ui.notify('你好!'))
ui.run()这里的 lambda: ui.notify(...) 就创建了一个临时的、没有名字的可调用对象。这个函数对象本身被赋值给了 on_click 参数。
通用事件
NiceGUI 为最常见的事件提供了方便的快捷方式,比如 ui.button 的 on_click 参数和 ui.input 的 on_change 参数。这些是“专用接口”,简单直接。
但是,前端世界(浏览器中的 JavaScript)有成百上千种不同的事件(比如鼠标移动、键盘按下、元素获得焦点、滚动、拖拽等等)。NiceGUI 的开发者不可能为每一种事件都创建一个专门的 on_... 参数。.on() 方法就是为了解决这个问题而生的。它允许你监听任何前端支持的事件,并将这个事件与后端的 Python 函数连接起来。
Example
例子: 文档中的 mousemove (鼠标移动)。
# ui.button 没有 on_mousemove 这个参数 # 所以我们必须使用 .on() 来监听鼠标移动事件
ui.button(‘C’).on(‘mousemove’, lambda: ui.notify(‘You moved on button C.‘))
我们初学者只需要了解这么多即可,大多数时候我们用预定义事件就够了。
异步事件
在处理耗时任务时,使用异步事件不会冻结UI,整个界面可以保持完全可交互。
Tip
黄金法则:只要你的事件处理函数中包含任何可能耗时的I/O操作,就应该使用异步。 以下是典型的应该使用异步事件的场景:
- 网络请求
- 数据库操作
- 文件读写
- 运行子进程执行外部命令并等待其完成
这个版本展示了在等待时,另一个按钮依然可以点击:
import asyncio
from nicegui import ui
async def long_task():
ui.notify('开始异步等待...')
await asyncio.sleep(5)
ui.notify('异步等待结束.')
ui.button('启动异步任务 (5s)', on_click=long_task)
ui.button('点我测试响应', on_click=lambda: ui.notify('UI依然响应!'))
ui.run()这个版本UI会被冻结,必须等同步等待结束后,另一个按钮才能被点击:
import time
from nicegui import ui
def long_task():
ui.notify('开始同步等待...')
time.sleep(5) # 阻塞操作
ui.notify('同步等待结束.')
ui.button('启动同步任务 (5s)', on_click=long_task)
ui.button('点我测试响应', on_click=lambda: ui.notify('UI依然响应!'))
ui.run()计时器
NiceGUI 诞生的一个主要驱动力是需要一种简单的方法来定期更新界面,例如显示包含传入测量值的图表。计时器会以给定的间隔重复执行回调。 参数:
interval: 调用计时器的间隔(可以在运行时更改)callback: 间隔结束后执行的函数或协程active: 是否应该执行回调(可以在运行时更改)once: 回调是否仅在间隔指定的延迟后执行一次(默认值:False)immediate: 是否应立即执行回调(默认值:True,如果once为True则忽略,在版本 2.9.0 中添加)
from datetime import datetime
from nicegui import ui
label = ui.label()
ui.timer(1.0, lambda: label.set_text(f'{datetime.now():%X}'))
ui.run()使用timer.cancel取消计时器后,它将无法再被激活。
from nicegui import ui
slider = ui.slider(min=0, max=1, value=0.5)
timer = ui.timer(0.1, lambda: slider.set_value((slider.value + 0.01) % 1.0))
ui.switch('active').bind_value_to(timer, 'active')
ui.button('Cancel', on_click=timer.cancel)
ui.run()页面和路由
私有页面(@ui.page装饰器)
基本语法:
from nicegui import ui
@ui.page('/other_page', title='other_page')
def other_page():
ui.label('Welcome to the other side')
@ui.page('/dark_page', dark=True)
def dark_page():
ui.label('Welcome to the dark side')
ui.link('Visit other page', other_page)
ui.link('Visit dark page', dark_page)
ui.run()- 创建方式: 使用
@ui.page('/some_path')装饰器来标记一个函数。 - 核心特性: 每个用户(或每个浏览器标签页)访问时,都会独立执行一次这个函数,创建一个全新的、隔离的页面实例。
- 适用场景: 需要为每个用户提供独立状态的页面,比如用户个人资料页、购物车等。
每次访问都会发现页面是不同的uuid:
from nicegui import ui
from uuid import uuid4
@ui.page('/private_page')
async def private_page():
ui.label(f'private page with ID {uuid4()}')
ui.label(f'shared auto-index page with ID {uuid4()}')
ui.link('private page', private_page)
ui.run()完整参数列表:
path: 定义这个新页面的 URL 路由。这个值必须以斜杠/开头。例如,path='/products'会让这个页面在用户访问http://your-server/products时显示。title: (可选) 设置浏览器标签页上显示的标题。viewport: (可选) 设置页面<meta name="viewport" ...>标签的内容,用于控制移动设备上的布局和缩放。favicon: (可选) 设置浏览器标签页上的小图标。可以是一个相对文件路径或一个绝对 URL。如果未提供,则使用 NiceGUI 默认图标。dark: 控制此页面是否使用 Quasar 的暗黑模式。如果未设置,则遵循ui.run()命令的全局dark参数。language: 设置页面的语言(例如'zh-CN')。如果未设置,则遵循ui.run()命令的全局language参数。response_timeout: 被装饰的页面构建函数允许执行的最长时间,默认为3.0秒。如果超时,将返回错误。reconnect_timeout: 服务器等待断开连接的浏览器重新连接的最长时间。如果未设置,则遵循ui.run()命令的全局reconnect_timeout参数。api_router: (高级用法) 指定一个自定义的 FastAPI APIRouter 实例来注册此页面路由,默认为None并使用默认路由器。kwargs: (高级用法) 传递额外的关键字参数给底层的 FastAPI 的@app.get()装饰器,用于 API 文档生成等高级配置。
共享的自动索引页
- 创建方式: 任何没有被
@ui.page装饰器包裹的 UI 元素,都会被自动放置在根路径/的一个页面上。 - 核心特性: 这个页面在服务器启动时只创建一次,所有连接到该服务器的用户看到的都是同一个页面实例。一个用户的操作会实时反映在所有其他用户的屏幕上。
- 适用场景: 需要数据共享和实时协作的仪表盘(Dashboard)、聊天室、公共展示页面等。
自动索引页面上显示的 ID 在浏览器重新加载页面时保持不变: 代码示例
页面布局
你可以在任何页面(私有或共享)中添加复杂的布局元素,这些元素来自于 Quasar 框架:
ui.header: 在页面顶部创建固定的或可滚动的页眉。ui.footer: 在页面底部创建页脚。ui.left_drawer/ui.right_drawer: 创建左侧或右侧的抽屉式导航栏或菜单。ui.page_sticky: 将元素“粘”在屏幕的特定位置。
sub_pages
注意:这是一个实验性功能,API 可能会发生变化。
什么是单页应用 (SPA)?
传统的网站,你每点击一个链接(比如从 / 到 /other),浏览器都会向服务器发送一个全新的请求,然后服务器返回一个完整的 HTML 页面,浏览器会整个刷新来显示新内容。
而在单页应用中,当你点击链接时,页面不会整体刷新。相反,只有页面中需要变化的部分内容会被动态地替换掉。这通常是通过 JavaScript 在前端实现的,给用户的感觉是应用响应更快、更流畅,就像一个桌面应用。
NiceGUI 的 ui.sub_pages 就是用来实现这个效果的工具。
Question
这里等之后功能成熟了再来了解。
参数注入
得益于底层的 FastAPI,页面函数可以直接从 URL 中接收参数
路径参数
from nicegui import ui
User = {
"name": "",
}
def handle_greet():
ui.navigate.to(f'/greet/{User["name"]}')
# 1. 定义一个页面,URL路径中的一部分 '{name}' 将作为参数
@ui.page('/greet/{name}')
def greet_page(name: str):
# 2. 函数的参数 'name' 会自动接收来自 URL 的值
ui.label(f'Hello, {name}!').classes('text-h4')
ui.input().bind_value_to(User, 'name')
ui.button(text='greet', on_click=handle_greet)
ui.run()查询参数
from nicegui import ui
# 1. 定义一个固定路径的页面
@ui.page('/add')
def add_page(a: int, b: int):
# 2. 函数参数 'a' 和 'b' 会自动从查询字符串中获取
result = a + b
ui.label(f'{a} + {b} = {result}').classes('text-h4')
ui.run()浏览器与交互控制
- 动态修改页面标题: 使用
ui.page_title('新标题')可以在事件处理中随时改变当前页面的标题。 - 浏览器历史导航 (
ui.navigate):提供了一组函数来控制浏览器的行为:ui.navigate.back(): 后退ui.navigate.forward(): 前进ui.navigate.reload(): 刷新ui.navigate.to('URL'): 可以将用户导航到任意内部或外部链接。
- 触发文件下载 (
ui.download): 提供了一组函数来让用户的浏览器下载文件:ui.download.file('local_path.txt'): 下载服务器上的本地文件。ui.download.from_url('/logo.png'): 下载服务器上已注册的静态资源。ui.download.content('内容', '文件名.txt'): 将内存中的字符串或字节作为文件下载。
静态资源与文件服务
- 提供静态文件 (
app.add_static_files):- 可以将服务器上的一个本地文件夹(如 images)映射到一个 URL 路径(如 /static)。
- 这使得浏览器可以通过
/static/my_image.png这样的 URL 访问到服务器上的文件,主要用于图片、CSS、JS 等文件。
- 提供媒体文件 (
app.add_media_files):- 与静态文件类似,但专门为音视频优化。
- 它支持流式传输 (Streaming),允许浏览器进行快进、拖动进度条等操作,这是
add_static_files做不到的。
将HTML添加到页面
你可以通过调用ui.add_head_html or ui.add_body_html将HTML代码注入到页面,这对于添加自定义 CSS 样式或 JavaScript 代码非常有用。
from nicegui import ui
ui.add_head_html('''
<style>
.my-red-label {
color: Crimson;
font-weight: bold;
}
</style>
''')
ui.label('RED').classes('my-red-label')
ui.run()编写API
NiceGUI 基于FastAPI。这意味着你可以使用 FastAPI 的所有功能。例如,除了图形用户界面之外,还可以实现 RESTful API。只需要从nicegui导入app对象即可。
你还可以在页面函数中返回任何其他 FastAPI 响应对象。例如,您可以返回一个,RedirectResponse以便在满足某些条件时将用户重定向到另一个页面。
from nicegui import app, ui
# 使用 @app.get 装饰器来定义一个 API 路由, 这和 FastAPI 的用法完全一样
@app.get('/api/hello')
def hello_api():
# 返回一个 Python 字典, FastAPI 会自动将其转换为 JSON 格式的响应
return {"message": "Hello from the API!"}
ui.run()样式控制
在NiceGUI中,最简单的样式定义方式就是使用Tailwind CSS。Tailwind是一个“功能类优先”的CSS框架,你不需要写CSS代码,而是通过组合预设的类名来构建样式。NiceGUI对其提供了卓越的支持。
在 NiceGUI 中,你有两种主要方式来使用 Tailwind工具类:
- .classes:一次性传入一个包含多个类的字符串,这是最接近原生 Tailwind HTML 写法的方式,非常适合直接从 Tailwind 官方文档或其他示例中复制代码。我们后面所有的示例都将只使用 .classes() 方法。
ui.label('Hello').classes('text-blue-500 font-bold')- ** .tailwind** :链式调用,非常 Pythonic。
ui.label('Hello').tailwind.text_color('blue-400').font_size('5xl')安装和部署
服务器托管
打包成windows应用
ios/安卓(PWA)
渐进式web应用,从浏览器直接“安装到桌面”。
