人人都会AI编程

多窗口创建、窗口实例获取、窗口间通信

更新时间:2026-07-11

在桌面应用中,多窗口是很常见的需求——比如打开一个新窗口展示设置、预览内容,或者让主窗口和辅助窗口协同工作。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 提供了 emitlisten,可以跨窗口通信:

发送方窗口:

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.emitwindow.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 的多窗口模型虽然简单,但结合事件系统已经可以覆盖绝大多数桌面应用场景,包括浮动工具窗口、向导、预览、自定义对话框等。