T-CREATOR

Tauri アプリのウィンドウ管理テクニック

Tauri アプリのウィンドウ管理テクニック

Tauri アプリケーションを開発する際、ウィンドウ管理は最も重要な要素の一つです。適切なウィンドウ管理により、ユーザーエクスペリエンスを大幅に向上させることができます。

この記事では、Tauri アプリのウィンドウ管理における実践的なテクニックを、初心者の方にもわかりやすく解説いたします。実際のエラーケースも含めて、実務で役立つ知識をお届けします。

基本的なウィンドウ作成と設定

Tauri アプリのウィンドウ管理は、tauri.conf.jsonファイルから始まります。このファイルでウィンドウの基本設定を行い、アプリケーションの見た目と動作を定義します。

ウィンドウ設定の基本構造

まず、tauri.conf.jsonファイルでウィンドウの基本設定を行います。

json{
  "tauri": {
    "windows": [
      {
        "label": "main",
        "title": "Tauriアプリ",
        "width": 800,
        "height": 600,
        "resizable": true,
        "fullscreen": false,
        "visible": true
      }
    ]
  }
}

この設定により、800x600 ピクセルのサイズで、リサイズ可能なメインウィンドウが作成されます。labelは後でウィンドウを参照する際の識別子として使用されます。

ウィンドウの作成と表示

TypeScript でウィンドウを作成する基本的なコードを見てみましょう。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// 新しいウィンドウを作成
const createNewWindow = async () => {
  try {
    const webview = new WebviewWindow('secondary', {
      url: 'index.html',
      title: 'セカンダリウィンドウ',
      width: 600,
      height: 400,
    });

    console.log('ウィンドウが正常に作成されました');
  } catch (error) {
    console.error('ウィンドウ作成エラー:', error);
  }
};

このコードでは、WebviewWindowクラスを使用して新しいウィンドウを作成しています。エラーハンドリングも含めて、安全にウィンドウを管理できます。

ウィンドウの状態管理と制御

ウィンドウの状態を適切に管理することで、ユーザーにとって使いやすいアプリケーションを作成できます。

ウィンドウの表示・非表示制御

ウィンドウの表示状態を動的に制御する方法を学びましょう。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// ウィンドウの表示制御
const controlWindowVisibility = async () => {
  const mainWindow = WebviewWindow.getByLabel('main');

  if (mainWindow) {
    // ウィンドウを非表示にする
    await mainWindow.hide();

    // 3秒後に再表示
    setTimeout(async () => {
      await mainWindow.show();
    }, 3000);
  }
};

このコードでは、メインウィンドウを一時的に非表示にして、3 秒後に再表示します。ユーザーに通知を表示する際などに活用できます。

ウィンドウサイズの動的変更

ユーザーの操作に応じてウィンドウサイズを変更する方法です。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// ウィンドウサイズの変更
const resizeWindow = async (
  width: number,
  height: number
) => {
  try {
    const mainWindow = WebviewWindow.getByLabel('main');

    if (mainWindow) {
      await mainWindow.setSize(width, height);
      console.log(
        `ウィンドウサイズを ${width}x${height} に変更しました`
      );
    }
  } catch (error) {
    console.error('ウィンドウサイズ変更エラー:', error);
  }
};

// 使用例
resizeWindow(1024, 768); // フルHDサイズに変更

このコードでは、setSizeメソッドを使用してウィンドウサイズを動的に変更しています。レスポンシブな UI を実現する際に役立ちます。

マルチウィンドウ管理の実践

複数のウィンドウを効率的に管理する方法を学びましょう。

ウィンドウ一覧の取得と管理

現在開いているウィンドウの一覧を取得し、適切に管理する方法です。

typescriptimport {
  WebviewWindow,
  getAll,
} from '@tauri-apps/api/window';

// 全ウィンドウの一覧を取得
const getAllWindows = async () => {
  try {
    const windows = await getAll();

    windows.forEach((window) => {
      console.log(
        `ウィンドウ: ${window.label}, タイトル: ${window.title}`
      );
    });

    return windows;
  } catch (error) {
    console.error('ウィンドウ一覧取得エラー:', error);
    return [];
  }
};

