Tauriの基礎 - ダイアログ

提供: MochiuWiki : SUSE, EC, PCB

2026年3月5日 (木) 02:29時点におけるWiki (トーク | 投稿記録)による版 (概要)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

概要

Tauri v2のダイアログプラグイン (dialog) は、デスクトップアプリケーションでネイティブなファイル選択ダイアログやメッセージダイアログを表示するための機能を提供する。
このプラグインを使用することで、オペレーティングシステムの標準的なダイアログを表示でき、ユーザにとって馴染みのあるインターフェースを提供できる。

ファイルオープンダイアログ (open) では、単一または複数のファイル選択、ディレクトリ選択、フィルターによる拡張子制限等が可能である。
ファイル保存ダイアログ (save) では、保存先の選択とデフォルトのファイル名や拡張子フィルターの設定ができる。
メッセージダイアログには、情報表示 (message)、Yes / No確認 (ask)、Ok / Cancel確認 (confirm) の3種類があり、それぞれ異なる用途に使用できる。

全てのダイアログAPIは非同期で設計されており、React等のモダンなフロントエンドフレームワークでの使用に適している。


ファイルオープンダイアログ

インストール

ダイアログプラグインを使用するには、まずインストールを行う。

npm run tauri add dialog


基本的な使用方法

open 関数を使用して、ファイル選択ダイアログを表示できる。

 import { open } from '@tauri-apps/plugin-dialog';
 
 // 単一ファイルを選択する関数
 async function selectFile(): Promise<string | null> {
   const selected = await open({
     multiple: false,  // 単一選択
     directory: false  // ファイル選択 (ディレクトリではない)
   });
 
   if (selected && typeof selected === 'string') {
     console.log('選択されたファイル:', selected);
     return selected;
   }
 
   console.log('ファイル選択がキャンセルされました');
   return null;
 }


複数ファイルの選択

multiple: true オプションを指定すると、複数のファイルを選択できる。

 import { open } from '@tauri-apps/plugin-dialog';
 
 // 複数ファイルを選択する関数
 async function selectMultipleFiles(): Promise<string[]> {
   const selected = await open({
     multiple: true,   // 複数選択を有効化
     directory: false
   });
 
   if (selected === null) {
     console.log('ファイル選択がキャンセルされました');
     return [];
   }
 
   // 選択結果は配列になる
   const files = Array.isArray(selected) ? selected : [selected];
   console.log(`${files.length}個のファイルが選択されました`);
 
   return files;
 }


ディレクトリの選択

directory: true オプションを指定すると、ディレクトリの選択が可能になる。

 import { open } from '@tauri-apps/plugin-dialog';
 
 // ディレクトリを選択する関数
 async function selectDirectory(): Promise<string | null> {
   const selected = await open({
     directory: true,  // ディレクトリ選択モード
     multiple: false
   });
 
   if (selected && typeof selected === 'string') {
     console.log('選択されたディレクトリ:', selected);
     return selected;
   }
 
   return null;
 }


オプション一覧

open() 関数のオプション一覧
オプション 説明 デフォルト値
multiple boolean 複数選択を許可するか false
directory boolean ディレクトリ選択モード false
filters Filter[] ファイル拡張子フィルター []
defaultPath string 初期表示パス (OS依存)
title string ダイアログのタイトル (OS依存)
canCreateDirectories boolean ディレクトリ作成を許可 (MacOS) true
recursive boolean 再帰的検索を有効化 false


Filterオブジェクトの構造を以下に示す。

 interface Filter {
   name: string;          // フィルター名 (表示用)
   extensions: string[];  // 拡張子の配列 (ドットなし)
 }



ファイル保存ダイアログ

save 関数を使用して、ファイル保存ダイアログを表示できる。

基本的な使用方法

 import { save } from '@tauri-apps/plugin-dialog';
 
 // ファイル保存先を選択する関数
 async function selectSavePath(): Promise<string | null> {
   const path = await save({
     defaultPath: 'untitled.txt',
     filters: [
       { name: 'テキストファイル', extensions: ['txt'] }
     ]
   });
 
   if (path) {
     console.log('保存先:', path);
     return path;
   }
 
   console.log('保存がキャンセルされました');
   return null;
 }


