菜单是桌面应用的重要组成部分,Tauri 提供了灵活的 API 来定制三种常见的菜单形式:全局应用菜单栏(窗口顶部的标准菜单)、上下文菜单(右键弹出菜单)以及系统托盘菜单(托盘图标上的菜单)。所有菜单的定义和事件处理都集中在 Rust 后端,保证了安全性和一致性,同时也可以方便地与前端通信。
1. 应用菜单栏
全局菜单栏位于 macOS 屏幕顶部(或 Windows/Linux 的窗口内),通常包含“文件”“编辑”“帮助”等固定项。Tauri 允许通过 Rust 代码完全自定义菜单结构,并绑定菜单事件。
基本步骤:
- 在 Rust 中定义一个
Menu对象,并添加菜单项与子菜单。 - 将菜单安装到应用的主窗口。
- 监听菜单项的点击事件,执行相应逻辑(例如向前端发送事件)。
示例:创建一个带“文件”和“帮助”的菜单
use tauri::{CustomMenuItem, Menu, MenuItem, Submenu};
fn main() {
tauri::Builder::default()
.setup(|app| {
// 创建菜单项
let new_file = CustomMenuItem::new("new_file".to_string(), "新建");
let open = CustomMenuItem::new("open".to_string(), "打开");
let save = CustomMenuItem::new("save".to_string(), "保存");
let quit = MenuItem::Quit.to_owned(); // 系统内置退出项
// 创建子菜单
let file_menu = Submenu::new("文件", Menu::new()
.add_item(new_file)
.add_item(open)
.add_item(save)
.add_native_item(quit) // 加入原生退出动作
);
let about = CustomMenuItem::new("about".to_string(), "关于");
let help_menu = Submenu::new("帮助", Menu::new().add_item(about));
// 构建主菜单(macOS 默认会附带应用名称菜单,Windows/Linux 直接显示)
let menu = Menu::new()
.add_submenu(file_menu)
.add_submenu(help_menu);
// 应用到当前窗口
let window = app.get_window("main").unwrap();
window.set_menu(menu).unwrap();
// 监听菜单事件
window.on_menu_event(|event| {
match event.menu_item_id() {
"new_file" => {
// 执行新建操作,可以全局广播或调用前端
event.window().emit("menu-event", "new_file").unwrap();
}
"open" => { /* ... */ }
"save" => { /* ... */ }
"about" => {
// 弹出关于对话框等
}
_ => {}
}
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
说明:
- 使用
CustomMenuItem::new(id, 标题)创建普通菜单项,id用于事件匹配。 MenuItem::Quit、MenuItem::Copy等是系统内置动作,行为已预定义。window.on_menu_event中的闭包可以调用window.emit()将事件发送至前端,实现前后端联动。- 在 macOS 上,如果希望保留默认的“关于”“偏好设置”等项,可以使用
Menu::os_default("应用名")内置模板,再追加自定义菜单。
2. 上下文菜单
上下文菜单是用户通过鼠标右键(或长按)触发的弹出式菜单。Tauri 支持两种上下文菜单:
- WebView 内的 DOM 元素右键菜单:可以完全在前端通过 JavaScript 阻止默认行为并显示自定义菜单,但最终需 Rust 参与实现原生菜单。
- 窗口级别右键菜单:使用 Rust API 在指定窗口上设置上下文菜单。
通常推荐后者,因为原生菜单风格与系统一致,且不受前端 CSS 影响。
实现步骤:
- 在 Rust 中创建
Menu对象(无需添加到主窗口)。 - 在前端监听
contextmenu事件,获取坐标并通过 Tauri 命令告知 Rust。 - Rust 接收到命令后,调用
window.popup_menu(menu, x, y)显示菜单。 - 使用菜单事件处理点击。
Rust 端核心函数示例:
use tauri::{CustomMenuItem, Menu, Manager};
#[tauri::command]
fn show_context_menu(window: tauri::Window, x: f64, y: f64) {
let copy = CustomMenuItem::new("ctx_copy".to_string(), "复制");
let paste = CustomMenuItem::new("ctx_paste".to_string(), "粘贴");
let inspect = CustomMenuItem::new("ctx_inspect".to_string(), "检查元素");
let menu = Menu::new()
.add_item(copy)
.add_item(paste)
.add_item(inspect);
// 在指定坐标弹出菜单
window.popup_menu(menu, x as f64, y as f64).unwrap();
}
// 在 setup 中注册事件监听
app.get_window("main").unwrap().on_menu_event(|event| {
match event.menu_item_id() {
"ctx_copy" => { /* 通过 emit 通知前端复制 */ }
"ctx_paste" => { /* 同理 */ }
"ctx_inspect" => {
// 打开开发者工具(仅调试用)
event.window().open_devtools();
}
_ => {}
}
});
前端触发(使用 JavaScript):
window.addEventListener('contextmenu', (e) => {
e.preventDefault();
// 调用 Rust 命令,传入客户端坐标
window.__TAURI__.invoke('show_context_menu', {
x: e.clientX,
y: e.clientY,
});
});
注意事项:
- 如果只想在某些元素上显示菜单,可以在前端判断
e.target是否符合条件再调用命令。 window.popup_menu的坐标是基于窗口的物理坐标,传入clientX/Y即可。- 菜单事件与前文全局菜单事件共用一个监听器,务必通过
menu_item_id区分来源。
3. 系统菜单(系统托盘菜单)
系统托盘菜单是附着在系统托盘图标上的菜单,通常用于后台驻留应用(如音乐播放器、网盘客户端)。Tauri 通过 tauri::SystemTray 提供完整支持。
实现步骤:
- 在 Rust 中创建
SystemTray对象,包含图标和菜单。 - 在
Builder上通过.system_tray(tray)安装。 - 可选:监听托盘图标左键点击事件,显示或隐藏主窗口。
- 菜单项事件在统一的
.on_system_tray_event()中处理。
简单示例:
use tauri::{CustomMenuItem, Menu, MenuItem, SystemTray, SystemTrayEvent, SystemTrayMenu};
fn main() {
let show = CustomMenuItem::new("show".to_string(), "显示窗口");
let quit = CustomMenuItem::new("quit".to_string(), "退出");
let tray_menu = SystemTrayMenu::new()
.add_item(show)
.add_native_item(MenuItem::Separator)
.add_item(quit);
let system_tray = SystemTray::new()
.with_menu(tray_menu)
.with_icon(tauri::Icon::Raw(include_bytes!("../icons/tray-icon.png").to_vec()));
tauri::Builder::default()
.system_tray(system_tray)
.on_system_tray_event(|app, event| match event {
SystemTrayEvent::LeftClick { .. } => {
// 左键切换窗口可见性
let window = app.get_window("main").unwrap();
if window.is_visible().unwrap_or(false) {
window.hide().unwrap();
} else {
window.show().unwrap();
window.set_focus().unwrap();
}
}
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
"show" => {
let window = app.get_window("main").unwrap();
window.show().unwrap();
window.set_focus().unwrap();
}
"quit" => {
std::process::exit(0);
}
_ => {}
},
_ => {}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
关键要点:
- 图标建议使用
.png(最好是 32x32 或 64x64),直接编译进二进制。 - 退出时若仅隐藏主窗口,应用会继续在后台运行,托盘图标保持活跃。
- 如果不需要主窗口显示,可以在
tauri.conf.json中将windows.main.visible设为false,然后在托盘菜单中手动显示。 - 在 macOS 上,托盘菜单使用原生模板图像效果更佳;Windows 上可以使用彩色图标。
小结:
Tauri 的菜单系统强制将结构与事件放在 Rust 端,保证了安全性和平台一致性。通过简单的配置和事件监听,你可以轻松实现应用必备的菜单栏、右键快捷菜单和系统托盘菜单,并优雅地与前端交互。