// 特定のラベルのウィンドウを閉じる
const closeWindowByLabel = async (label: string) => {
  try {
    const window = WebviewWindow.getByLabel(label);

    if (window) {
      await window.close();
      console.log(`${label}ウィンドウを閉じました`);
    }
  } catch (error) {
    console.error('ウィンドウ閉じるエラー:', error);
  }
};

このコードでは、getAll()メソッドで全ウィンドウを取得し、close()メソッドで特定のウィンドウを閉じることができます。

ウィンドウ間の通信

複数のウィンドウ間でデータをやり取りする方法を実装しましょう。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';
import { emit, listen } from '@tauri-apps/api/event';

// メインウィンドウからセカンダリウィンドウにメッセージを送信
const sendMessageToSecondary = async (message: string) => {
  try {
    const secondaryWindow =
      WebviewWindow.getByLabel('secondary');

    if (secondaryWindow) {
      await emit('message-from-main', { message });
      console.log('メッセージを送信しました:', message);
    }
  } catch (error) {
    console.error('メッセージ送信エラー:', error);
  }
};

// セカンダリウィンドウでメッセージを受信
const listenToMainWindow = async () => {
  try {
    await listen('message-from-main', (event) => {
      console.log(
        'メインウィンドウからのメッセージ:',
        event.payload
      );
      // 受信したメッセージを処理
    });
  } catch (error) {
    console.error('メッセージ受信エラー:', error);
  }
};

このコードでは、emitlistenを使用してウィンドウ間でイベントベースの通信を実現しています。

エラーハンドリングとトラブルシューティング

実際の開発で遭遇する可能性のあるエラーとその対処法を学びましょう。

よくあるエラーと解決策

1. ウィンドウが見つからないエラー

typescript// エラー例: "Window with label 'main' not found"
const handleWindowNotFound = async () => {
  try {
    const window = WebviewWindow.getByLabel('main');

    if (!window) {
      console.error('メインウィンドウが見つかりません');
      // ウィンドウを再作成する処理
      return;
    }

    await window.show();
  } catch (error) {
    console.error('ウィンドウ操作エラー:', error);
  }
};

このエラーは、指定したラベルのウィンドウが存在しない場合に発生します。getByLabelの戻り値をチェックして適切に処理しましょう。

2. ウィンドウ作成時の権限エラー

typescript// エラー例: "Permission denied"
const createWindowWithPermission = async () => {
  try {
    const webview = new WebviewWindow('new-window', {
      url: 'index.html',
      title: '新規ウィンドウ',
    });

    // ウィンドウが正常に作成されたかチェック
    webview.once('tauri://created', () => {
      console.log('ウィンドウが正常に作成されました');
    });

    webview.once('tauri://error', (error) => {
      console.error('ウィンドウ作成エラー:', error);
    });
  } catch (error) {
    console.error('ウィンドウ作成失敗:', error);
  }
};

このコードでは、ウィンドウ作成の成功・失敗をイベントで監視し、適切にエラーハンドリングを行っています。

3. ウィンドウサイズ変更時の制約エラー

typescript// エラー例: "Invalid size"
const setValidWindowSize = async (
  width: number,
  height: number
) => {
  try {
    // 最小サイズの制約をチェック
    const minWidth = 300;
    const minHeight = 200;

    const validWidth = Math.max(width, minWidth);
    const validHeight = Math.max(height, minHeight);

    const mainWindow = WebviewWindow.getByLabel('main');

    if (mainWindow) {
      await mainWindow.setSize(validWidth, validHeight);
      console.log(
        `ウィンドウサイズを ${validWidth}x${validHeight} に設定しました`
      );
    }
  } catch (error) {
    console.error('ウィンドウサイズ設定エラー:', error);
  }
};

このコードでは、最小サイズの制約を設けて、無効なサイズが設定されることを防いでいます。

高度なウィンドウ管理テクニック

より高度なウィンドウ管理の実装方法を学びましょう。

ウィンドウの位置制御

ウィンドウを画面の特定の位置に配置する方法です。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// ウィンドウを画面中央に配置
const centerWindow = async () => {
  try {
    const mainWindow = WebviewWindow.getByLabel('main');

    if (mainWindow) {
      // 現在のウィンドウサイズを取得
      const size = await mainWindow.innerSize();

      // 画面サイズを取得(簡易的な実装)
      const screenWidth = window.screen.width;
      const screenHeight = window.screen.height;

      // 中央位置を計算
      const x = (screenWidth - size.width) / 2;
      const y = (screenHeight - size.height) / 2;

      await mainWindow.setPosition(x, y);
      console.log('ウィンドウを中央に配置しました');
    }
  } catch (error) {
    console.error('ウィンドウ位置設定エラー:', error);
  }
};