拡張子フィルターの設定

複数のフィルターを設定して、ユーザが保存するファイルの種類を選択できるようにする。

 import { save } from '@tauri-apps/plugin-dialog';
 
 // 画像の保存先を選択する関数
 async function selectImageSavePath(): Promise<string | null> {
   const path = await save({
     defaultPath: 'image.png',
     filters: [
       { name: 'PNG画像', extensions: ['png'] },
       { name: 'JPEG画像', extensions: ['jpg', 'jpeg'] },
       { name: '全てのファイル', extensions: ['*'] }
     ]
   });
 
   return path;
 }


オプション一覧

save() 関数のオプション一覧
オプション 説明 デフォルト値
defaultPath string 初期ファイル名またはパス (OS依存)
filters Filter[] ファイル拡張子フィルター []
title string ダイアログのタイトル (OS依存)
canCreateDirectories boolean ディレクトリ作成を許可 (MacOS) true



メッセージダイアログ

情報ダイアログ (message)

message 関数は、ユーザに情報を表示するためのダイアログを表示する。

 import { message } from '@tauri-apps/plugin-dialog';
 
 // 情報メッセージを表示する関数
 async function showInfo(): Promise<void> {
   await message('処理が完了しました。', {
     title: '完了',
     kind: 'info'  // 情報アイコンを表示
   });
 }
 
 // 警告メッセージを表示する関数
 async function showWarning(): Promise<void> {
   await message('この操作は取り消せません。', {
     title: '警告',
     kind: 'warning'  // 警告アイコンを表示
   });
 }
 
 // エラーメッセージを表示する関数
 async function showError(errorMessage: string): Promise<void> {
   await message(`エラーが発生しました:\n${errorMessage}`, {
     title: 'エラー',
     kind: 'error'  // エラーアイコンを表示
   });
 }


Yes / No確認ダイアログ (ask)

ask 関数は、ユーザにYes / Noで回答を求めるダイアログを表示する。

戻り値の型はbooleanであり、Yesが選択された場合は true、Noが選択された場合は false を返す。

 import { ask } from '@tauri-apps/plugin-dialog';
 
 // 終了確認ダイアログを表示する関数
 async function confirmExit(): Promise<boolean> {
   const shouldExit = await ask('アプリケーションを終了しますか?', {
     title: '終了確認',
     kind: 'warning'
   });
 
   if (shouldExit) {
     console.log('ユーザは終了を選択しました');
   }
   else {
     console.log('ユーザは終了をキャンセルしました');
   }
 
   return shouldExit;
 }
 
 // 保存確認ダイアログを表示する関数
 async function confirmSave(): Promise<boolean> {
   const shouldSave = await ask('変更を保存しますか?', {
     title: '保存確認',
     kind: 'info'
   });
 
   return shouldSave;
 }


Ok / Cancel確認ダイアログ (confirm)

confirm 関数は、ユーザにOk / Cancelで確認を求めるダイアログを表示する。

戻り値の型はbooleanであり、Okが選択された場合は true、Cancelが選択された場合は false を返す。

 import { confirm } from '@tauri-apps/plugin-dialog';
 
 // 削除確認ダイアログを表示する関数
 async function confirmDelete(fileName: string): Promise<boolean> {
   const shouldDelete = await confirm(
     `「${fileName}」を削除してもよろしいですか?\nこの操作は取り消せません。`,
     {
       title: '削除確認',
       kind: 'warning'
     }
   );
 
   return shouldDelete;
 }
 
 // 上書き確認ダイアログを表示する関数
 async function confirmOverwrite(filePath: string): Promise<boolean> {
   const shouldOverwrite = await confirm(
     `ファイル「${filePath}」は既に存在します。\n上書きしますか?`,
     {
       title: '上書き確認',
       kind: 'warning'
     }
   );
 
   return shouldOverwrite;
 }


メッセージダイアログのオプション

メッセージダイアログのオプション一覧
オプション 説明 使用可能な関数
title string ダイアログのタイトル message, ask, confirm
kind string ダイアログの種類 (アイコン) message, ask, confirm
okLabel string Okボタンのラベル (カスタマイズ) confirm
cancelLabel string Cancelボタンのラベル confirm


kindの値
説明
info 情報アイコン (青)
warning 警告アイコン (黄)
error エラーアイコン (赤)



フィルター設定

ファイルダイアログのフィルター機能について以下に示す。

フィルターの構造

 interface DialogFilter {
   name: string;          // フィルターの表示名
   extensions: string[];  // 拡張子の配列 (ドットなし)
 }


使用例

 import { open, save } from '@tauri-apps/plugin-dialog';
 
 // 画像ファイルのみを選択可能にする
 async function selectImageFile(): Promise<string | null> {
   const path = await open({
     multiple: false,
     filters: [
       { 
         name: '画像ファイル', 
         extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'] 
       },
       { 
         name: 'PNG', 
         extensions: ['png'] 
       },
       { 
         name: 'JPEG', 
         extensions: ['jpg', 'jpeg'] 
       },
       { 
         name: '全てのファイル', 
         extensions: ['*'] 
       }
     ]
   });
 
   return typeof path === 'string' ? path : null;
 }
 
 // ドキュメントファイルの保存先を選択
 async function selectDocumentSavePath(): Promise<string | null> {
   const path = await save({
     defaultPath: 'document.md',
     filters: [
       { name: 'Markdown', extensions: ['md', 'markdown'] },
       { name: 'テキスト', extensions: ['txt'] },
       { name: 'HTML', extensions: ['html', 'htm'] }
     ]
   });
 
   return path;
 }


よく使用されるフィルター設定

