在桌面应用中,多窗口是很常见的需求——比如打开一个新窗口展示设置、预览内容,或者让主窗口和辅助窗口协同工作。Tauri 提供了一套轻量的窗口管理 API,从创建到通信都非常直接。
1. 创建新窗口
最简单的创建方式是在前端通过 Tauri 的 @tauri-apps/api 调用 WebviewWindow:
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
const settingsWin = new WebviewWindow('settings', {
url: '/settings.html',
title: '设置',
width: 480,
height: 320,
resizable: false
});
- 第一个参数
'settings'是窗口的唯一标签(label),后续获取、关该窗口都需要用到它。 url指向前端路由的一个页面(如果使用 React/Vue 等单页应用,可配合路由)。- 其余配置项可控制窗口尺寸、是否可缩放、是否带标题栏等,详见 官方文档。
如果需要从 Rust 后端创建窗口,可以使用 tauri::WebviewWindowBuilder:
use tauri::WebviewWindowBuilder;
#[tauri::command]
fn open_settings(app: tauri::AppHandle) -> Result<(), String> {
WebviewWindowBuilder::new(
&app,
"settings",
tauri::WebviewUrl::App("/settings".into()),
)
.title("设置")
.inner_size(480.0, 320.0)
.resizable(false)
.build()
.map_err(|e| e.to_string())?;
Ok(())
}
2. 获取已存在的窗口实例
窗口创建后,可以通过标签(label)获取它的实例,以便后续控制(如聚焦、关闭、发送事件)。
前端获取:
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
const settingsWin = WebviewWindow.getByLabel('settings');
if (settingsWin) {
settingsWin.setFocus();
}
Rust 获取:
use tauri::Manager;
#[tauri::command]
fn focus_settings(app: tauri::AppHandle) -> Result<(), String> {
if let Some(settings_win) = app.get_webview_window("settings") {
settings_win.set_focus().map_err(|e| e.to_string())?;
}
Ok(())
}
app.get_webview_window("label") 返回 Option,如果窗口不存在或已被销毁会得到 None。
3. 窗口间通信
Tauri 的窗口之间不能直接互相访问 DOM 或内存,必须通过事件系统或通过 Rust 后端做中转。推荐使用 Tauri 内置的事件系统,它既可从 Rust 发出,也可从某个窗口发出并广播给其他窗口。
方式一:前端事件(前端 → 其他前端)
@tauri-apps/api 提供了 emit 和 listen,可以跨窗口通信:
发送方窗口:
import { emit } from '@tauri-apps/api/event';
// 向所有窗口广播一个事件(包括自己)
emit('some-event', { message: 'hello from settings' });
接收方窗口:
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('some-event', (event) => {
console.log('收到事件:', event.payload);
// 根据 payload 更新界面
});
// 使用完后记得取消监听
// unlisten();
注意:默认 emit 会广播到所有窗口(包括发起者自己)。如果只想发给特定窗口,可以使用 emitTo 并指定目标窗口的 label。
方式二:通过 Rust 后端中转
有时需要先处理逻辑再把结果发给窗口,或需要控制哪些窗口能接收事件,可以使用 Rust 的 app_handle.emit 或 window.emit。
#[tauri::command]
fn send_message_to_main(app: tauri::AppHandle, msg: String) -> Result<(), String> {
if let Some(main_win) = app.get_webview_window("main") {
main_win.emit("message-to-main", msg).map_err(|e| e.to_string())?;
}
Ok(())
}
前端监听方法相同,都是通过 listen('message-to-main', ...)。
方式三:共享状态(通过 Rust)
如果多个窗口需要访问同一份数据(如用户设置),可以让 Rust 端维护一个 Mutex<State> 的状态,然后各个窗口通过调用同一个命令来读写,这样天然线程安全且能保证数据一致性。
实用建议
- 使用 前端路由 区分不同窗口加载的页面,避免为每个窗口单独创建 HTML 文件。例如用 React Router,
/是主窗口,/settings是设置窗口。 - 尽量通过事件通信而非直接获取其他窗口的 WebView window 对象,因为那破坏了沙箱隔离,且 Tauri 本身就不提供这种能力。
- 别忘了在窗口关闭时取消事件监听,避免内存泄漏。
- 如果想限制窗口只能打开一个,可以在创建前用
getByLabel判断是否已存在,若存在则聚焦并返回,而不是重复创建。
Tauri 的多窗口模型虽然简单,但结合事件系统已经可以覆盖绝大多数桌面应用场景,包括浮动工具窗口、向导、预览、自定义对话框等。