このコードでは、ウィンドウを画面の中央に配置する処理を実装しています。

ウィンドウの最小化・最大化制御

ウィンドウの状態を動的に変更する方法です。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// ウィンドウの状態制御
const controlWindowState = async (
  action: 'minimize' | 'maximize' | 'unmaximize'
) => {
  try {
    const mainWindow = WebviewWindow.getByLabel('main');

    if (mainWindow) {
      switch (action) {
        case 'minimize':
          await mainWindow.minimize();
          console.log('ウィンドウを最小化しました');
          break;
        case 'maximize':
          await mainWindow.maximize();
          console.log('ウィンドウを最大化しました');
          break;
        case 'unmaximize':
          await mainWindow.unmaximize();
          console.log('ウィンドウの最大化を解除しました');
          break;
      }
    }
  } catch (error) {
    console.error('ウィンドウ状態変更エラー:', error);
  }
};

このコードでは、ウィンドウの最小化・最大化・最大化解除を動的に制御できます。

ウィンドウのフォーカス制御

特定のウィンドウにフォーカスを設定する方法です。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// ウィンドウにフォーカスを設定
const focusWindow = async (label: string) => {
  try {
    const window = WebviewWindow.getByLabel(label);

    if (window) {
      await window.setFocus();
      console.log(
        `${label}ウィンドウにフォーカスを設定しました`
      );
    }
  } catch (error) {
    console.error('フォーカス設定エラー:', error);
  }
};

// 全ウィンドウのフォーカス状態を確認
const checkWindowFocus = async () => {
  try {
    const windows = await WebviewWindow.getAll();

    for (const window of windows) {
      const isFocused = await window.isFocused();
      console.log(
        `${window.label}: フォーカス状態 = ${isFocused}`
      );
    }
  } catch (error) {
    console.error('フォーカス状態確認エラー:', error);
  }
};

このコードでは、特定のウィンドウにフォーカスを設定し、全ウィンドウのフォーカス状態を確認できます。

パフォーマンス最適化のテクニック

ウィンドウ管理におけるパフォーマンスを最適化する方法を学びましょう。

ウィンドウの遅延作成

必要な時だけウィンドウを作成する方法です。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// ウィンドウの遅延作成
class WindowManager {
  private windows: Map<string, WebviewWindow> = new Map();

  async getOrCreateWindow(label: string, options: any) {
    try {
      // 既存のウィンドウをチェック
      let window = this.windows.get(label);

      if (!window) {
        // 新しいウィンドウを作成
        window = new WebviewWindow(label, options);
        this.windows.set(label, window);

        // ウィンドウが閉じられた時の処理
        window.once('tauri://close-requested', () => {
          this.windows.delete(label);
          console.log(
            `${label}ウィンドウを管理から削除しました`
          );
        });
      }

      return window;
    } catch (error) {
      console.error('ウィンドウ取得・作成エラー:', error);
      return null;
    }
  }

  // 全ウィンドウを閉じる
  async closeAllWindows() {
    try {
      for (const [label, window] of this.windows) {
        await window.close();
      }
      this.windows.clear();
      console.log('全ウィンドウを閉じました');
    } catch (error) {
      console.error('全ウィンドウ閉じるエラー:', error);
    }
  }
}

// 使用例
const windowManager = new WindowManager();
const settingsWindow =
  await windowManager.getOrCreateWindow('settings', {
    url: 'settings.html',
    title: '設定',
    width: 400,
    height: 300,
  });

このコードでは、WindowManagerクラスを使用してウィンドウの遅延作成と効率的な管理を実現しています。

メモリリークの防止

ウィンドウ管理におけるメモリリークを防ぐ方法です。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// メモリリークを防ぐウィンドウ管理
class SafeWindowManager {
  private activeWindows: Set<string> = new Set();