よく使用されるフィルター設定例
用途 フィルター設定
画像ファイル { name: '画像', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp'] }
ドキュメント { name: 'ドキュメント', extensions: ['pdf', 'doc', 'docx', 'txt', 'md'] }
ソースコード { name: 'ソースコード', extensions: ['ts', 'tsx', 'js', 'jsx', 'py', 'rs', 'go'] }
音声ファイル { name: '音声', extensions: ['mp3', 'wav', 'ogg', 'flac', 'm4a'] }
動画ファイル { name: '動画', extensions: ['mp4', 'avi', 'mov', 'mkv', 'webm'] }
JSONファイル { name: 'JSON', extensions: ['json'] }
設定ファイル { name: '設定', extensions: ['json', 'yaml', 'yml', 'toml', 'ini'] }



複数選択オプション

戻り値の処理

multiple: true を指定した場合、戻り値は配列または null になる。

 import { open } from '@tauri-apps/plugin-dialog';
 
 // 複数ファイル選択の処理例
 async function handleMultipleFileSelection(): Promise<void> {
   const selected = await open({
     multiple: true,
     filters: [
       { name: 'テキストファイル', extensions: ['txt', 'md'] }
     ]
   });
 
   // 選択がキャンセルされた場合
   if (selected === null) {
     console.log('選択がキャンセルされました');
     return;
   }
 
   // 選択結果を配列に変換
   const files = Array.isArray(selected) ? selected : [selected];
 
   // 各ファイルを処理
   for (const filePath of files) {
     console.log(`選択されたファイル: ${filePath}`);
     // ファイルの処理を実行
   }
 
   console.log(`合計 ${files.length} 個のファイルが選択されました`);
 }


Reactコンポーネントでの使用例

 import { useState } from 'react';
 import { open } from '@tauri-apps/plugin-dialog';
 
 // ファイル選択コンポーネント
 function FileSelector() {
   const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
   const [isLoading, setIsLoading] = useState(false);
 
   // ファイル選択ハンドラー
   const handleSelectFiles = async () => {
     setIsLoading(true);
 
     try {
       const selected = await open({
         multiple: true,
         filters: [
           { name: '画像ファイル', extensions: ['png', 'jpg', 'jpeg', 'gif'] }
         ]
       });
 
       if (selected !== null) {
         const files = Array.isArray(selected) ? selected : [selected];
         setSelectedFiles(files);
       }
     }
     catch (error) {
       console.error('ファイル選択エラー:', error);
     }
     finally {
       setIsLoading(false);
     }
   };
 
   // 選択をクリア
   const handleClear = () => {
     setSelectedFiles([]);
   };
 
   return (
     <div className="file-selector">
       <div className="buttons">
         <button 
           onClick={handleSelectFiles}
           disabled={isLoading}
         >
           {isLoading ? '選択中...' : 'ファイルを選択'}
         </button>
         {selectedFiles.length > 0 && (
           <button onClick={handleClear}>選択をクリア</button>
         )}
       </div>
 
       {selectedFiles.length > 0 && (
         <div className="selected-files">
           <h3>選択されたファイル ({selectedFiles.length})</h3>
           <ul>
             {selectedFiles.map((file, index) => (
               <li key={index}>{file}</li>
             ))}
           </ul>
         </div>
       )}
     </div>
   );
 }
 
 export default FileSelector;



サンプルコード

ファイルエディタアプリケーション

ダイアログとファイルシステムプラグインを組み合わせたファイルエディタの定義例を示す。

 import { useState, useCallback, useEffect } from 'react';
 import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
 import { open, save, ask, message } from '@tauri-apps/plugin-dialog';
 
 // エディタの状態を管理する型
 interface EditorState {
   content: string;
   filePath: string | null;
   isModified: boolean;
   isLoading: boolean;
 }
 
 // ファイルエディタコンポーネント
 function FileEditor() {
   const [state, setState] = useState<EditorState>({
     content: '',
     filePath: null,
     isModified: false,
     isLoading: false
   });
 
   // 保存確認を行う関数
   const confirmSaveIfNeeded = async (): Promise<boolean> => {
     if (!state.isModified) {
       return true;  // 変更がない場合は続行
     }
 
     const shouldSave = await ask(
       '未保存の変更があります。保存しますか?',
       { title: '保存確認', kind: 'warning' }
     );
 
     if (shouldSave) {
       await handleSave();
     }
 
     return true;  // 保存してもしなくても続行
   };
 
   // ファイルを開く処理
   const handleOpen = useCallback(async () => {
     // 未保存の変更があれば確認
     await confirmSaveIfNeeded();
 
     // ファイル選択ダイアログを表示
     const selectedPath = await open({
       multiple: false,
       filters: [
         { name: 'テキストファイル', extensions: ['txt', 'md', 'json'] },
         { name: '全てのファイル', extensions: ['*'] }
       ]
     });
 
     if (selectedPath === null || typeof selectedPath !== 'string') {
       return;  // キャンセルされた
     }
 
     setState(prev => ({ ...prev, isLoading: true }));
 
     try {
       // ファイルの内容を読み込む
       const content = await readTextFile(selectedPath);
       setState({
         content,
         filePath: selectedPath,
         isModified: false,
         isLoading: false
       });
 
       console.log(`ファイルを開きました: ${selectedPath}`);
     }
     catch (error) {
       setState(prev => ({ ...prev, isLoading: false }));
       await message(
         `ファイルを開けませんでした:\n${error}`,
         { title: 'エラー', kind: 'error' }
       );
     }
   }, [state.isModified]);
 
   // ファイルを保存する処理
   const handleSave = useCallback(async () => {
     let targetPath = state.filePath;
 
     // ファイルパスがない場合は保存ダイアログを表示
     if (!targetPath) {
       targetPath = await save({
         filters: [
           { name: 'テキストファイル', extensions: ['txt'] }
         ],
         defaultPath: 'untitled.txt'
       }) as string | null;
 
       if (!targetPath) {
         return;  // キャンセルされた
       }
     }
 
     setState(prev => ({ ...prev, isLoading: true }));
 
     try {
       // ファイルに書き込む
       await writeTextFile(targetPath, state.content);
       setState(prev => ({
         ...prev,
         filePath: targetPath,
         isModified: false,
         isLoading: false
       }));
 
       console.log(`ファイルを保存しました: ${targetPath}`);
     }
     catch (error) {
       setState(prev => ({ ...prev, isLoading: false }));
       await message(
         `ファイルを保存できませんでした:\n${error}`,
         { title: 'エラー', kind: 'error' }
       );
     }
   }, [state.content, state.filePath]);
 
   // 名前を付けて保存
   const handleSaveAs = useCallback(async () => {
     const targetPath = await save({
       filters: [
         { name: 'テキストファイル', extensions: ['txt'] }
       ],
       defaultPath: state.filePath || 'untitled.txt'
     });
 
     if (!targetPath) {
       return;  // キャンセルされた
     }
 
     setState(prev => ({ ...prev, isLoading: true }));
 
     try {
       await writeTextFile(targetPath, state.content);
       setState(prev => ({
         ...prev,
         filePath: targetPath,
         isModified: false,
         isLoading: false
       }));
 
       console.log(`ファイルを保存しました: ${targetPath}`);
     }
     catch (error) {
       setState(prev => ({ ...prev, isLoading: false }));
       await message(
         `ファイルを保存できませんでした:\n${error}`,
         { title: 'エラー', kind: 'error' }
       );
     }
   }, [state.content, state.filePath]);
 
   // 新規ファイル
   const handleNew = useCallback(async () => {
     await confirmSaveIfNeeded();
 
     setState({
       content: '',
       filePath: null,
       isModified: false,
       isLoading: false
     });
   }, [state.isModified]);
 
   // テキスト変更時の処理
   const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
     setState(prev => ({
       ...prev,
       content: e.target.value,
       isModified: true
     }));
   };
 
   // ウインドウを閉じる前の確認
   useEffect(() => {
     const handleBeforeUnload = (e: BeforeUnloadEvent) => {
       if (state.isModified) {
         e.preventDefault();
         e.returnValue = '';
       }
     };
 
     window.addEventListener('beforeunload', handleBeforeUnload);
     return () => window.removeEventListener('beforeunload', handleBeforeUnload);
   }, [state.isModified]);
 
   return (
     <div className="file-editor">
       <div className="toolbar">
         <button onClick={handleNew} disabled={state.isLoading}>
           新規
         </button>
         <button onClick={handleOpen} disabled={state.isLoading}>
           開く
         </button>
         <button onClick={handleSave} disabled={state.isLoading || !state.isModified}>
           保存
         </button>
         <button onClick={handleSaveAs} disabled={state.isLoading}>
           名前を付けて保存
         </button>
         <span className="status">
           {state.filePath || '未保存'}
           {state.isModified && ' *'}
         </span>
       </div>
 
       <textarea
         value={state.content}
         onChange={handleChange}
         disabled={state.isLoading}
         placeholder="ここにテキストを入力..."
       />
     </div>
   );
 }
 
 export default FileEditor;



