窗口管理:创建、多窗口与装饰
桌面应用与网页的一大区别是窗口本身是可编程对象:创建、销毁、最大化、置顶、定制标题栏都需要代码控制。Tauri 2.x 中窗口概念由 WebviewWindow 统一表示,本篇讲多窗口管理、窗口间通信与无边框窗口的实现。
一、静态与动态两种创建方式
静态窗口在 tauri.conf.json 中声明,应用启动即创建:
{
"app": {
"windows": [
{ "label": "main", "title": "主窗口", "width": 900, "height": 600 },
{ "label": "settings", "title": "设置", "width": 400, "height": 300, "visible": false }
]
}
}
label 是窗口的唯一标识,后续所有 API 都靠它定位。动态创建用 Rust 代码:
use tauri::WebviewWindowBuilder;
#[tauri::command]
fn open_docs(app: tauri::AppHandle) -> Result<(), String> {
WebviewWindowBuilder::new(
&app,
"docs",
tauri::WebviewUrl::App("index.html".into()),
)
.title("文档")
.inner_size(800.0, 600.0)
.build()
.map_err(|e| e.to_string())?;
Ok(())
}
| 方式 | 适用场景 |
|---|---|
| 配置声明 | 启动即需要的固定窗口 |
| 代码创建 | 按需弹出的子窗口、详情窗口 |
| JS API 创建 | 纯前端逻辑驱动的窗口 |
也可以用 JS 端的 WebviewWindow API 创建,适合前端完全主导 UI 的项目,API 形态以官方文档为准。
二、多窗口与窗口间通信
多窗口共享同一个 Rust 核心进程,窗口间通信天然走 Rust 中转或事件系统。常用模式是在一个窗口里 emit,另一个窗口监听。Rust 端也可以按 label 定向发送:
use tauri::{AppHandle, Emitter};
fn focus_main(app: &AppHandle) {
if let Some(window) = app.get_webview_window("main") {
let _ = window.emit("switch-tab", "settings");
}
}
多窗口开发的常见坑是重复创建:相同 label 的窗口已存在时再次 build 会报错,创建前先 get_webview_window 判空。状态共享建议放在 Rust 端统一管理,见「全局状态:tauri::State 与共享数据」。
三、常用窗口属性与控制
窗口运行时操作集中在 WebviewWindow 类型上:
| 操作 | 方法 |
|---|---|
| 显示与隐藏 | show、hide |
| 最大化 | maximize、unmaximize |
| 最小化 | minimize |
| 置顶 | set_always_on_top |
| 全屏 | set_fullscreen |
| 标题 | set_title |
| 居中 | center |
前端也有对应 JS API,例如 getCurrentWindow().maximize(),适合做自定义标题栏按钮。
四、无边框与自定义标题栏
把窗口设为无边框后,需要自己实现拖拽与窗口按钮。关键是为拖拽区域添加 data-tauri-drag-region 属性:
<template>
<div class="titlebar" data-tauri-drag-region>
<span>我的应用</span>
<button @click="minimize">-</button>
<button @click="close">×</button>
</div>
</template>
<script setup>
import { getCurrentWindow } from '@tauri-apps/api/window'
const minimize = () => getCurrentWindow().minimize()
const close = () => getCurrentWindow().close()
</script>
配合 decorations: false 与 transparent 可以做出各种异形窗口、悬浮小组件。需要注意 Windows 上无边框窗口的阴影、圆角与系统按钮需要自行处理,不同平台表现不一致,建议在目标平台实测。
五、窗口生命周期
窗口创建与销毁可以挂回调,做初始化与资源清理。setup 钩子里可以拿到所有窗口并注入逻辑;on_window_event 处理关闭、聚焦等事件:
use tauri::{WindowEvent};
fn main() {
tauri::Builder::default()
.on_window_event(|window, event| {
if let WindowEvent::CloseRequested { .. } = event {
println!("窗口关闭:{}", window.label());
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
托盘与窗口联动是常见需求,比如关闭窗口时只隐藏、从托盘再恢复,详见「系统托盘、菜单与全局快捷键」。多窗口架构设计与消息流排查,可以借鉴 Obsidian 指南 里的多视图通信思路,Rust 端类型基础见 Rust 入门,用 AI 辅助生成窗口管理样板代码的方式见 Claude Code 指南。
六、窗口体验的细节打磨
窗口交互的细节直接决定应用的「原生感」。以下这些点值得在发布前逐项检查:
| 检查项 | 目标 |
|---|---|
| 恢复窗口位置 | 记住上次的位置与尺寸 |
| 单实例保护 | 重复打开时聚焦已有窗口 |
| 深浅色标题栏 | 跟随系统主题 |
| 最小尺寸 | 防止窗口缩到不可用 |
| 高 DPI 缩放 | 不同分辨率下文字清晰 |
窗口位置记忆是典型实现:把位置尺寸存到应用数据目录,启动时读取恢复,代码涉及 fs 插件与 State,分别见「官方插件清单:fs、dialog、shell 与 notification」和「全局状态:tauri::State 与共享数据」。
单实例保护在 Windows 与 macOS 体验差异明显:不做处理时双击图标会开出第二个实例,数据状态立刻混乱。做法是在启动时检测已有实例并聚焦其主窗口,实现需要用到单实例插件或系统 API,具体以官方文档为准。
窗口管理的 API 数量多但模式单一:构建器配置初始状态,运行时方法控制动态行为,事件回调响应交互。掌握 builder 模式后其余 API 举一反三。下一章看状态如何在多窗口之间共享。
窗口管理的 API 表面庞大,但日常高频只有十来个:显示、隐藏、聚焦、最大化、居中、关闭。把这十来个练熟,90% 的窗口交互需求当场就能写完。剩下的边角能力等真正需要时查文档即可,不必提前记忆。
顺带一句性能提醒:透明窗口在部分平台有合成开销,老旧设备上会掉帧。是否使用华丽效果,先在目标机器实测再决定,「简单窗口加好的交互逻辑」往往胜过炫技。下一章进入 State 共享,看数据如何在窗口与命令之间流动。
延伸阅读
- 系统托盘、菜单与全局快捷键——窗口生命周期的另一半
- 全局状态:tauri::State 与共享数据——多窗口共享状态的方案
- Rust 入门:WebviewWindow 相关的 Rust 语法
