【Electron + Next.js】contextBridge使って右クリックメニュー (ContextMenu)

突然始めたから初心者レベルやで。

適当にプロジェクト作って、こんな画面を作成。
(プロジェクト作成方法とかは何がいいのか知らないので割愛)

いろいろ検索するとだいたいレンダラープロセス(HTML)のほうでNodeの機能(主にremote)を呼び出してるんだけど、
現在はデフォルトで非推奨らしいのでcontextBridgeを使って、なんでもメインプロセスで動かす必要があるらしい。
まぁそりゃレンダラーのほうでなんでも実行されたらセキュリティ的にあぶないし。

ボタンでダイアログ

とりあえずボタン押したらダイアログを出す。
contextBridgeに関しては以下のページがすごく参考になりました。
https://blog.katsubemakito.net/nodejs/electron/ipc-for-contextbridge

まずはpreload.jsを作成して、レンダラー → メインを作成。名前は適当に「dialogMsg」にしてます。

preload.js

const { ipcRenderer, contextBridge } = require("electron");

contextBridge.exposeInMainWorld("electron", {
  // レンダラー → メイン
  dialogMsg: async (data) => await ipcRenderer.invoke("dialogMsg", data),
});

メインプロセス側のindex.jsはipcMainでダイアログを出す処理を作成。

メインプロセス - index.js

// Native
const { join } = require("path");
const { format } = require("url");

// Packages
const { BrowserWindow, app, ipcMain, dialog} = require("electron");
const isDev = require("electron-is-dev");
const prepareNext = require("electron-next");

// Prepare the renderer once the app is ready
app.on("ready", async () => {
  await prepareNext("./renderer");

  // ウィンドウ作成
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: join(__dirname, "preload.js"),
    },
  });

  // URL作成
  const url = isDev
    ? "http://localhost:8000"
    : format({
        pathname: join(__dirname, "../renderer/out/index.html"),
        protocol: "file:",
        slashes: true,
      });

  if (isDev) {
    // 開発者ツール
    mainWindow.webContents.openDevTools();
  }

  mainWindow.loadURL(url);
});

// 終了処理
app.on("window-all-closed", app.quit);

// 以下レンダーとの通信
ipcMain.handle("dialogMsg", (event, data) => {
  const w = BrowserWindow.getFocusedWindow();
  dialog.showMessageBox(w, {
    type: "info",
    title: "タイトル",
    message: data,
  });
  return;
});

レンダラープロセス側のindex.jsではonClickイベントでdialogActionを呼び出して、
メインプロセス側の処理を呼び出し。

レンダラープロセス - index.js

import { useState, useEffect } from "react";

export default function Home() {
  // 初回のイベント
  useEffect(() => {
    console.log("useEffect");
  }, []);

  // ボタンイベント
  const dialogAction = async (event) => {
    console.log("dialogAction");
    await window.electron.dialogMsg("テストだよ");
  };

  return (
    <div className="main">
      <h1 id="test">
        右クリックサンプル
      </h1>

      <button id="test_button" type="button" onClick={dialogAction}>
        TEST
      </button>

      <style jsx>{`
        h1 {
          font-size: 20px;
        }
      `}</style>
      <style jsx global>{`
        body {
          background-color: white;
        }
      `}</style>
    </div>
  );
}

結果

h1タグで右クリック

まぁダイアログとほぼ同じですね。まぁあれなんでレンダラーのほうのイベントもキックします。

まずはpreload.jsを修正。レンダラー → メインのpopupMenuを追加して、メイン → レンダラーを追加しました。

preload.js

const { ipcRenderer, contextBridge } = require("electron");
contextBridge.exposeInMainWorld("electron", {
  // レンダラー → メイン
  dialogMsg: async (data) => await ipcRenderer.invoke("dialogMsg", data),
  popupMenu: async (data) => await ipcRenderer.invoke("popupMenu", data),
  // メイン → レンダラー
  on: (channel, callback) =>
    ipcRenderer.on(channel, (event, argv) => callback(event, argv)),
});

次にメインプロセス側でメニュー作成とそれを表示するipcMainを追加。

メインプロセス - index.js

// メニューを作成
const menu = Menu.buildFromTemplate([
  {
    label: "Test Menu",
    click: () => {
      // ウィンドウを取得して、レンダラーに通知
      const w = BrowserWindow.getFocusedWindow();
      w.webContents.send("popuo-return");
    },
  },
  { type: "separator" },
  { role: "quit" },
]);
// コンテキストメニューを表示
ipcMain.handle("popupMenu", (event) => {
  const w = BrowserWindow.getFocusedWindow();
  menu.popup(w);
  return;
});

レンダラー側ではonContextMenuを追加してcontextMenuイベントを呼び出して、ContextMenuを表示して、
メニューを選択したら、popuo-returnを起動して、レンダラー側でAlertを表示。

レンダラープロセス - index.js

import { useState, useEffect } from "react";

export default function Home() {
  // 初回のイベント
  useEffect(() => {
    console.log("useEffect");

    window.electron.on("popuo-return", (event) => {
      alert("右クリックからのアラート");
    });
  }, []);

  // 右クリックメニュー
  const contextMenu = async (event) => {
    console.log("contextMenu", event.currentTarget);
    event.preventDefault();
    await window.electron.popupMenu();
  };

  // ボタンイベント
  const dialogAction = async (event) => {
    console.log("dialogAction");
    await window.electron.dialogMsg("テストだよ");
  };

  return (
    <div className="main">
      <h1 id="test" onContextMenu={contextMenu}>
        右クリックサンプル
      </h1>

      <button id="test_button" type="button" onClick={dialogAction}>
        TEST
      </button>

      <style jsx>{`
        h1 {
          font-size: 20px;
        }
      `}</style>
      <style jsx global>{`
        body {
          background-color: white;
        }
      `}</style>
    </div>
  );
}

結果

以上です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください