多窗口管理 #
一、多窗口概述 #
1.1 多窗口架构 #
text
┌─────────────────────────────────────────────────────────────┐
│ 多窗口应用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 主窗口 │ │ 设置窗口 │ │ 详情窗口 │ │
│ │ main │ │ settings │ │ detail │ │
│ │ │ │ │ │ │ │
│ │ 主界面 │ │ 配置页面 │ │ 详情页面 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ └───────────────┼───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 窗口管理器 │ │
│ │ Window Manager │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 使用场景 #
| 场景 | 说明 |
|---|---|
| 设置窗口 | 独立的配置界面 |
| 详情窗口 | 显示详细信息 |
| 登录窗口 | 独立的认证界面 |
| 工具窗口 | 辅助工具面板 |
二、窗口管理器 #
2.1 创建窗口管理器 #
typescript
// WindowManager.ts
import { WebviewWindow, WindowOptions } from '@tauri-apps/api/webviewWindow';
interface WindowConfig {
label: string;
options: WindowOptions;
singleton?: boolean;
}
class WindowManager {
private windows: Map<string, WebviewWindow> = new Map();
private configs: Map<string, WindowConfig> = new Map();
register(config: WindowConfig) {
this.configs.set(config.label, config);
}
async open(label: string, data?: unknown) {
const config = this.configs.get(label);
if (!config) {
throw new Error(`Window config not found: ${label}`);
}
// 单例窗口检查
if (config.singleton) {
const existing = await this.get(label);
if (existing) {
await existing.setFocus();
if (data) {
await existing.emit('window-data', data);
}
return existing;
}
}
// 创建新窗口
const window = new WebviewWindow(label, config.options);
this.windows.set(label, window);
// 监听窗口关闭
window.once('tauri://destroyed', () => {
this.windows.delete(label);
});
// 发送初始数据
if (data) {
window.once('tauri://created', async () => {
await window.emit('window-data', data);
});
}
return window;
}
async get(label: string) {
return WebviewWindow.getByLabel(label);
}
async close(label: string) {
const window = await this.get(label);
if (window) {
await window.close();
}
}
async closeAll() {
const all = WebviewWindow.getAll();
for (const window of all) {
await window.close();
}
}
async focus(label: string) {
const window = await this.get(label);
if (window) {
await window.setFocus();
}
}
}
export const windowManager = new WindowManager();
2.2 注册窗口配置 #
typescript
// 注册窗口
windowManager.register({
label: 'settings',
options: {
url: 'settings.html',
title: 'Settings',
width: 500,
height: 600,
center: true,
},
singleton: true,
});
windowManager.register({
label: 'detail',
options: {
url: 'detail.html',
title: 'Detail',
width: 600,
height: 400,
center: true,
},
singleton: false,
});
2.3 使用窗口管理器 #
typescript
// 打开设置窗口
await windowManager.open('settings');
// 打开详情窗口并传递数据
await windowManager.open('detail', { id: 123, name: 'Item' });
// 关闭窗口
await windowManager.close('detail');
// 关闭所有窗口
await windowManager.closeAll();
三、窗口列表 #
3.1 获取所有窗口 #
typescript
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
// 获取所有窗口
const windows = WebviewWindow.getAll();
console.log('All windows:', windows.map(w => w.label));
3.2 获取当前窗口 #
typescript
import { getCurrentWindow } from '@tauri-apps/api/window';
const currentWindow = getCurrentWindow();
console.log('Current window:', currentWindow.label);
3.3 按标签获取窗口 #
typescript
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
const settingsWindow = await WebviewWindow.getByLabel('settings');
if (settingsWindow) {
await settingsWindow.setFocus();
}
四、窗口切换 #
4.1 窗口切换组件 #
tsx
import { useState, useEffect } from 'react';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
function WindowSwitcher() {
const [windows, setWindows] = useState<WebviewWindow[]>([]);
useEffect(() => {
const updateWindows = () => {
setWindows(WebviewWindow.getAll());
};
updateWindows();
// 定期更新窗口列表
const interval = setInterval(updateWindows, 1000);
return () => clearInterval(interval);
}, []);
const focusWindow = async (label: string) => {
const window = await WebviewWindow.getByLabel(label);
if (window) {
await window.setFocus();
}
};
return (
<div className="window-switcher">
{windows.map((window) => (
<button
key={window.label}
onClick={() => focusWindow(window.label)}
>
{window.label}
</button>
))}
</div>
);
}
4.2 窗口菜单 #
tsx
import { useState } from 'react';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
function WindowMenu() {
const [isOpen, setIsOpen] = useState(false);
const windows = WebviewWindow.getAll();
const handleWindowClick = async (window: WebviewWindow) => {
await window.setFocus();
setIsOpen(false);
};
return (
<div className="window-menu">
<button onClick={() => setIsOpen(!isOpen)}>
Window
</button>
{isOpen && (
<ul className="window-list">
{windows.map((window) => (
<li key={window.label}>
<button onClick={() => handleWindowClick(window)}>
{window.label}
</button>
</li>
))}
</ul>
)}
</div>
);
}
五、窗口状态同步 #
5.1 状态同步服务 #
typescript
// WindowStateSync.ts
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { emit, listen } from '@tauri-apps/api/event';
class WindowStateSync {
private state: Map<string, unknown> = new Map();
async broadcast(key: string, value: unknown) {
this.state.set(key, value);
await emit('state-sync', { key, value });
}
async subscribe(callback: (key: string, value: unknown) => void) {
return listen<{ key: string; value: unknown }>(
'state-sync',
(event) => {
this.state.set(event.payload.key, event.payload.value);
callback(event.payload.key, event.payload.value);
}
);
}
get(key: string) {
return this.state.get(key);
}
}
export const windowStateSync = new WindowStateSync();
5.2 使用状态同步 #
typescript
// 主窗口:广播状态变更
await windowStateSync.broadcast('theme', 'dark');
await windowStateSync.broadcast('user', { name: 'Alice' });
// 其他窗口:监听状态变更
const unlisten = await windowStateSync.subscribe((key, value) => {
console.log(`State updated: ${key} =`, value);
if (key === 'theme') {
applyTheme(value as string);
}
});
六、窗口间通信 #
6.1 消息传递 #
typescript
import { emitTo, listen } from '@tauri-apps/api/event';
// 发送消息到指定窗口
async function sendToWindow(label: string, message: unknown) {
await emitTo(label, 'window-message', message);
}
// 接收消息
await listen('window-message', (event) => {
console.log('Received message:', event.payload);
});
6.2 请求-响应模式 #
typescript
// WindowMessenger.ts
import { emitTo, listen } from '@tauri-apps/api/event';
class WindowMessenger {
private requestId = 0;
async request<T>(
targetLabel: string,
action: string,
data: unknown
): Promise<T> {
const id = ++this.requestId;
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Request timeout'));
}, 5000);
listen<{ id: number; result: T }>(
`response-${id}`,
(event) => {
clearTimeout(timeout);
resolve(event.payload.result);
}
);
emitTo(targetLabel, 'request', { id, action, data });
});
}
async respond(requestId: number, targetLabel: string, result: unknown) {
await emitTo(targetLabel, `response-${requestId}`, { id: requestId, result });
}
}
export const messenger = new WindowMessenger();
6.3 使用请求-响应 #
typescript
// 发送请求
const result = await messenger.request<string>(
'main',
'get-config',
{ key: 'theme' }
);
// 处理请求
await listen('request', async (event) => {
const { id, action, data } = event.payload;
if (action === 'get-config') {
const config = getConfig(data.key);
await messenger.respond(id, 'settings', config);
}
});
七、窗口生命周期 #
7.1 窗口创建钩子 #
typescript
// 创建窗口时的钩子
const windowHooks = new Map<string, () => void>();
function registerWindowHook(label: string, hook: () => void) {
windowHooks.set(label, hook);
}
async function createWindowWithHook(label: string, options: WindowOptions) {
const window = new WebviewWindow(label, options);
window.once('tauri://created', () => {
const hook = windowHooks.get(label);
if (hook) {
hook();
}
});
return window;
}
7.2 窗口关闭确认 #
typescript
import { getCurrentWindow } from '@tauri-apps/api/window';
const window = getCurrentWindow();
await window.onCloseRequested(async (event) => {
const confirmed = await showConfirmDialog(
'确定要关闭吗?',
'未保存的更改将丢失。'
);
if (!confirmed) {
event.preventDefault();
}
});
八、窗口分组 #
8.1 窗口分组管理 #
typescript
class WindowGroupManager {
private groups: Map<string, Set<string>> = new Map();
addToGroup(groupName: string, windowLabel: string) {
if (!this.groups.has(groupName)) {
this.groups.set(groupName, new Set());
}
this.groups.get(groupName)!.add(windowLabel);
}
removeFromGroup(groupName: string, windowLabel: string) {
this.groups.get(groupName)?.delete(windowLabel);
}
async closeGroup(groupName: string) {
const labels = this.groups.get(groupName);
if (labels) {
for (const label of labels) {
await windowManager.close(label);
}
}
}
getGroupWindows(groupName: string) {
return Array.from(this.groups.get(groupName) || []);
}
}
export const groupManager = new WindowGroupManager();
8.2 使用窗口分组 #
typescript
// 添加窗口到分组
groupManager.addToGroup('editors', 'editor-1');
groupManager.addToGroup('editors', 'editor-2');
groupManager.addToGroup('editors', 'editor-3');
// 关闭整个分组
await groupManager.closeGroup('editors');
九、窗口布局 #
9.1 窗口位置管理 #
typescript
import { LogicalPosition } from '@tauri-apps/api/dpi';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
async function tileWindows(windows: WebviewWindow[]) {
const screenWidth = window.screen.width;
const screenHeight = window.screen.height;
const count = windows.length;
const cols = Math.ceil(Math.sqrt(count));
const rows = Math.ceil(count / cols);
const windowWidth = screenWidth / cols;
const windowHeight = screenHeight / rows;
for (let i = 0; i < windows.length; i++) {
const col = i % cols;
const row = Math.floor(i / cols);
await windows[i].setPosition(
new LogicalPosition(col * windowWidth, row * windowHeight)
);
await windows[i].setSize(
{ width: windowWidth, height: windowHeight }
);
}
}
9.2 窗口排列 #
typescript
async function cascadeWindows(windows: WebviewWindow[]) {
const offset = 30;
for (let i = 0; i < windows.length; i++) {
await windows[i].setPosition(
new LogicalPosition(offset * i, offset * i)
);
await windows[i].setFocus();
}
}
十、最佳实践 #
10.1 单例窗口 #
typescript
async function openSingletonWindow(label: string, options: WindowOptions) {
const existing = await WebviewWindow.getByLabel(label);
if (existing) {
await existing.setFocus();
return existing;
}
return new WebviewWindow(label, options);
}
10.2 窗口池 #
typescript
class WindowPool {
private pool: WebviewWindow[] = [];
private maxSize: number;
constructor(maxSize: number = 5) {
this.maxSize = maxSize;
}
async acquire(options: WindowOptions): Promise<WebviewWindow> {
if (this.pool.length > 0) {
const window = this.pool.pop()!;
await window.show();
return window;
}
return new WebviewWindow(`pool-${Date.now()}`, options);
}
async release(window: WebviewWindow) {
if (this.pool.length < this.maxSize) {
await window.hide();
this.pool.push(window);
} else {
await window.close();
}
}
}
十一、调试技巧 #
11.1 窗口状态日志 #
typescript
function logAllWindows() {
const windows = WebviewWindow.getAll();
console.log('=== Window Status ===');
windows.forEach(async (window) => {
console.log({
label: window.label,
title: await window.title(),
visible: await window.isVisible(),
focused: await window.isFocused(),
});
});
}
11.2 窗口事件追踪 #
typescript
import { listen } from '@tauri-apps/api/event';
// 追踪所有窗口事件
await listen('window-event', (event) => {
console.log('Window event:', event);
});
十二、总结 #
12.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 窗口管理器 | 统一管理窗口创建和销毁 |
| 窗口列表 | 获取和遍历所有窗口 |
| 窗口通信 | 通过事件进行消息传递 |
| 状态同步 | 保持多窗口状态一致 |
| 窗口分组 | 按功能分组管理窗口 |
12.2 下一步 #
现在你已经掌握了多窗口管理,接下来让我们学习 窗口通信,深入了解窗口间的数据交换机制!
最后更新:2026-03-28