MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Tauriの基礎 - メニューとシステムトレイのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Tauriの基礎 - メニューとシステムトレイ
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == Tauriのメニューとシステムトレイ機能は、デスクトップアプリケーションに不可欠なユーザーインターフェース要素を提供する。<br> アプリケーションメニューは、MacOSのメニューバー、Windows/Linuxのウインドウメニューとして表示され、ファイル操作、編集機能、設定へのアクセス等を提供する。<br> <br> システムトレイ (またはタスクトレイ) は、アプリケーションをバックグラウンドで実行しながら、ステータス表示やクイックアクションへのアクセスを可能にする。<br> <br> Tauri v2では、メニューとシステムトレイのAPIが大幅に改善され、RustとJavaScriptの両方から一貫した方法で操作できる。<br> メニュー項目は階層構造をサポートし、サブメニューやセパレーター、チェックボックス項目等、ネイティブアプリケーションと同等の機能を実現できる。<br> <br> コンテキストメニュー (右クリックメニュー) もサポートされており、ユーザーが特定の要素を右クリックした時に適切なアクションを提供できる。<br> <br><br> == アプリケーションメニューの基本概念 == ==== メニューの構成要素 ==== Tauriのメニューは、以下に示す要素で構成される。<br> <br> <center> {| class="wikitable" |+ Tauriのメニューの構成要素 ! 要素 !! 説明 |- | <code>Menu</code> || メニュー全体を表すコンテナ<br>複数のMenuItemを含む。 |- | <code>MenuItem</code> || 個別のメニュー項目<br>クリック時のアクションを定義 |- | <code>Submenu</code> || サブメニュー<br>別のMenuをネスト |- | <code>PredefinedMenuItem</code> || 定義済みの標準メニュー項目<br>Copy、Paste、Quit等 |- | <code>CheckMenuItem</code> || チェックボックス付きメニュー項目<br>オン / オフ状態を管理する。 |- | <code>IconMenuItem</code> || アイコン付きメニュー項目 |- | <code>MenuItemKind</code> || 全てのメニュー項目タイプを統一的に扱う列挙型 |} </center> <br> ==== プラットフォームごとの違い ==== <center> {| class="wikitable" |+ プラットフォーム別メニュー表示 |- ! プラットフォーム !! 表示場所 !! 特徴 |- | MacOS || 画面上部のメニューバー || アプリケーション全体で共有 |- | Windows || ウインドウ内のタイトルバー下 || ウインドウごとに独立 |- | Linux || ウインドウ内 || デスクトップ環境による |} </center> <br><br> == メニューの定義 == ==== Rustでのメニュー定義 ==== <syntaxhighlight lang="rust"> // src-tauri/src/lib.rs use tauri::{ menu::{Menu, MenuBuilder, MenuItem, MenuItemBuilder, Submenu, SubmenuBuilder}, Builder, Manager, }; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .setup(|app| { // ファイルメニューの項目を作成 let new_item = MenuItemBuilder::with_id("new", "New").build(app)?; let open_item = MenuItemBuilder::with_id("open", "Open...").build(app)?; let save_item = MenuItemBuilder::with_id("save", "Save").build(app)?; let quit_item = MenuItemBuilder::with_id("quit", "Quit").build(app)?; // ファイルメニューを作成 let file_menu = SubmenuBuilder::new(app, "File") .item(&new_item) .item(&open_item) .item(&save_item) .separator() .item(&quit_item) .build()?; // 編集メニューの項目 let copy_item = MenuItemBuilder::with_id("copy", "Copy").build(app)?; let paste_item = MenuItemBuilder::with_id("paste", "Paste").build(app)?; let cut_item = MenuItemBuilder::with_id("cut", "Cut").build(app)?; let edit_menu = SubmenuBuilder::new(app, "Edit") .item(©_item) .item(&paste_item) .item(&cut_item) .build()?; // メインメニューを作成 let menu = MenuBuilder::new(app) .item(&file_menu) .item(&edit_menu) .build()?; // メニューバーに設定 app.set_menu(&menu)?; Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== JavaScript / TypeScript でのメニュー定義 ==== <syntaxhighlight lang="typescript"> // src/utils/menu.ts import { Menu, Submenu, MenuItem } from '@tauri-apps/api/menu'; export async function setupMenu() { // ファイルメニューの項目を作成 const fileMenu = await Submenu.new({ text: 'File', items: [ { id: 'new', text: 'New' }, { id: 'open', text: 'Open...' }, { id: 'save', text: 'Save' }, { type: 'separator' }, { id: 'quit', text: 'Quit' }, ], }); // 編集メニューを作成 const editMenu = await Submenu.new({ text: 'Edit', items: [ { id: 'copy', text: 'Copy' }, { id: 'paste', text: 'Paste' }, { id: 'cut', text: 'Cut' }, ], }); // ヘルプメニューを作成 const helpMenu = await Submenu.new({ text: 'Help', items: [ { id: 'about', text: 'About' }, { id: 'docs', text: 'Documentation' }, ], }); // メインメニューを作成 const menu = await Menu.new({ items: [fileMenu, editMenu, helpMenu], }); // メニューバーに設定 await menu.setAsAppMenu(); return menu; } </syntaxhighlight> <br><br> == カスタムメニュー項目 == ==== 様々なメニュー項目タイプ ==== <syntaxhighlight lang="rust"> use tauri::{ menu::{ Menu, MenuBuilder, MenuItem, MenuItemBuilder, CheckMenuItem, CheckMenuItemBuilder, PredefinedMenuItem, IconMenuItem, IconMenuItemBuilder, }, Builder, Manager, image::Image, }; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .setup(|app| { // 通常のメニュー項目 let normal_item = MenuItemBuilder::with_id("normal", "Normal Item") .enabled(true) .build(app)?; // チェックボックス付きメニュー項目 let check_item = CheckMenuItemBuilder::with_id("check", "Enable Feature") .checked(true) .build(app)?; // アイコン付きメニュー項目 // let icon_item = IconMenuItemBuilder::with_id("icon", "Icon Item") // .icon(Image::from_bytes(include_bytes!("icon.png"))?) // .build(app)?; // 定義済みメニュー項目 let copy_item = PredefinedMenuItem::copy(app, None)?; let paste_item = PredefinedMenuItem::paste(app, None)?; let separator = PredefinedMenuItem::separator(app)?; let menu = MenuBuilder::new(app) .item(&normal_item) .item(&check_item) // .item(&icon_item) .items(&[&separator, ©_item, &paste_item]) .build()?; app.set_menu(&menu)?; Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== アクセラレーター (ショートカットキー) ==== <syntaxhighlight lang="rust"> use tauri::menu::{MenuItemBuilder, MenuBuilder}; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .setup(|app| { // ショートカットキー付きのメニュー項目 let new_item = MenuItemBuilder::with_id("new", "New") .accelerator("CmdOrCtrl+N") // MacOS: Cmd+N, Windows/Linux: Ctrl+N .build(app)?; let open_item = MenuItemBuilder::with_id("open", "Open...") .accelerator("CmdOrCtrl+O") .build(app)?; let save_item = MenuItemBuilder::with_id("save", "Save") .accelerator("CmdOrCtrl+S") .build(app)?; let quit_item = MenuItemBuilder::with_id("quit", "Quit") .accelerator("CmdOrCtrl+Q") .build(app)?; let menu = MenuBuilder::new(app) .items(&[&new_item, &open_item, &save_item, &quit_item]) .build()?; app.set_menu(&menu)?; Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== 動的メニューの更新 ==== <syntaxhighlight lang="rust"> use tauri::{AppHandle, Manager}; use tauri::menu::{Menu, MenuItem, CheckMenuItem}; #[tauri::command] async fn toggle_menu_item(app: AppHandle, item_id: String) -> Result<(), String> { // メニュー項目を取得して状態を変更 if let Some(menu) = app.menu() { if let Some(item) = menu.get(&item_id) { // チェックボックス項目の場合 if let Some(check_item) = item.as_check_menuitem() { let current = check_item.is_checked().map_err(|e| e.to_string())?; check_item.set_checked(!current).map_err(|e| e.to_string())?; } // 通常項目の有効/無効 else if let Some(menu_item) = item.as_menu_item() { let current = menu_item.is_enabled().map_err(|e| e.to_string())?; menu_item.set_enabled(!current).map_err(|e| e.to_string())?; } } } Ok(()) } #[tauri::command] async fn update_menu_text(app: AppHandle, item_id: String, new_text: String) -> Result<(), String> { if let Some(menu) = app.menu() { if let Some(item) = menu.get(&item_id) { if let Some(menu_item) = item.as_menu_item() { menu_item.set_text(&new_text).map_err(|e| e.to_string())?; } } } Ok(()) } </syntaxhighlight> <br><br> == コンテキストメニュー == ==== 基本的なコンテキストメニュー ==== <syntaxhighlight lang="rust"> use tauri::{ menu::{Menu, MenuBuilder, MenuItemBuilder}, webview::WindowBuilder, Builder, Manager, WebviewUrl, }; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .setup(|app| { // コンテキストメニューを作成 let context_menu = MenuBuilder::new(app) .item(&MenuItemBuilder::with_id("ctx-copy", "Copy").build(app)?) .item(&MenuItemBuilder::with_id("ctx-paste", "Paste").build(app)?) .item(&MenuItemBuilder::with_id("ctx-cut", "Cut").build(app)?) .build()?; // ウインドウにコンテキストメニューを設定 if let Some(window) = app.get_webview_window("main") { window.set_menu(&context_menu)?; } Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== TypeScriptでのコンテキストメニュー ==== <syntaxhighlight lang="typescript"> // src/hooks/useContextMenu.ts import { Menu } from '@tauri-apps/api/menu'; import { useCallback, useState } from 'react'; interface ContextMenuOptions { items: Array<{ id: string; text: string; enabled?: boolean; }>; } export function useContextMenu() { const [menu, setMenu] = useState<Menu | null>(null); const createMenu = useCallback(async (options: ContextMenuOptions) => { const newMenu = await Menu.new({ items: options.items.map(item => ({ id: item.id, text: item.text, enabled: item.enabled ?? true, })), }); setMenu(newMenu); return newMenu; }, []); const show = useCallback(async (x: number, y: number) => { if (menu) { await menu.popup({ x, y }); } }, [menu]); return { createMenu, show, menu }; } // 使用例 function MyComponent() { const { createMenu, show } = useContextMenu(); const handleContextMenu = async (e: React.MouseEvent) => { e.preventDefault(); const menu = await createMenu({ items: [ { id: 'edit', text: 'Edit' }, { id: 'delete', text: 'Delete' }, { id: 'copy', text: 'Copy' }, ], }); await show(e.clientX, e.clientY); }; return ( <div onContextMenu={handleContextMenu}> Right-click me! </div> ); } </syntaxhighlight> <br><br> == システムトレイアイコンとメニュー == ==== 基本的なシステムトレイ設定 ==== <syntaxhighlight lang="rust"> use tauri::{ menu::{Menu, MenuBuilder, MenuItemBuilder}, tray::{TrayIcon, TrayIconBuilder}, Builder, Manager, }; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .setup(|app| { // トレイメニューを作成 let tray_menu = MenuBuilder::new(app) .item(&MenuItemBuilder::with_id("show", "Show Window").build(app)?) .item(&MenuItemBuilder::with_id("hide", "Hide Window").build(app)?) .separator() .item(&MenuItemBuilder::with_id("settings", "Settings...").build(app)?) .item(&MenuItemBuilder::with_id("quit", "Quit").build(app)?) .build()?; // システムトレイを作成 let _tray = TrayIconBuilder::new() .icon(app.default_window_icon().unwrap().clone()) .menu(&tray_menu) .menu_on_left_click(true) .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" => { // 設定ウインドウを開く } "quit" => { app.exit(0); } _ => {} } }) .build(app)?; Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== TypeScriptでのシステムトレイ ==== <syntaxhighlight lang="typescript"> // src/utils/tray.ts import { TrayIcon } from '@tauri-apps/api/tray'; import { Menu } from '@tauri-apps/api/menu'; export async function setupTray() { // トレイメニューを作成 const menu = await Menu.new({ items: [ { id: 'show', text: 'Show Window' }, { id: 'hide', text: 'Hide Window' }, { type: 'separator' }, { id: 'settings', text: 'Settings...' }, { id: 'quit', text: 'Quit' }, ], }); // システムトレイを作成 const tray = await TrayIcon.new({ menu, menuOnLeftClick: true, action: (event) => { console.log('Tray event:', event); }, }); return tray; } // メニュー項目のクリックイベントを処理 export async function handleTrayMenuClick( itemId: string ): Promise<void> { switch (itemId) { case 'show': // ウインドウを表示 break; case 'hide': // ウインドウを非表示 break; case 'quit': // アプリを終了 break; } } </syntaxhighlight> <br> ==== トレイアイコンの動的変更 ==== <syntaxhighlight lang="rust"> use tauri::{ tray::TrayIconBuilder, image::Image, Manager, }; #[tauri::command] async fn set_tray_icon(app: tauri::AppHandle, status: String) -> Result<(), String> { let tray = app.tray_by_id("main") .ok_or("Tray not found")?; // ステータスに応じてアイコンを変更 let icon_data = match status.as_str() { "active" => include_bytes!("../icons/active.png"), "inactive" => include_bytes!("../icons/inactive.png"), "error" => include_bytes!("../icons/error.png"), _ => include_bytes!("../icons/default.png"), }; let icon = Image::from_bytes(icon_data) .map_err(|e| e.to_string())?; tray.set_icon(Some(icon)) .map_err(|e| e.to_string())?; Ok(()) } #[tauri::command] async fn set_tray_tooltip(app: tauri::AppHandle, tooltip: String) -> Result<(), String> { let tray = app.tray_by_id("main") .ok_or("Tray not found")?; tray.set_tooltip(Some(&tooltip)) .map_err(|e| e.to_string())?; Ok(()) } </syntaxhighlight> <br><br> == メニューイベントハンドラ == ==== Rustでのイベントハンドリング ==== <syntaxhighlight lang="rust"> use tauri::{ menu::{Menu, MenuBuilder, MenuItemBuilder}, Builder, Manager, }; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .setup(|app| { let menu = MenuBuilder::new(app) .item(&MenuItemBuilder::with_id("new", "New").build(app)?) .item(&MenuItemBuilder::with_id("open", "Open").build(app)?) .item(&MenuItemBuilder::with_id("save", "Save").build(app)?) .build()?; app.set_menu(&menu)?; Ok(()) }) .on_menu_event(|app, event| { // メニューイベントを処理 match event.id.as_ref() { "new" => { println!("New menu item clicked"); // 新規ファイル作成処理 } "open" => { println!("Open menu item clicked"); // ファイルを開く処理 } "save" => { println!("Save menu item clicked"); // 保存処理 } _ => { println!("Unknown menu item: {}", event.id); } } }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br> ==== TypeScript でのイベントハンドリング ==== <syntaxhighlight lang="typescript"> // src/hooks/useMenuEvents.ts import { listen } from '@tauri-apps/api/event'; import { useEffect } from 'react'; interface MenuEvent { id: string; } export function useMenuEvents( handlers: Record<string, () => void> ) { useEffect(() => { let unlisten: (() => void) | undefined; const setup = async () => { unlisten = await listen<MenuEvent>('tauri://menu', (event) => { const handler = handlers[event.payload.id]; if (handler) { handler(); } }); }; setup(); return () => { if (unlisten) { unlisten(); } }; }, [handlers]); } // 使用例 function App() { useMenuEvents({ 'new': () => console.log('New file'), 'open': () => console.log('Open file'), 'save': () => console.log('Save file'), 'quit': () => window.close(), }); return <div>App</div>; } </syntaxhighlight> <br> ==== トレイイベントのハンドリング ==== <syntaxhighlight lang="rust"> use tauri::{ tray::{TrayIconBuilder, TrayIconEvent, MouseButton, MouseButtonState}, menu::{MenuBuilder, MenuItemBuilder}, Builder, Manager, }; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { Builder::default() .setup(|app| { let tray_menu = MenuBuilder::new(app) .item(&MenuItemBuilder::with_id("quit", "Quit").build(app)?) .build()?; let _tray = TrayIconBuilder::new() .icon(app.default_window_icon().unwrap().clone()) .menu(&tray_menu) // メニュークリックイベント .on_menu_event(|app, event| { if event.id.as_ref() == "quit" { app.exit(0); } }) // トレイアイコンクリックイベント .on_tray_icon_event(|tray, event| { match event { TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } => { // 左クリックでウインドウを表示 let app = tray.app_handle(); if let Some(window) = app.get_webview_window("main") { let _ = window.unminimize(); let _ = window.show(); let _ = window.set_focus(); } } TrayIconEvent::DoubleClick { button: MouseButton::Left, .. } => { // ダブルクリックでアプリをアクティブ化 let app = tray.app_handle(); if let Some(window) = app.get_webview_window("main") { let _ = window.set_focus(); } } _ => {} } }) .build(app)?; Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br><br> == React + TypeScriptでのサンプルコード == ==== メニュー管理カスタムフック ==== <syntaxhighlight lang="typescript"> // src/hooks/useAppMenu.ts import { useEffect, useState, useCallback } from 'react'; import { Menu, Submenu } from '@tauri-apps/api/menu'; import { listen } from '@tauri-apps/api/event'; interface MenuItem { id: string; text: string; accelerator?: string; enabled?: boolean; handler?: () => void; } interface MenuConfig { [menuName: string]: MenuItem[]; } export function useAppMenu(config: MenuConfig) { const [menu, setMenu] = useState<Menu | null>(null); const [isLoading, setIsLoading] = useState(false); const createMenu = useCallback(async () => { setIsLoading(true); try { const submenus = await Promise.all( Object.entries(config).map(async ([name, items]) => { const submenu = await Submenu.new({ text: name, items: items.map(item => ({ id: item.id, text: item.text, accelerator: item.accelerator, enabled: item.enabled ?? true, })), }); return submenu; }) ); const newMenu = await Menu.new({ items: submenus, }); await newMenu.setAsAppMenu(); setMenu(newMenu); } catch (error) { console.error('Failed to create menu:', error); } finally { setIsLoading(false); } }, [config]); // メニューイベントをリッスン useEffect(() => { let unlisten: (() => void) | undefined; const setup = async () => { unlisten = await listen<{ id: string }>('tauri://menu', (event) => { // 登録されたハンドラを探して実行 Object.values(config).forEach(items => { const item = items.find(i => i.id === event.payload.id); if (item?.handler) { item.handler(); } }); }); }; setup(); return () => { if (unlisten) { unlisten(); } }; }, [config]); // メニューを作成 useEffect(() => { createMenu(); }, [createMenu]); return { menu, isLoading, recreate: createMenu }; } </syntaxhighlight> <br> ==== メニューコンポーネント ==== <syntaxhighlight lang="typescript"> // src/components/MenuProvider.tsx import React, { ReactNode } from 'react'; import { useAppMenu } from '../hooks/useAppMenu'; interface MenuProviderProps { children: ReactNode; onNewFile: () => void; onOpenFile: () => void; onSaveFile: () => void; onQuit: () => void; onCopy: () => void; onPaste: () => void; onSettings: () => void; onAbout: () => void; } export function MenuProvider({ children, onNewFile, onOpenFile, onSaveFile, onQuit, onCopy, onPaste, onSettings, onAbout, }: MenuProviderProps) { const { isLoading } = useAppMenu({ 'File': [ { id: 'new', text: 'New', accelerator: 'CmdOrCtrl+N', handler: onNewFile }, { id: 'open', text: 'Open...', accelerator: 'CmdOrCtrl+O', handler: onOpenFile }, { id: 'save', text: 'Save', accelerator: 'CmdOrCtrl+S', handler: onSaveFile }, { id: 'quit', text: 'Quit', accelerator: 'CmdOrCtrl+Q', handler: onQuit }, ], 'Edit': [ { id: 'copy', text: 'Copy', accelerator: 'CmdOrCtrl+C', handler: onCopy }, { id: 'paste', text: 'Paste', accelerator: 'CmdOrCtrl+V', handler: onPaste }, ], 'Help': [ { id: 'settings', text: 'Settings...', handler: onSettings }, { id: 'about', text: 'About', handler: onAbout }, ], }); if (isLoading) { return <div>Loading menu...</div>; } return <>{children}</>; } // App.tsxでの使用 function App() { const handleNewFile = () => { console.log('New file'); }; const handleOpenFile = () => { console.log('Open file'); }; // ...その他のハンドラ return ( <MenuProvider onNewFile={handleNewFile} onOpenFile={handleOpenFile} onSaveFile={() => console.log('Save')} onQuit={() => window.close()} onCopy={() => console.log('Copy')} onPaste={() => console.log('Paste')} onSettings={() => console.log('Settings')} onAbout={() => console.log('About')} > <MainContent /> </MenuProvider> ); } </syntaxhighlight> <br> ==== システムトレイ管理フック ==== <syntaxhighlight lang="typescript"> // src/hooks/useSystemTray.ts import { useEffect, useState, useCallback } from 'react'; import { TrayIcon } from '@tauri-apps/api/tray'; import { Menu } from '@tauri-apps/api/menu'; import { listen } from '@tauri-apps/api/event'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; interface TrayConfig { onShowWindow?: () => void; onHideWindow?: () => void; onQuit?: () => void; } export function useSystemTray(config: TrayConfig = {}) { const [tray, setTray] = useState<TrayIcon | null>(null); const [isInitialized, setIsInitialized] = useState(false); const initialize = useCallback(async () => { try { const menu = await Menu.new({ items: [ { id: 'show', text: 'Show Window' }, { id: 'hide', text: 'Hide Window' }, { type: 'separator' }, { id: 'quit', text: 'Quit' }, ], }); const newTray = await TrayIcon.new({ menu, menuOnLeftClick: true, action: (event) => { if (event.type === 'Click' && event.button === 'Left') { // 左クリックでウインドウをトグル getCurrentWebviewWindow().then(window => { window.isVisible().then(visible => { if (visible) { window.hide(); } else { window.show(); window.setFocus(); } }); }); } }, }); setTray(newTray); setIsInitialized(true); } catch (error) { console.error('Failed to initialize tray:', error); } }, []); // メニュークリックイベントをリッスン useEffect(() => { let unlisten: (() => void) | undefined; const setup = async () => { unlisten = await listen<{ id: string }>('tauri://menu', (event) => { switch (event.payload.id) { case 'show': config.onShowWindow?.(); break; case 'hide': config.onHideWindow?.(); break; case 'quit': config.onQuit?.(); break; } }); }; setup(); return () => { if (unlisten) { unlisten(); } }; }, [config]); // 初期化 useEffect(() => { initialize(); }, [initialize]); const setTooltip = useCallback(async (tooltip: string) => { if (tray) { await tray.setToolTip(tooltip); } }, [tray]); return { tray, isInitialized, setTooltip, }; } </syntaxhighlight> <br><br> == トラブルシューティング == ==== メニューが表示されない ==== * 原因 *: メニューが正しく設定されていない、または、プラットフォーム固有の問題 * 解決方法 *: <syntaxhighlight lang="rust"> // メニューを正しく設定 app.set_menu(&menu)?; // MacOSでアプリメニューを設定 #[cfg(target_os = "macos")] { app.set_menu(&menu)?; } </syntaxhighlight> <br> ==== ショートカットキーが機能しない ==== * 原因 *: アクセラレーターの設定が正しくない、または、フォーカスの問題 * 解決方法 *: <syntaxhighlight lang="rust"> // クロスプラットフォームのアクセラレーター MenuItemBuilder::with_id("save", "Save") .accelerator("CmdOrCtrl+S") // MacOS: Cmd, 他: Ctrl .build(app)?; </syntaxhighlight> <br> ==== システムトレイアイコンが表示されない ==== * 原因 *: アイコンファイルが見つからない、または、形式が不正 * 解決方法 *: <syntaxhighlight lang="rust"> // デフォルトアイコンを使用 TrayIconBuilder::new() .icon(app.default_window_icon().unwrap().clone()) .build(app)?; // カスタムアイコンを使用 let icon = Image::from_bytes(include_bytes!("../icons/tray.png"))?; TrayIconBuilder::new() .icon(icon) .build(app)?; </syntaxhighlight> <br> ==== Linuxでシステムトレイが動作しない ==== * 原因 *: デスクトップ環境がシステムトレイをサポートしていない。 * 解決方法 ** GTKベースの環境 (GNOME, XFCE) を使用 ** システムトレイ拡張機能をインストール ** フォールバックとしてウインドウベースのUIを提供<br> <br> ==== メニューイベントが重複して発生する ==== * 原因 *: イベントリスナーが複数回登録されている。 * 解決方法 *: <syntaxhighlight lang="typescript"> // useEffectでクリーンアップを正しく実装 useEffect(() => { let unlisten: (() => void) | undefined; const setup = async () => { unlisten = await listen('tauri://menu', handler); }; setup(); return () => { if (unlisten) { unlisten(); } }; }, []); </syntaxhighlight> <br><br> == 関連情報 == * [https://v2.tauri.app/reference/javascript/api/namespacemenu/ Tauri Menu API] * [https://v2.tauri.app/reference/javascript/api/namespacetray/ Tauri Tray API] * [https://v2.tauri.app/develop/window-menu/ Tauri Window Menu Guide] * [[Tauriの基礎 - 状態管理]] * [[Tauriの基礎 - ウインドウ管理]] * [[Tauriの基礎 - セキュリティモデル]] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板,Tauri,メニュー,Menu,システムトレイ,System Tray,コンテキストメニュー,デスクトップアプリ |description={{PAGENAME}} - Tauriアプリケーションにおけるメニューとシステムトレイの実装方法を詳しく解説 | This page is {{PAGENAME}} about Tauri menu and system tray |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Rust]]
Tauriの基礎 - メニューとシステムトレイ
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse