系统托盘 #

一、托盘概述 #

1.1 系统托盘功能 #

系统托盘允许应用在后台运行,通过托盘图标提供快速访问功能。

text
┌─────────────────────────────────────────────────────────────┐
│                      系统托盘                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    任务栏                            │   │
│  │  [开始] [应用图标] [应用图标] [托盘图标] [时钟]     │   │
│  │                                     ▼               │   │
│  │                              ┌──────────────┐       │   │
│  │                              │ 显示窗口     │       │   │
│  │                              │ 设置         │       │   │
│  │                              │ ────────────│       │   │
│  │                              │ 退出         │       │   │
│  │                              └──────────────┘       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 托盘功能 #

功能 说明
托盘图标 显示在系统托盘区域
托盘菜单 右键点击显示菜单
托盘提示 鼠标悬停显示提示
托盘事件 点击、双击事件

二、基本配置 #

2.1 配置托盘图标 #

json
// tauri.conf.json
{
    "app": {
        "trayIcon": {
            "iconPath": "icons/icon.png",
            "iconAsTemplate": true,
            "id": "main"
        }
    }
}

2.2 Cargo.toml 配置 #

toml
[dependencies]
tauri = { version = "2", features = ["tray-icon"] }

三、创建托盘 #

3.1 基本托盘 #

rust
use tauri::{
    menu::{Menu, MenuItem},
    tray::{TrayIcon, TrayIconBuilder},
    Manager,
};

fn create_tray(app: &tauri::AppHandle) -> Result<TrayIcon, Box<dyn std::error::Error>> {
    let show = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?;
    let quit = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
    
    let menu = Menu::with_items(app, &[&show, &quit])?;
    
    let tray = TrayIconBuilder::new()
        .icon(app.default_window_icon().unwrap().clone())
        .menu(&menu)
        .on_menu_event(|app, event| {
            match event.id.as_ref() {
                "show" => {
                    if let Some(window) = app.get_webview_window("main") {
                        let _ = window.show();
                        let _ = window.set_focus();
                    }
                }
                "quit" => {
                    app.exit(0);
                }
                _ => {}
            }
        })
        .build(app)?;
    
    Ok(tray)
}

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            create_tray(app.handle())?;
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

3.2 托盘菜单 #

rust
use tauri::menu::{Menu, MenuItem, Submenu, PredefinedMenuItem};

fn create_tray_with_menu(app: &tauri::AppHandle) -> Result<TrayIcon, Box<dyn std::error::Error>> {
    let show = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?;
    let hide = MenuItem::with_id(app, "hide", "隐藏窗口", true, None::<&str>)?;
    let separator = PredefinedMenuItem::separator(app)?;
    let settings = MenuItem::with_id(app, "settings", "设置", true, None::<&str>)?;
    let about = MenuItem::with_id(app, "about", "关于", true, None::<&str>)?;
    let quit = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
    
    let menu = Menu::with_items(app, &[
        &show,
        &hide,
        &separator,
        &settings,
        &about,
        &separator,
        &quit,
    ])?;
    
    let tray = TrayIconBuilder::new()
        .icon(app.default_window_icon().unwrap().clone())
        .menu(&menu)
        .on_menu_event(|app, event| {
            match event.id.as_ref() {
                "show" => {
                    if let Some(window) = app.get_webview_window("main") {
                        let _ = window.show();
                        let _ = window.set_focus();
                    }
                }
                "hide" => {
                    if let Some(window) = app.get_webview_window("main") {
                        let _ = window.hide();
                    }
                }
                "settings" => {
                    // 打开设置窗口
                }
                "about" => {
                    // 打开关于窗口
                }
                "quit" => {
                    app.exit(0);
                }
                _ => {}
            }
        })
        .build(app)?;
    
    Ok(tray)
}

3.3 子菜单 #

rust
use tauri::menu::{Menu, MenuItem, Submenu};

fn create_submenu(app: &tauri::AppHandle) -> Result<Submenu, Box<dyn std::error::Error>> {
    let light = MenuItem::with_id(app, "theme_light", "浅色", true, None::<&str>)?;
    let dark = MenuItem::with_id(app, "theme_dark", "深色", true, None::<&str>)?;
    let system = MenuItem::with_id(app, "theme_system", "跟随系统", true, None::<&str>)?;
    
    let theme_menu = Menu::with_items(app, &[&light, &dark, &system])?;
    
    let submenu = Submenu::with_items(app, "主题", true, &[&theme_menu])?;
    
    Ok(submenu)
}

四、托盘事件 #

4.1 点击事件 #

rust
use tauri::tray::TrayIconBuilder;

let tray = TrayIconBuilder::new()
    .icon(app.default_window_icon().unwrap().clone())
    .on_tray_icon_event(|tray, event| {
        match event {
            tauri::tray::TrayIconEvent::Click { button, .. } => {
                match button {
                    tauri::tray::MouseButton::Left => {
                        // 左键点击
                        if let Some(window) = tray.app_handle().get_webview_window("main") {
                            let _ = window.show();
                            let _ = window.set_focus();
                        }
                    }
                    tauri::tray::MouseButton::Right => {
                        // 右键点击,显示菜单
                    }
                    _ => {}
                }
            }
            tauri::tray::TrayIconEvent::Double { button, .. } => {
                // 双击事件
            }
            _ => {}
        }
    })
    .build(app)?;

4.2 托盘提示 #

rust
use tauri::tray::TrayIconBuilder;

let tray = TrayIconBuilder::new()
    .icon(app.default_window_icon().unwrap().clone())
    .tooltip("我的应用")
    .build(app)?;

五、动态更新 #

5.1 更新图标 #

rust
use tauri::image::Image;

fn update_tray_icon(tray: &TrayIcon, image_data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
    let image = Image::from_bytes(image_data)?;
    tray.set_icon(Some(image))?;
    Ok(())
}

5.2 更新菜单 #

rust
use tauri::menu::MenuItem;

fn update_menu_item(menu_item: &MenuItem, enabled: bool) -> Result<(), Box<dyn std::error::Error>> {
    menu_item.set_enabled(enabled)?;
    Ok(())
}

5.3 更新提示 #

rust
fn update_tooltip(tray: &TrayIcon, text: &str) -> Result<(), Box<dyn std::error::Error>> {
    tray.set_tooltip(Some(text))?;
    Ok(())
}

六、后台运行 #

6.1 关闭到托盘 #

rust
use tauri::Manager;

tauri::Builder::default()
    .setup(|app| {
        let window = app.get_webview_window("main").unwrap();
        
        window.on_window_event(move |event| {
            if let tauri::WindowEvent::CloseRequested { api, .. } = event {
                // 阻止关闭
                api.prevent_close();
                // 隐藏窗口
                let _ = window.hide();
            }
        });
        
        Ok(())
    })
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

6.2 前端控制 #

typescript
import { getCurrentWindow } from '@tauri-apps/api/window';

const window = getCurrentWindow();

// 关闭到托盘
await window.onCloseRequested(async (event) => {
    event.preventDefault();
    await window.hide();
});

七、托盘通知 #

7.1 显示通知 #

typescript
import { sendNotification } from '@tauri-apps/plugin-notification';

async function showTrayNotification(title: string, body: string) {
    await sendNotification({
        title,
        body,
    });
}

7.2 后台任务通知 #

rust
use tauri::Manager;

fn background_task(app: &tauri::AppHandle) {
    // 执行后台任务
    let result = do_task();
    
    // 发送通知
    app.emit("task-complete", result).ok();
}

八、最佳实践 #

8.1 托盘状态管理 #

rust
use std::sync::Mutex;

struct TrayState {
    is_running: bool,
    notification_count: u32,
}

struct AppState {
    tray_state: Mutex<TrayState>,
}

#[tauri::command]
fn set_running(state: tauri::State<AppState>, running: bool) {
    let mut tray_state = state.tray_state.lock().unwrap();
    tray_state.is_running = running;
}

#[tauri::command]
fn get_running(state: tauri::State<AppState>) -> bool {
    state.tray_state.lock().unwrap().is_running
}

8.2 托盘图标状态 #

rust
fn update_tray_status(tray: &TrayIcon, status: &str) -> Result<(), Box<dyn std::error::Error>> {
    let icon = match status {
        "running" => include_bytes!("../icons/running.png"),
        "paused" => include_bytes!("../icons/paused.png"),
        "error" => include_bytes!("../icons/error.png"),
        _ => include_bytes!("../icons/default.png"),
    };
    
    let image = tauri::image::Image::from_bytes(icon)?;
    tray.set_icon(Some(image))?;
    
    Ok(())
}

九、总结 #

9.1 核心要点 #

要点 说明
托盘创建 TrayIconBuilder
托盘菜单 Menu 和 MenuItem
托盘事件 点击、双击事件
后台运行 隐藏窗口而非关闭
动态更新 更新图标和菜单

9.2 下一步 #

现在你已经掌握了系统托盘,接下来让我们学习 菜单系统,了解如何创建应用菜单!

最后更新:2026-03-28