[Electron] ダイアログで指定したファイルを読み込む

今回はファイルダイアログでファイルを指定しその内容を読み込んでみます。

Electronで実装するにはいくつかのパターンがありますが、ここではOSの機能を利用するダイアログの表示とファイルを読み込む部分はメインプロセスが担当し、それ以外の部分をレンダラープロセス(要はChromium)が行うことにします。レンダラーでもOSの機能を利用できますがメインプロセスに任せた方が役割分担がはっきりして個人的に気持ち良いためです。

※「保存」が行いたい方はこちらの記事を参照ください。

デモ動画

「読み込み」ボタンを押すとファイルダイアログが開き、指定したテキストファイルの内容がテキストエリアに表示されます。

ソースコード

実際に稼働するソースはGitHubからも確認できます。 github.com

メインプロセス

実際の処理は48行目以降のipcMain.handle('file-open'から始まる箇所で行っています。

ipcMain.handle()はレンダラープロセスからの通信を受け取るための物で、第1引数にイベント名、第2引数に具体的な処理を書いた関数を指定します。

その他のElectronのチュートリアルと異なる点は背景色を変更しておきました。

  • 1行目でipcMain, dialogを追加で読み込んでいます。
  • 2行目でファイル処理を行いますのでfsモジュールを追加
  • 4行目でウィンドウを管理する変数をグローバルで持ちます
const { app, ipcMain, BrowserWindow, dialog } = require('electron')
const fs = require('fs')

let mainWin;

/**
 * ウィンドウを作成する
 */
function createWindow () {
  // ウィンドウを新たに開く
  mainWin = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  // ファイルを開く
  mainWin.loadFile('public/index.html')
}

// 初期化が終了したらウィンドウを新規に作成する
app.whenReady().then(createWindow)


// すべてのウィンドウが閉じられたときの処理
app.on('window-all-closed', () => {
  // macOS以外はアプリを終了する
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

// アプリがアクティブになった時の処理
// (macOSはDocのアイコンがクリックされたとき)
app.on('activate', () => {
  // ウィンドウがすべて閉じられている場合は新しく開く
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

/**
 * [IPC] 指定ファイルの内容を返却
 *
 */
ipcMain.handle('file-open', async (event) => {
  // ファイルを選択
  const paths = dialog.showOpenDialogSync(mainWin, {
    buttonLabel: '開く',  // 確認ボタンのラベル
    filters: [
      { name: 'Text', extensions: ['txt', 'text'] },
    ],
    properties:[
      'openFile',         // ファイルの選択を許可
      'createDirectory',  // ディレクトリの作成を許可 (macOS)
    ]
  });

  // キャンセルで閉じた場合
  if( paths === undefined ){
    return({status: undefined});
  }

  // ファイルの内容を返却
  try {
    const path = paths[0];
    const buff = fs.readFileSync(path);

    return({
      status: true,
      path: path,
      text: buff.toString()
    });
  }
  catch(error) {
    return({status:false, message:error.message});
  }
});

レンダラープロセス

「読み込み」ボタンが押されたら25行目のipcRenderer.invoke()でメインプロセス内の処理を呼び出しています。

ファイルを指定して内容を取得するところまでをメインプロセスで行っていますので、レンダラープロセス的には結果を待っていればよく、返ってきた結果をさばいてやるだけです。もしユーザーが何も選択しなかった場合にはundefined、何らかの理由で開けなかった場合は{status:false}が入っているのでそれぞれ適当な処理を行います。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
  <title>Electron Smaple - FileLoad</title>
  <style>
    body{ background-color:white; }
    #btn-load{ margin:12px; }
    #output{ width:100%; height:500px; }
  </style>
</head>
<body>

<form>
  <button type="button" id="btn-load">読み込み</button>
</form>
<textarea id="output"></textarea>

<script>
const {ipcRenderer} = require('electron');

document.querySelector('#btn-load').addEventListener('click', () => {
  // メインプロセスを呼び出し
  ipcRenderer.invoke('file-open')
    .then((data) => {
      // キャンセルで閉じた
      if( data.status === undefined ){
        return(false);
      }
      // ファイルが開けなかった
      if( ! data.status ){
        alert(`ファイルが開けませんでした\n${data.message}`);
        return(false);
      }

      // ファイルが無事に開けた
      document.querySelector('#output').value = data.text;
    })
    .catch((err) => {
      alert(err);
    });
});
</script>
</body>
</html>

関連ページ

ファイルダイアログで書き込みを行う場合はこちら。 blog.katsubemakito.net

参考ページ