  async createSafeWindow(label: string, options: any) {
    try {
      // 既に存在するウィンドウをチェック
      if (this.activeWindows.has(label)) {
        console.warn(`${label}ウィンドウは既に存在します`);
        return WebviewWindow.getByLabel(label);
      }

      const window = new WebviewWindow(label, options);
      this.activeWindows.add(label);

      // ウィンドウが閉じられた時のクリーンアップ
      window.once('tauri://close-requested', () => {
        this.activeWindows.delete(label);
        console.log(
          `${label}ウィンドウをクリーンアップしました`
        );
      });

      return window;
    } catch (error) {
      console.error('安全なウィンドウ作成エラー:', error);
      return null;
    }
  }

  // アクティブなウィンドウ数を取得
  getActiveWindowCount() {
    return this.activeWindows.size;
  }

  // 全ウィンドウの状態をログ出力
  logWindowStatus() {
    console.log(
      `アクティブなウィンドウ数: ${this.activeWindows.size}`
    );
    console.log(
      'アクティブなウィンドウ:',
      Array.from(this.activeWindows)
    );
  }
}

このコードでは、アクティブなウィンドウを追跡し、適切なクリーンアップを行うことでメモリリークを防いでいます。

実践的なウィンドウ管理パターン

実際のアプリケーションで使用できる実践的なパターンを紹介いたします。

モーダルウィンドウの実装

モーダルウィンドウを作成する実践的なパターンです。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// モーダルウィンドウの作成
const createModalWindow = async (
  title: string,
  content: string
) => {
  try {
    const modal = new WebviewWindow('modal', {
      url: 'modal.html',
      title: title,
      width: 400,
      height: 300,
      resizable: false,
      alwaysOnTop: true,
      center: true,
    });

    // モーダルウィンドウの結果を待つ
    return new Promise((resolve) => {
      modal.once('tauri://close-requested', () => {
        resolve('modal-closed');
      });

      // モーダルからの結果を受信
      modal.once('modal-result', (event) => {
        resolve(event.payload);
      });
    });
  } catch (error) {
    console.error('モーダルウィンドウ作成エラー:', error);
    return null;
  }
};

// 使用例
const showConfirmDialog = async () => {
  const result = await createModalWindow(
    '確認',
    '本当に削除しますか?'
  );

  if (result === 'confirm') {
    console.log('削除が確認されました');
  } else {
    console.log('削除がキャンセルされました');
  }
};

このコードでは、モーダルウィンドウを作成し、ユーザーの操作結果を待つ処理を実装しています。

ツールウィンドウの実装

開発者ツールのようなツールウィンドウを作成する方法です。

typescriptimport { WebviewWindow } from '@tauri-apps/api/window';

// ツールウィンドウの作成
const createToolWindow = async (toolType: string) => {
  try {
    const toolWindow = new WebviewWindow(
      `tool-${toolType}`,
      {
        url: `tools/${toolType}.html`,
        title: `${toolType}ツール`,
        width: 600,
        height: 400,
        resizable: true,
        alwaysOnTop: false,
      }
    );

    // ツールウィンドウの位置を設定
    const mainWindow = WebviewWindow.getByLabel('main');
    if (mainWindow) {
      const mainPosition = await mainWindow.outerPosition();
      const mainSize = await mainWindow.outerSize();

      // メインウィンドウの右側に配置
      await toolWindow.setPosition(
        mainPosition.x + mainSize.width + 10,
        mainPosition.y
      );
    }

    return toolWindow;
  } catch (error) {
    console.error('ツールウィンドウ作成エラー:', error);
    return null;
  }
};

// 使用例
const openDevTools = async () => {
  await createToolWindow('devtools');
};

このコードでは、メインウィンドウの右側にツールウィンドウを配置する処理を実装しています。

まとめ

Tauri アプリのウィンドウ管理は、適切な実装によりユーザーエクスペリエンスを大幅に向上させることができます。

この記事で学んだテクニックを活用することで、以下のような効果が期待できます:

  • 効率的なウィンドウ管理: 適切なエラーハンドリングとメモリ管理により、安定したアプリケーションを構築できます
  • ユーザビリティの向上: ウィンドウの位置やサイズを動的に制御することで、使いやすいインターフェースを提供できます
  • パフォーマンスの最適化: 遅延作成やクリーンアップにより、リソースを効率的に使用できます

実際の開発では、これらのテクニックを組み合わせて、プロジェクトの要件に合わせたウィンドウ管理システムを構築してください。エラーハンドリングを適切に行い、ユーザーにとって快適なアプリケーションを作成することが重要です。

関連リンク