菜单系统 #
一、菜单概述 #
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,
©,
&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, &[©, &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