エラーハンドリング

ダイアログ操作におけるエラーハンドリングの定義例を示す。

 import { open, save, message } from '@tauri-apps/plugin-dialog';
 
 // エラーハンドリングを含むファイル選択関数
 async function safeFileOpen(): Promise<string | null> {
   try {
     const path = await open({
       multiple: false,
       filters: [
         { name: 'テキスト', extensions: ['txt'] }
       ]
     });
 
     // ユーザがキャンセルした場合
     if (path === null) {
       console.log('ファイル選択がキャンセルされました');
       return null;
     }
 
     // 単一選択の場合は文字列が返る
     if (typeof path === 'string') {
       return path;
     }
 
     // 予期しない戻り値
     console.warn('予期しない戻り値:', path);
     return null;
   }
   catch (error) {
     // エラーが発生した場合
     console.error('ダイアログエラー:', error);
 
     await message(
       'ファイル選択ダイアログを開けませんでした。',
       { title: 'エラー', kind: 'error' }
     );
 
     return null;
   }
 }



トラブルシューティング

ダイアログが表示されない場合

ファイルダイアログが表示されない場合は、以下に示す事柄を確認する。

  • Capabilities設定の確認
    dialog:default パーミッションが設定されているか確認する。

  • 非同期処理の確認
    await を使用して、ダイアログの結果を待機しているか確認する。

  • エラーログの確認
    コンソールにエラーメッセージが出力されていないか確認する。


キャンセル時の挙動

ユーザがダイアログをキャンセルした場合の戻り値は null である。

 const result = await open();
 
 if (result === null) {
   // キャンセルされた場合の処理
   console.log('キャンセルされました');
   return;
 }
 
 // ファイルが選択された場合の処理
 console.log('選択された:', result);


プラットフォーム固有の注意事項

プラットフォーム固有の注意事項
プラットション 注意事項
Windows defaultPath は絶対パスまたは相対パスを受け付ける。
MacOS canCreateDirectories オプションでディレクトリ作成を制御可能
Linux GTKダイアログが使用される。
一部の機能が制限される場合がある。



参考リンク