多窗口管理 #

一、多窗口概述 #

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