Help us understand the problem. What is going on with this article?

Electron で画像ビューアを作ってみた話

🔰 はじめに

🍻 得られた知見

[メインプロセス] dialog.showOpenDialog でファイルやフォルダが開かない

electron v6.x から dialog.showOpenDialogdialog.showOpenDialogSync分割されたようです。
非同期バージョンを利用する場合は、当然に帰ってくるプロミスを処理しなければなりません。

main.ts
  // レンダラープロセスから「ファイルを開く」ダイアログの要求を受けたとき
  ipcMain.on('open-dialog', (): void => {
    dialog
      .showOpenDialog(win, {
        properties: ['openFile'],
        defaultPath: app.getPath('home'),
      })
      .then((result) => {
        // キャンセル :boolean
        if (result.canceled) console.log(result.canceled);

        // ファイルが選択されたとき
        if (result.filePaths) {
          // ファイルパス(フルパス)をレンダラープロセスへ送信
          win.webContents.send('selected-file', result.filePaths[0]);
        }
      });
  });

[メインプロセス] 「このアプリケーションで開く」でアプリを起動させたい

右クリックからの "Open with..." でアプリを起動させる方法がわかりませんでした(とくに macOS)。

Windows

Windows の場合は普通に process.argv を渡せば良いです。

main.ts
app.on('ready', (): void => {
  win = new BrowserWindow({ webPreferences: { nodeIntegration: true } });

  win.webContents.on('did-finish-load', (): void => {
    if (process.platform === 'win32' && process.argv.length >= 2) {
      win.webContents.send(
        'selected-file',
        process.argv[process.argv.length - 1]
      );
    }
  });
});

macOS

ready イベントに先立つ will-finish-launching イベント内で open-file イベントを拾う必要があります。
open-fileopen-url 以外のイベントでは、通常の ready イベント内で処理すべきであるようです。
ファイルパスの受け渡しのため、いったんグローバル変数へ預ける必要があります。
また、かならず event.preventDefault() しておかなければいけません。

main.ts
let win: BrowserWindow | null = null;
let filepath: string | null = null;

// アプリ起動時
app.on('will-finish-launching', (): void => {
  app.on('open-file', (e, path): void => {
    e.preventDefault();

    // BrowserWindow インスタンスの作成前
    // まだ webContents を呼べないため、いったんグローバル変数に
    filepath = path;
  });
});

app.on('ready', (): void => {
  win = new BrowserWindow({ webPreferences: { nodeIntegration: true } });

  // アプリ起動時にファイルパスを受け取る
  win.webContents.on('did-finish-load', (): void => {
    if (process.platform === 'darwin' && filepath) {
      win.webContents.send('selected-file', filepath);
      filepath = null;
    }
  });

  // アプリ起動「後」のイベント拾い
  app.on('open-file', (e, path): void => {
    e.preventDefault();
    win.webContents.send('selected-file', path);
  });
});


[メインプロセス・レンダラープロセス] shell.openExternal(url) が動かない

dialog.showOpenDialog の場合と同様に 同期/非同期 バージョンができたようです。
なお、同期バージョンは duplicated と表示されています。

メインプロセス

menu.ts
const url = 'https://qiita.com/';

const template: MenuItemConstructorOptions[] = [
  {
    label: 'Help',
    submenu: [
      {
        label: 'Website',
        click: async (): Promise<void> => await shell.openExternal(url),
      },
    ],
  },
];

レンダラープロセス

App.tsx
const handleOnClick = async (): Promise<void> => {
  await shell.openExternal(url);
};

return (
  <div>
    <p onClick={handleOnClick}>Hello!</p>
  </div>
);

[レンダラープロセス] ドラッグ&ドロップやウィンドウ・リサイズへの対応

まさか File API
それは嫌だなと思っていたら、 react-event-listener という便利なライブラリがありました。

App.tsx
import EventListener from 'react-event-listener';

class App extends React.Component {
  public render(): JSX.Element {
    return (
      <div className='container'>
        <EventListener
          target="window"
          onResize={this.handleOnResize}
          onDragOver={(e): void => this.handleOnPreventDefault(e)}
          onDragEnter={(e): void => this.handleOnPreventDefault(e)}
          onDragLeave={(e): void => this.handleOnPreventDefault(e)}
          onDrop={(e): void => this.handleOnDrop(e)}
        />
      </div>
    );
  }
}

[Windows] second-instance の立ち上がりがめちゃくちゃ遅い

私の実力では解決ができませんでした・・・
仕方がないので SingleInstanceLock を強制。
(Windowsのみ。macOSでは second-instance イベントですでに起動中のインスタンスがアクティブ化される(たぶん)ため不要)

main.ts
if (process.platform === 'win32') {
  const lock = app.requestSingleInstanceLock();

  if (!lock) {
    app.quit();
  } else {
    app.on('second-instance', (e, argv): void => {
      // 最小化されていたらレストアしてフォーカス
      if (win.isMinimized()) win.restore();
      win.focus();

      // second-instance ではファイルパスは引数の3番目以降となる
      if (argv.length >= 4) {
        win.webContents.send('selected-file', argv[argv.length - 1]);
      }
    });
  }
}

[Chrome] react-devtools を読み込みたい

いろいろなライブラリが用意されてますが、試してみていちばん安定していたのが electron-load-devtool でした。

main.ts
import loadDevtool from 'electron-load-devtool';

app.on('ready', (): void => {
  win = new BrowserWindow({ webPreferences: { nodeIntegration: true } });
  if (process.env.NODE_ENV === 'development') {
    loadDevtool(loadDevtool.REACT_DEVELOPER_TOOLS);
  }
});

[メインプロセス] 起動時やリサイズするときにウィンドウが真っ白

公式にあるとおり、ready-to-showbackgroundColor を使います。

main.ts
app.on('ready', (): void => {
  win = new BrowserWindow({
    webPreferences: { nodeIntegration: true },
    show: false,
    backgroundColor: '#1e1e1e',
  });

  win.once('ready-to-show', (): void => {
    win.show();
  });
});

参考にさせていただきました

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした