菜单系统 #

一、菜单概述 #

1.1 菜单类型 #

类型 说明 位置
应用菜单 应用主菜单 顶部菜单栏
窗口菜单 窗口专属菜单 窗口标题栏
上下文菜单 右键菜单 鼠标位置
托盘菜单 托盘图标菜单 托盘图标

1.2 菜单结构 #

text
┌─────────────────────────────────────────────────────────────┐
│  文件   编辑   视图   窗口   帮助                           │
├─────────────────────────────────────────────────────────────┤
│         ┌─────────────┐                                     │
│         │ 新建        │                                     │
│         │ 打开...     │                                     │
│         │ 保存        │                                     │
│         │ 另存为...   │                                     │
│         │ ─────────── │                                     │
│         │ 最近文件 ▶  │                                     │
│         │ ─────────── │                                     │
│         │ 退出        │                                     │
│         └─────────────┘                                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、创建菜单 #

2.1 基本菜单 #

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

fn create_menu(app: &tauri::AppHandle) -> Result<Menu, Box<dyn std::error::Error>> {
    let new_file = MenuItem::with_id(app, "new", "新建", true, None::<&str>)?;
    let open = MenuItem::with_id(app, "open", "打开...", true, None::<&str>)?;
    let save = MenuItem::with_id(app, "save", "保存", true, None::<&str>)?;
    let quit = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
    
    let menu = Menu::with_items(app, &[&new_file, &open, &save, &quit])?;
    
    Ok(menu)
}

2.2 应用菜单 #

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

fn create_app_menu(app: &tauri::AppHandle) -> Result<Menu, Box<dyn std::error::Error>> {
    // 文件菜单
    let new_file = MenuItem::with_id(app, "new", "新建", true, Some("CmdOrCtrl+N"))?;
    let open = MenuItem::with_id(app, "open", "打开...", true, Some("CmdOrCtrl+O"))?;
    let save = MenuItem::with_id(app, "save", "保存", true, Some("CmdOrCtrl+S"))?;
    let save_as = MenuItem::with_id(app, "save_as", "另存为...", true, Some("CmdOrCtrl+Shift+S"))?;
    let separator1 = PredefinedMenuItem::separator(app)?;
    let quit = MenuItem::with_id(app, "quit", "退出", true, Some("CmdOrCtrl+Q"))?;
    
    let file_menu = Menu::with_items(app, &[
        &new_file,
        &open,
        &separator1,
        &save,
        &save_as,
        &separator1,
        &quit,
    ])?;
    
    let file_submenu = Submenu::with_items(app, "文件", true, &[&file_menu])?;
    
    // 编辑菜单
    let undo = MenuItem::with_id(app, "undo", "撤销", true, Some("CmdOrCtrl+Z"))?;
    let redo = MenuItem::with_id(app, "redo", "重做", true, Some("CmdOrCtrl+Shift+Z"))?;
    let separator2 = PredefinedMenuItem::separator(app)?;
    let cut = MenuItem::with_id(app, "cut", "剪切", true, Some("CmdOrCtrl+X"))?;
    let copy = MenuItem::with_id(app, "copy", "复制", true, Some("CmdOrCtrl+C"))?;
    let paste = MenuItem::with_id(app, "paste", "粘贴", true, Some("CmdOrCtrl+V"))?;
    let select_all = MenuItem::with_id(app, "select_all", "全选", true, Some("CmdOrCtrl+A"))?;
    
    let edit_menu = Menu::with_items(app, &[
        &undo,
        &redo,
        &separator2,
        &cut,
        &copy,
        &paste,
        &select_all,
    ])?;
    
    let edit_submenu = Submenu::with_items(app, "编辑", true, &[&edit_menu])?;
    
    // 帮助菜单
    let about = MenuItem::with_id(app, "about", "关于", true, None::<&str>)?;
    let help_menu = Menu::with_items(app, &[&about])?;
    let help_submenu = Submenu::with_items(app, "帮助", true, &[&help_menu])?;
    
    // 主菜单
    let menu = Menu::with_items(app, &[&file_submenu, &edit_submenu, &help_submenu])?;
    
    Ok(menu)
}

2.3 设置菜单到窗口 #

rust
use tauri::Manager;

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let menu = create_app_menu(app.handle())?;
            
            // 设置到所有窗口
            app.set_menu(menu)?;
            
            // 或设置到特定窗口
            // let window = app.get_webview_window("main").unwrap();
            // window.set_menu(menu)?;
            
            Ok(())
        })
        .on_menu_event(|app, event| {
            match event.id.as_ref() {
                "new" => {
                    // 新建文件
                }
                "open" => {
                    // 打开文件
                }
                "save" => {
                    // 保存文件
                }
                "quit" => {
                    app.exit(0);
                }
                _ => {}
            }
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

三、菜单项类型 #

3.1 普通菜单项 #

rust
let item = MenuItem::with_id(
    app,
    "my_item",
    "我的菜单项",
    true,  // enabled
    Some("CmdOrCtrl+M")  // 快捷键
)?;

3.2 分隔符 #

rust
let separator = PredefinedMenuItem::separator(app)?;

3.3 复选菜单项 #

rust
use tauri::menu::CheckMenuItem;

let check_item = CheckMenuItem::with_id(
    app,
    "show_sidebar",
    "显示侧边栏",
    true,
    true,  // checked
    Some("CmdOrCtrl+B")
)?;

3.4 单选菜单项 #

rust
use tauri::menu::RadioMenuItem;

let radio1 = RadioMenuItem::with_id(app, "theme_light", "浅色", true, true, None::<&str>)?;
let radio2 = RadioMenuItem::with_id(app, "theme_dark", "深色", true, false, None::<&str>)?;
let radio3 = RadioMenuItem::with_id(app, "theme_system", "跟随系统", true, false, None::<&str>)?;

3.5 图标菜单项 #

rust
use tauri::image::Image;

let icon = Image::from_bytes(include_bytes!("../icons/icon.png"))?;
let item = MenuItem::with_id_and_icon(
    app,
    "icon_item",
    "带图标的菜单项",
    true,
    Some("CmdOrCtrl+I"),
    &icon
)?;

四、上下文菜单 #

4.1 创建上下文菜单 #

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

fn create_context_menu(app: &tauri::AppHandle) -> Result<Menu, Box<dyn std::error::Error>> {
    let copy = MenuItem::with_id(app, "ctx_copy", "复制", true, None::<&str>)?;
    let paste = MenuItem::with_id(app, "ctx_paste", "粘贴", true, None::<&str>)?;
    let delete = MenuItem::with_id(app, "ctx_delete", "删除", true, None::<&str>)?;
    
    let menu = Menu::with_items(app, &[&copy, &paste, &delete])?;
    
    Ok(menu)
}

4.2 显示上下文菜单 #

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

async function showContextMenu(event: MouseEvent) {
    const menu = await Menu.new({
        items: [
            { id: 'copy', text: '复制' },
            { id: 'paste', text: '粘贴' },
            { id: 'delete', text: '删除' },
        ],
    });

    const window = getCurrentWindow();
    await menu.popup(window, event.clientX, event.clientY);
}

4.3 React 封装 #

tsx
import { useEffect, useRef } from 'react';
import { Menu } from '@tauri-apps/api/menu';
import { getCurrentWindow } from '@tauri-apps/api/window';

interface ContextMenuProps {
    items: Array<{ id: string; text: string }>;
    children: React.ReactNode;
}

export function ContextMenu({ items, children }: ContextMenuProps) {
    const ref = useRef<HTMLDivElement>(null);

    useEffect(() => {
        const element = ref.current;
        if (!element) return;

        const handleContextMenu = async (event: MouseEvent) => {
            event.preventDefault();

            const menu = await Menu.new({ items });
            const window = getCurrentWindow();
            await menu.popup(window, event.clientX, event.clientY);
        };

        element.addEventListener('contextmenu', handleContextMenu);
        return () => {
            element.removeEventListener('contextmenu', handleContextMenu);
        };
    }, [items]);

    return <div ref={ref}>{children}</div>;
}

五、菜单事件 #

5.1 全局菜单事件 #

rust
tauri::Builder::default()
    .on_menu_event(|app, event| {
        println!("Menu item clicked: {}", event.id);
        
        match event.id.as_ref() {
            "new" => {
                // 处理新建
            }
            "open" => {
                // 处理打开
            }
            _ => {}
        }
    })
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

5.2 窗口菜单事件 #

rust
use tauri::Manager;

let window = app.get_webview_window("main").unwrap();
window.on_menu_event(move |event| {
    println!("Window menu item clicked: {}", event.id);
});

5.3 前端监听菜单事件 #

typescript
import { listen } from '@tauri-apps/api/event';

await listen('menu://item-clicked', (event) => {
    console.log('Menu item clicked:', event.payload);
});

六、动态菜单 #

6.1 更新菜单项状态 #

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(())
}

fn update_check_item(menu_item: &CheckMenuItem, checked: bool) -> Result<(), Box<dyn std::error::Error>> {
    menu_item.set_checked(checked)?;
    Ok(())
}

6.2 动态添加菜单项 #

rust
use tauri::menu::Menu;

fn add_recent_file(menu: &Menu, app: &tauri::AppHandle, file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let item = MenuItem::with_id(
        app,
        &format!("recent_{}", file_path),
        file_path,
        true,
        None::<&str>
    )?;
    
    menu.insert(&item, 0)?;
    
    Ok(())
}

七、快捷键 #

7.1 定义快捷键 #

rust
// 使用平台特定快捷键
let item = MenuItem::with_id(
    app,
    "save",
    "保存",
    true,
    Some("CmdOrCtrl+S")  // macOS: Cmd+S, Windows/Linux: Ctrl+S
)?;

// 多键组合
let item = MenuItem::with_id(
    app,
    "save_all",
    "全部保存",
    true,
    Some("CmdOrCtrl+Shift+S")
)?;

7.2 快捷键格式 #

text
修饰键:
- CmdOrCtrl: macOS 上是 Cmd,其他平台是 Ctrl
- Cmd: macOS 上的 Command 键
- Ctrl: Control 键
- Shift: Shift 键
- Alt: Alt 键 (macOS 上是 Option)

示例:
- "CmdOrCtrl+S": 保存
- "CmdOrCtrl+Shift+S": 另存为
- "CmdOrCtrl+Alt+Delete": 特殊组合

八、最佳实践 #

8.1 菜单组织 #

rust
// 标准菜单结构
文件: 新建、打开、保存、退出
编辑: 撤销、重做、剪切、复制、粘贴
视图: 缩放、全屏
窗口: 最小化、关闭
帮助: 关于、文档

8.2 菜单状态同步 #

rust
use std::sync::Mutex;

struct MenuState {
    can_save: bool,
    can_undo: bool,
    can_redo: bool,
}

impl MenuState {
    fn update_menu(&self, app: &tauri::AppHandle) {
        if let Some(save_item) = app.menu().find("save") {
            save_item.set_enabled(self.can_save).ok();
        }
        if let Some(undo_item) = app.menu().find("undo") {
            undo_item.set_enabled(self.can_undo).ok();
        }
        if let Some(redo_item) = app.menu().find("redo") {
            redo_item.set_enabled(self.can_redo).ok();
        }
    }
}

九、总结 #

9.1 核心要点 #

要点 说明
菜单创建 Menu 和 MenuItem
应用菜单 设置到应用或窗口
上下文菜单 右键弹出菜单
菜单事件 on_menu_event
快捷键 CmdOrCtrl 格式

9.2 下一步 #

现在你已经掌握了菜单系统,接下来让我们学习 通知系统,了解如何发送系统通知!

最后更新:2026-03-28