別のウィンドウ作成時にデータを渡す

Recipe ID: win-019

新しいサブウィンドウを開く (Vite Multi-page) (win-017) の構成を前提に、ウィンドウ作成時に親から子へデータを渡す実装方法を紹介します。

単純にウィンドウ作成直後にイベントを emit しても、子ウィンドウ側のロード(HTML/JSの読み込み)が完了しておらず、イベントを受け取れない「競合状態(Race Condition)」が発生します。
これを回避するため、子ウィンドウから「準備完了」イベントを送信し、それを受け取った親がデータを送る(ハンドシェイク) パターンを実装します。

前提条件: win-017 との差分

win-017 のディレクトリ構成(subwindow.html, src/subwindow/main.ts など)をそのまま使用します。

Permissions (権限) の設定

win-017 の権限設定に加え、イベントの送受信を行うために core:event:default が必要です。

src-tauri/capabilities/default.jsonpermissions 配列に以下を追加してください。

{
  "permissions": [
    ...,
    "core:window:default",
    "core:webview:allow-create-webview-window",
    "core:window:allow-close",
    "core:event:default"  // 追加: イベント送受信に必要
  ],
  "windows": ["main", "settings-window"]
}

1. 子ページ(サブウィンドウ)の変更

サブウィンドウ側では、画面の準備が整ったことを親に通知し、その後親から送られてくるデータをリッスンします。

src/subwindow/main.ts

win-017 のコードに対し、イベントリスナーと通知を行う処理を追加します。

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

const appWindow = getCurrentWindow();
const outputEl = document.getElementById('message-output');

// 1. 親からのデータを受信するリスナーを設定
listen('init-data', (event) => {
    const data = event.payload as { userId: number, userName: string };
    console.log('親からデータを受信:', data);
    
    if (outputEl) {
        outputEl.innerText = `UserID: ${data.userId}, Name: ${data.userName}`;
    }
});

document.getElementById('close-btn')?.addEventListener('click', async () => {
    await appWindow.close();
});

// 2. 準備完了を親ウィンドウに通知 (ハンドシェイク開始)
// HTML要素の描画やリスナー登録が終わったタイミングで実行します
emit('subwindow-ready');

subwindow.html<div id="message-output"></div> などを追加して、データが表示されるようにしておくと動作確認がしやすくなります。

2. 親ページ(メインウィンドウ)の変更

親ウィンドウ側では、ウィンドウを作成する前に「準備完了」イベントのリスナーを待機させておき、イベントを受信したらデータを送信します。

src/main.ts

WebviewWindow を作成する前後のロジックを変更します。

import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { listen, emitTo } from '@tauri-apps/api/event';

async function openSubWindowWithData() {
  const label = 'settings-window';

  // 既に存在するかチェック
  const existingWin = await WebviewWindow.getByLabel(label);
  if (existingWin) {
    await existingWin.setFocus();
    // 既に開いている場合もデータを再送したいならここで emitTo を呼ぶ
    return;
  }

  // 1. サブウィンドウからの「準備完了」イベントを待ち受ける
  // once: true にすることで、1回受信したらリスナーを自動解除
  const unlisten = await listen('subwindow-ready', async (event) => {
    console.log('サブウィンドウの準備が完了しました');

    // 2. 特定のウィンドウ(ラベル: settings-window)に向けてデータを送信
    // emitTo(label, eventName, payload)
    await emitTo(label, 'init-data', { 
        userId: 1001, 
        userName: 'Tauri User' 
    });
    
    // リスナー役目が終わったので明示的に解除(onceを使っていない場合など)
    unlisten(); 
  });

  // 3. ウィンドウを作成
  const webview = new WebviewWindow(label, {
    url: '/subwindow.html',
    title: '設定 (データ受信)',
    width: 600,
    height: 400,
  });

  webview.once('tauri://error', async (e) => {
    console.error('ウィンドウ作成エラー:', e);
    // エラー時はリスナーが残り続けないように解除
    unlisten(); 
  });
}

document.querySelector('#open-settings')?.addEventListener('click', openSubWindowWithData);

解説

ハンドシェイクの流れ

1. : listen('subwindow-ready') で待ち受け開始。
2. : new WebviewWindow(...) でウィンドウ作成。
3. : HTML/JS がロードされる。
4. : listen('init-data') でデータ待ち受け準備。
5. : emit('subwindow-ready') を発火。
6. : subwindow-ready を検知し、emitTo(..., 'init-data', ...) でデータを送信。
7. : init-data を受信し、画面に反映。

この手順を踏むことで、ウィンドウのロード待ちによるデータの取りこぼしを防ぐことができます。

別のアプローチ (Pull 型)

子ウィンドウ側から能動的にデータを取得する「Pull 型」のアプローチもあります。
子ウィンドウのマウント時(起動時)に invoke コマンドでバックエンドからデータを取得したり、親ウィンドウにリクエストを送って返答をもらう方法です。データの依存関係が単純な場合は Pull 型の方が実装がシンプルになる場合もあります。