Chapter 14無料公開

ファイル選択ダイアログからのファイルパスの取得 (dialog)

Kei Touge
Kei Touge
2021.09.18に更新

https://www.electronjs.org/docs/api/dialog

メインプロセス(メニュー)からダイアログを開く場合

メインプロセス

src/createMenu.ts
import {
  BrowserWindow,
  MenuItemConstructorOptions,
  dialog,
  Menu,
} from 'electron';

export const createMenu = (win: BrowserWindow) => {
  const template: MenuItemConstructorOptions[] = [
    {
      label: 'File',
      submenu: [
        {
          label: 'Open...',
          accelerator: 'CmdOrCtrl+O',
          click: async () =>
            dialog
              /**
               * ダイアログをモーダルにしたい場合は
               * 第1引数に BrowserWindow インスタンスを渡す(省略可)
               */
              .showOpenDialog(win, {
                /**
                 * 'openFile' - 単一のファイル
                 * 'openDirectory' - 単一のディレクトリ
                 * 'multiSelections' - 複数選択可にする
                 * 'showHiddenFiles' - ドットファイルも選択可にする
                 */
                properties: ['openFile', 'showHiddenFiles'],
                title: 'ファイルを選択する',
                filters: [
                  {
                    name: '画像ファイル',
                    extensions: ['png', 'jpg', 'jpeg'],
                  },
                ],
                /**
                 * result: Electron.OpenDialogReturnValue
                 *
                 * result.canceled: boolean
                 * result.filePaths: string[]
                 *
                 */
              })
              .then((result) => {
                // キャンセルボタンが押されたとき
                if (result.canceled) return;

                // レンダラープロセスへファイルのフルパスを送信
                win.webContents.send('menu-open', result.filePaths[0]);
              })
              .catch((err) => console.log(`Error: ${err}`)),
        },
      ],
    },
    { role: 'editMenu' },
    { role: 'viewMenu' },
    { role: 'windowMenu' },
    { role: 'help', submenu: [{ role: 'about' }] },
  ];

  if (process.platform === 'darwin') template.unshift({ role: 'appMenu' });

  return Menu.buildFromTemplate(template);
};

プリロードスクリプト

src/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('myAPI', {
  openByMenu: (listener: (_e: Event, filepath: string) => void) =>
    ipcRenderer.on('open-by-menu', listener),
});

型定義ファイル

src/global.d.ts
declare global {
  interface Window {
    myAPI: Sandbox;
  }
}

export interface Sandbox {
  openByMenu: (
    listener: (_e: Event, filepath: string) => void
  ) => Electron.IpcRenderer;
}

レンダラープロセス

src/app.ts
const { myAPI } = window;
const text = document.getElementById('text');

const listener = (filepath: string) => {
  if (text) text.textContent = filepath;
};

myAPI.openByMenu((_e, filepath) => listener(filepath));

レンダラープロセスのイベントからダイアログを開く場合

プリロードスクリプト

src/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('myAPI', {
  openByButton: () => ipcRenderer.invoke('open-by-button'),
});

型定義ファイル

src/global.d.ts
declare global {
  interface Window {
    myAPI: Sandbox;
  }
}

export interface Sandbox {
  openByButton: () => Promise<string | void | undefined>;
}

レンダラープロセス

src/app.ts
const { myAPI } = window;

const text = document.getElementById('text');
const button = document.getElementById('button');

button?.addEventListener('click', async () => {
  const filepath = await myAPI.openByButton();

  if (filepath) {
    if (text) text.textContent = filepath;
  } else {
    if (text) text.textContent = '';
  }
});

メインプロセス

src/main.ts
  ipcMain.handle('open-by-button', async () => {
    return dialog
      .showOpenDialog(mainWindow, {
        properties: ['openFile'],
        title: 'ファイルを選択する',
        filters: [
          {
            name: '画像ファイル',
            extensions: ['png', 'jpeg', 'jpg'],
          },
        ],
      })
      .then((result) => {
        if (result.canceled) return;
        return result.filePaths[0];
      })
      .catch((err) => console.log(`Error: ${err}`));
  });