窗口管理是桌面应用的核心特性之一,良好的窗口管理可以显著提升用户体验。在Web开发中,我们通常被限制在浏览器窗口内,但Tauri允许前端开发者控制应用窗口的方方面面,从而创造出更加原生的体验。
窗口配置基础
初始窗口配置
在tauri.conf.json
文件中,我们可以配置应用启动时的主窗口。Tauri v2提供了更丰富的窗口配置选项:
{
"app": {
"windows": [
{
"label": "main", // 窗口唯一标识符
"title": "My Tauri App", // 窗口标题
"width": 800, // 窗口宽度
"height": 600, // 窗口高度
"x": null, // 窗口X坐标,null表示自动
"y": null, // 窗口Y坐标,null表示自动
"center": true, // 是否居中显示
"resizable": true, // 是否可调整大小
"minWidth": 400, // 最小宽度
"minHeight": 200, // 最小高度
"maxWidth": null, // 最大宽度,null表示不限制
"maxHeight": null, // 最大高度,null表示不限制
"fullscreen": false, // 是否全屏
"focus": true, // 创建时是否获得焦点
"maximized": false, // 是否最大化
"visible": true, // 是否可见
"decorations": true, // 是否显示窗口装饰
"alwaysOnTop": false, // 是否置顶
"skipTaskbar": false, // 是否在任务栏隐藏
"transparent": false, // 是否透明
"shadow": true, // 是否显示阴影
"theme": null, // 窗口主题,"light"或"dark"
"closable": true, // 是否可关闭
"fileDropEnabled": true, // 是否允许拖放文件
"acceptFirstMouse": false, // macOS上是否接受首次鼠标点击
"tabbingIdentifier": null, // macOS上的标签标识符
"titleBarStyle": "Visible", // 标题栏样式,"Visible"、"Transparent"或"Overlay"
"hiddenTitle": false, // macOS上是否隐藏标题
"url": "index.html", // 加载的URL或本地文件路径
"userAgent": null, // 自定义用户代理
"incognito": false, // 是否使用隐身模式
"contentProtected": false, // 是否保护窗口内容不被截图
"cursor": null, // 默认鼠标指针样式
"windowEffects": null, // 窗口视觉效果设置
"maximizable": true, // 是否可最大化
"minimizable": true, // 是否可最小化
"webviewAttributes": {} // WebView的额外属性
}
]
}
}
这些配置选项让你能够精确控制窗口的外观和行为。每个选项都有其特定用途:
- 基本属性:
width
、height
、title
等控制窗口的基本外观 - 位置控制:
x
、y
、center
决定窗口的初始位置 - 大小限制:
minWidth
、minHeight
、maxWidth
、maxHeight
控制窗口大小范围 - 状态控制:
maximized
、fullscreen
、visible
等控制窗口的初始状态 - 外观设置:
decorations
、transparent
、shadow
等控制窗口的视觉效果 - 行为控制:
resizable
、closable
、fileDropEnabled
等控制窗口的交互行为
窗口标识符
每个窗口都需要一个唯一的标识符(label
),这是通过API操作窗口的关键。默认主窗口的标识符是main
。如果你需要创建多窗口应用,需要为每个窗口指定唯一的标识符:
{
"app": {
"windows": [
{
"label": "main", // 主窗口标识符
"title": "主窗口"
},
{
"label": "settings", // 设置窗口标识符
"title": "设置",
"width": 500,
"height": 400,
"visible": false // 初始不可见
}
]
}
}
窗口父子关系
Tauri v2支持设置窗口的父子关系,这对于创建模态窗口或工具窗口特别有用:
{
"app": {
"windows": [
{
"label": "main",
"title": "主窗口"
},
{
"label": "modal",
"title": "模态窗口",
"parent": "main", // 指定父窗口
"width": 400,
"height": 300,
"decorations": false, // 无边框窗口
"transparent": true, // 透明背景
"alwaysOnTop": true // 保持在最上层
}
]
}
}
窗口操作API
Tauri提供了丰富的窗口操作API,让你可以从前端控制窗口行为。在Tauri v2中,这些API更加类型安全和易用。
基本窗口操作
import { Window } from '@tauri-apps/api/window';
// 获取当前窗口实例
const window = Window.getCurrent();
// 基本窗口操作
async function windowControls() {
// 最小化
await window.minimize();
// 最大化
await window.maximize();
// 恢复原始大小
await window.unmaximize();
// 切换全屏
await window.setFullscreen(true);
// 关闭窗口
await window.close();
// 获取窗口状态
const isMaximized = await window.isMaximized();
const isFullscreen = await window.isFullscreen();
const isVisible = await window.isVisible();
// 设置窗口大小
await window.setSize(new LogicalSize(800, 600));
// 设置窗口位置
await window.setPosition(new LogicalPosition(100, 100));
// 设置窗口标题
await window.setTitle('新标题');
// 设置窗口焦点
await window.setFocus();
// 设置窗口置顶
await window.setAlwaysOnTop(true);
// 设置窗口透明度
await window.setOpacity(0.8);
}
窗口状态监听
Tauri v2提供了更强大的事件监听系统,让你能够响应窗口的各种状态变化:
import { Window } from '@tauri-apps/api/window';
function setupWindowListeners() {
const window = Window.getCurrent();
// 窗口移动
const unlisten1 = window.onMoved(({ x, y }) => {
console.log(`窗口移动到 (${x}, ${y})`);
});
// 窗口缩放
const unlisten2 = window.onResized(({ width, height }) => {
console.log(`窗口大小调整为 ${width}x${height}`);
});
// 窗口焦点变化
const unlisten3 = window.onFocusChanged(({ focused }) => {
console.log(`窗口${focused ? '获得' : '失去'}焦点`);
});
// 窗口即将关闭
const unlisten4 = window.onCloseRequested((event) => {
// 阻止默认关闭行为
event.preventDefault();
// 显示确认对话框
if (confirm('确定要关闭吗?')) {
window.close();
}
});
// 窗口主题变化
const unlisten5 = window.onThemeChanged(({ theme }) => {
console.log(`窗口主题变为: ${theme}`);
});
// 窗口缩放比例变化
const unlisten6 = window.onScaleFactorChanged(({ scaleFactor }) => {
console.log(`窗口缩放比例变为: ${scaleFactor}`);
});
// 清理监听器
return () => {
unlisten1();
unlisten2();
unlisten3();
unlisten4();
unlisten5();
unlisten6();
};
}
多窗口管理
Tauri v2提供了更强大的多窗口管理能力,支持创建、查找和控制多个窗口:
import { Window } from '@tauri-apps/api/window';
async function openSettingsWindow() {
// 检查窗口是否已存在
const existingWindow = Window.getByLabel('settings');
if (existingWindow) {
// 如果已存在,激活它
await existingWindow.setFocus();
return existingWindow;
}
// 创建新窗口
const settingsWindow = new Window('settings', {
url: 'settings.html', // 加载的HTML文件
title: '应用设置',
width: 500,
height: 400,
center: true,
resizable: true,
parent: Window.getCurrent(), // 设置父窗口
decorations: false, // 无边框窗口
transparent: true, // 透明背景
alwaysOnTop: true // 保持在最上层
});
// 监听窗口事件
settingsWindow.once('tauri://created', () => {
console.log('设置窗口已创建');
});
settingsWindow.once('tauri://error', (e) => {
console.error('设置窗口创建失败:', e);
});
return settingsWindow;
}
// 获取所有窗口
async function getAllWindows() {
const windows = await Window.getAll();
console.log('所有窗口:', windows.map(w => w.label));
}
// 获取主窗口
async function getMainWindow() {
const mainWindow = await Window.getByLabel('main');
return mainWindow;
}
窗口间通信
Tauri v2提供了多种窗口间通信的方式:
通过事件通信
import { Window } from '@tauri-apps/api/window';
import { emit, listen } from '@tauri-apps/api/event';
// 在主窗口中发送事件
function sendDataToSettings(data) {
emit('data-to-settings', data);
}
// 在设置窗口中监听事件
function listenForData() {
return listen('data-to-settings', (event) => {
console.log('收到数据:', event.payload);
// 处理数据...
});
}
通过窗口API直接发送
import { Window } from '@tauri-apps/api/window';
// 在一个窗口中向另一个窗口发送消息
async function sendMessageToMain(message) {
const mainWindow = await Window.getByLabel('main');
if (mainWindow) {
await mainWindow.emit('message-from-settings', message);
}
}
自定义窗口
无边框窗口
Tauri v2提供了更灵活的无边框窗口配置,让你能够创建完全自定义的窗口界面:
{
"app": {
"windows": [
{
"decorations": false, // 禁用标准窗口装饰
"transparent": true, // 启用透明背景
"titleBarStyle": "Overlay", // 使用覆盖式标题栏
"shadow": true, // 保留窗口阴影
"contentProtected": true // 保护窗口内容
}
]
}
}
然后在前端实现自定义标题栏:
<template>
<div class="custom-titlebar">
<div class="titlebar-drag-region"></div>
<div class="window-title">{{ title }}</div>
<div class="window-controls">
<button @click="minimize" class="control-button">
<span>🗕</span>
</button>
<button @click="toggleMaximize" class="control-button">
<span>{{ isMaximized ? '🗗' : '🗖' }}</span>
</button>
<button @click="close" class="control-button close">
<span>✕</span>
</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { Window } from '@tauri-apps/api/window';
const title = ref('My Custom App');
const isMaximized = ref(false);
const window = Window.getCurrent();
async function minimize() {
await window.minimize();
}
async function toggleMaximize() {
if (isMaximized.value) {
await window.unmaximize();
isMaximized.value = false;
} else {
await window.maximize();
isMaximized.value = true;
}
}
async function close() {
await window.close();
}
let unlisten;
onMounted(async () => {
// 监听窗口状态变化
unlisten = await window.onResized(() => {
// 检查是否最大化
isMaximized.value = await window.isMaximized();
});
});
onUnmounted(() => {
if (unlisten) unlisten();
});
</script>
<style scoped>
.custom-titlebar {
height: 32px;
background: #2b2b2b;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
user-select: none;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
}
.titlebar-drag-region {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 100%;
-webkit-app-region: drag; /* 允许拖拽 */
}
.window-title {
margin-left: 12px;
font-size: 12px;
}
.window-controls {
display: flex;
-webkit-app-region: no-drag; /* 按钮区域不可拖拽 */
}
.control-button {
width: 46px;
height: 32px;
border: none;
background: transparent;
color: white;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.control-button:hover {
background: rgba(255, 255, 255, 0.1);
}
.control-button.close:hover {
background: #e81123;
}
</style>
系统托盘
Tauri v2提供了更强大的系统托盘功能,支持自定义图标、菜单和事件处理:
// src-tauri/src/main.rs
use tauri::{
CustomMenuItem,
SystemTray,
SystemTrayMenu,
SystemTrayMenuItem,
SystemTrayEvent,
Manager
};
fn main() {
// 定义托盘菜单项
let quit = CustomMenuItem::new("quit".to_string(), "退出");
let show = CustomMenuItem::new("show".to_string(), "显示窗口");
let hide = CustomMenuItem::new("hide".to_string(), "隐藏窗口");
let settings = CustomMenuItem::new("settings".to_string(), "设置");
// 创建托盘菜单
let tray_menu = SystemTrayMenu::new()
.add_item(show)
.add_item(hide)
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(settings)
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(quit);
// 创建系统托盘
let system_tray = SystemTray::new()
.with_menu(tray_menu)
.with_icon(tauri::Icon::Raw(include_bytes!("../icons/icon.png").to_vec()));
tauri::Builder::default()
.system_tray(system_tray)
.on_system_tray_event(|app, event| match event {
// 处理托盘菜单点击
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
"quit" => {
app.exit(0);
}
"show" => {
let window = app.get_window("main").unwrap();
window.show().unwrap();
window.set_focus().unwrap();
}
"hide" => {
let window = app.get_window("main").unwrap();
window.hide().unwrap();
}
"settings" => {
let window = app.get_window("main").unwrap();
window.emit("open-settings", ()).unwrap();
}
_ => {}
},
// 托盘图标左键点击
SystemTrayEvent::LeftClick { .. } => {
let window = app.get_window("main").unwrap();
if window.is_visible().unwrap() {
window.hide().unwrap();
} else {
window.show().unwrap();
window.set_focus().unwrap();
}
}
// 托盘图标右键点击
SystemTrayEvent::RightClick { .. } => {
// 可以在这里显示自定义菜单
}
_ => {}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
在前端监听托盘事件:
import { Window } from '@tauri-apps/api/window';
import { listen } from '@tauri-apps/api/event';
// 监听设置菜单点击事件
listen('open-settings', () => {
// 打开设置窗口
openSettingsWindow();
});
窗口内容管理
单页面应用与路由
对于单页面应用(SPA),你可以利用前端路由管理不同页面,而不是创建多个窗口:
// Vue Router示例
import { createRouter, createWebHashHistory } from 'vue-router';
const router = createRouter({
history: createWebHashHistory(), // 使用Hash模式路由
routes: [
{ path: '/', component: Home },
{ path: '/settings', component: Settings },
{ path: '/profile', component: Profile }
]
});
窗口内加载URL
Tauri v2提供了更灵活的URL加载方式:
import { Window } from '@tauri-apps/api/window';
// 创建窗口并加载外部URL
const docsWindow = new Window('docs', {
url: 'https://tauri.app/docs/',
title: 'Tauri文档',
});
// 在现有窗口中加载新URL
async function navigateToHelp() {
const window = Window.getCurrent();
await window.loadUrl('/help.html');
}
嵌入网页内容
Tauri v2支持在应用中嵌入外部网页内容,并提供了更好的安全控制:
<template>
<div class="webview-container">
<iframe
:src="url"
class="webview"
@load="onFrameLoad"
sandbox="allow-same-origin allow-scripts"
></iframe>
</div>
</template>
<script setup>
import { ref } from 'vue';
const url = ref('https://tauri.app');
function onFrameLoad(event) {
console.log('Frame loaded:', event);
}
</script>
<style scoped>
.webview-container {
width: 100%;
height: 100%;
overflow: hidden;
}
.webview {
width: 100%;
height: 100%;
border: none;
}
</style>
响应式设计与窗口缩放
动态调整布局
Tauri v2提供了更好的窗口缩放事件支持,让你能够实现更流畅的响应式设计:
<template>
<div class="app-container" :class="{ 'compact': isCompact }">
<div class="sidebar" v-if="!isCompact">
<!-- 侧边栏内容 -->
</div>
<div class="main-content">
<!-- 主要内容 -->
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { Window } from '@tauri-apps/api/window';
const isCompact = ref(false);
const window = Window.getCurrent();
let unlisten;
async function checkWindowSize() {
const { width } = await window.innerSize();
isCompact.value = width < 768;
}
onMounted(async () => {
await checkWindowSize();
unlisten = await window.onResized(() => {
checkWindowSize();
});
});
onUnmounted(() => {
if (unlisten) unlisten();
});
</script>
<style scoped>
.app-container {
display: flex;
height: 100vh;
}
.sidebar {
width: 250px;
background: #f5f5f5;
}
.main-content {
flex: 1;
}
.compact .main-content {
width: 100%;
}
</style>
保存和恢复窗口状态
Tauri v2提供了更可靠的窗口状态管理:
import { Window } from '@tauri-apps/api/window';
import { Store } from 'tauri-plugin-store-api';
// 创建存储
const store = new Store('window-state.dat');
// 保存窗口状态
async function saveWindowState() {
try {
const window = Window.getCurrent();
const position = await window.outerPosition();
const size = await window.outerSize();
const isMaximized = await window.isMaximized();
await store.set('windowState', {
x: position.x,
y: position.y,
width: size.width,
height: size.height,
isMaximized
});
await store.save();
} catch (error) {
console.error('保存窗口状态失败:', error);
}
}
// 恢复窗口状态
async function restoreWindowState() {
try {
const window = Window.getCurrent();
const state = await store.get('windowState');
if (state) {
// 先设置位置和大小
if (state.width && state.height) {
await window.setSize(new LogicalSize(state.width, state.height));
}
if (state.x !== undefined && state.y !== undefined) {
await window.setPosition(new LogicalPosition(state.x, state.y));
}
// 然后处理最大化状态
if (state.isMaximized) {
await window.maximize();
}
}
} catch (error) {
console.error('恢复窗口状态失败:', error);
}
}
// 在窗口即将关闭时保存状态
Window.getCurrent().onCloseRequested(async (event) => {
// 保存窗口状态
await saveWindowState();
});
// 应用启动时恢复状态
document.addEventListener('DOMContentLoaded', async () => {
await restoreWindowState();
});
小结
作为前端开发者,Tauri的窗口管理功能让你能够打造专业级的桌面应用体验。通过本文介绍的技术,你可以:
- 配置和控制窗口的基本属性
- 创建多窗口应用并实现窗口间通信
- 设计自定义无边框窗口和标题栏
- 添加系统托盘功能
- 管理窗口内容和导航
- 实现响应式设计以适应窗口缩放
- 保存和恢复窗口状态
这些能力使你能够超越传统Web应用的限制,创建具有原生应用外观和体验的桌面软件。
在下一篇文章中,我们将探讨Tauri的系统集成功能,包括通知、快捷键和自动启动等特性。