你有沒有發現,很多桌面軟體都有個共同特色:就算你把視窗關掉,程式還是在背景偷偷跑著,隨時準備為你服務。這個神奇功能的關鍵就是系統匣圖示(System Tray)!透過系統匣,你可以快速使用程式的基本功能,不用每次都重新打開完整的視窗。
在 Windows 系統中,系統匣圖示就在工具列的右下角。這個地方正式名稱叫 Notification Area,裡面的小圖示們就是 Notification Area Icon,中文有人叫「系統匣」、有人叫「系統托盤」。名字有點混亂,但反正你只要知道我們今天要討論的就是工具列右下角那些小圖示就對了XD
系統匣圖示不僅僅是一個美觀的小圖標,它代表著應用程式對使用者工作流程的深度整合。想像一個音樂播放器可以在系統匣中提供播放/暫停控制,或是一個待辦事項應用允許使用者快速新增任務,這些都大大提升了軟體的實用性和使用者滿意度。
對於工具類型的應用程式,系統匣更是不可或缺的功能。它讓應用程式能夠在不干擾使用者當前工作的情況下,持續提供背景服務,這正是現代桌面軟體用戶體驗的重要組成部分。
在 Tauri 中實作系統匣功能相當直觀。我們可以在 Rust 後端建立系統匣物件,並定義相關的選單結構:
// lib.rs
use tauri::{Builder};
use tauri::tray::TrayIconBuilder;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
Builder::default()
.plugin(tauri_plugin_opener::init())
.setup(|app| {
let tray = TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
.build(app)?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
大部分的系統匣圖示都會有個右鍵選單,讓使用者可以做更多操作。比如說 Discord 的選單就長這樣:
如果要加上選單,只要再加上幾行程式碼就可以了:
// lib.rs
use tauri::{Builder};
use tauri::tray::TrayIconBuilder;
+ use tauri::menu::{Menu, MenuItem};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
Builder::default()
.plugin(tauri_plugin_opener::init())
.setup(|app| {
+ let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
+ let menu = Menu::with_items(app, &[&quit_i])?;
let tray = TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
+ .menu(&menu)
+ .show_menu_on_left_click(false)
.build(app)?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
官網範例上用的
.menu_on_left_click(true)
已經 deprecated 了。
跟昨天一樣,上面的程式碼只是把選單做出來而已,接下來我們還要讓選單項目真的能做事:
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
Builder::default()
.plugin(tauri_plugin_opener::init())
.setup(|app| {
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&quit_i])?;
let tray = TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
.menu(&menu)
+ .on_menu_event(|app, event| match event.id.as_ref() {
+ "quit" => {
+ println!("quit menu item was clicked");
+ app.exit(0);
+ }
+ _ => {
+ println!("menu item {:?} not handled", event.id);
+ }
+ })
.show_menu_on_left_click(false)
.build(app)?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
要讓系統匣功能正常運作,我們得防止使用者按下關閉按鈕時程式真的退出。我們可以攔截關閉事件,然後偷偷把視窗藏起來就好:
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
Builder::default()
.plugin(tauri_plugin_opener::init())
.setup(|app| {
// 設定系統托盤圖示
setup_tray_icon(app)?;
Ok(())
})
.on_window_event(|window, event| {
// 設定視窗關閉事件處理器
match event {
tauri::WindowEvent::CloseRequested { api, .. } => {
window.hide().unwrap();
api.prevent_close();
}
_ => {}
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
當然,也要加上可以重新打開視窗的按鈕到選單中:
// 前後省略
.on_menu_event(|app, event| match event.id.as_ref() {
"open" => {
println!("open menu item was clicked");
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
"quit" => {
println!("quit menu item was clicked");
app.exit(0);
}
_ => {
println!("menu item {:?} not handled", event.id);
}
})
做系統匣功能的時候,要仔細想想使用者會怎麼用。通常左鍵點擊是用來顯示/隱藏主視窗,右鍵點擊才會跳出選單。選單項目要簡潔一點,放最常用的功能就好。還有記得一定要有個「退出」選項,讓使用者真的想關掉程式時可以完全關閉。
今天我們學會了 Tauri 2.0 的系統匣功能!從最基本的系統匣圖示,到加上選單和事件處理,還有防止程式被意外關掉的小技巧。
系統匣功能讓你的程式可以在背景持續跑著,使用者想用的時候隨時都能快速存取。這對工具類的程式來說超重要的,讓使用者可以隨時叫出功能,完全不會打斷手邊的工作。
有了適當的系統匣設計,我們的 Tauri 程式就能提供更專業的桌面軟體體驗,真正融入使用者的日常工作環境中!