Tauri窗口与界面管理:打造专业桌面应用体验 (入门系列五)

发布于:2025-05-01 ⋅ 阅读:(34) ⋅ 点赞:(0)

窗口管理是桌面应用的核心特性之一,良好的窗口管理可以显著提升用户体验。在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的额外属性
      }
    ]
  }
}

这些配置选项让你能够精确控制窗口的外观和行为。每个选项都有其特定用途:

  • 基本属性widthheighttitle等控制窗口的基本外观
  • 位置控制xycenter决定窗口的初始位置
  • 大小限制minWidthminHeightmaxWidthmaxHeight控制窗口大小范围
  • 状态控制maximizedfullscreenvisible等控制窗口的初始状态
  • 外观设置decorationstransparentshadow等控制窗口的视觉效果
  • 行为控制resizableclosablefileDropEnabled等控制窗口的交互行为

窗口标识符

每个窗口都需要一个唯一的标识符(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的系统集成功能,包括通知、快捷键和自动启动等特性。


网站公告

今日签到

点亮在社区的每一天
去签到