【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 を使っています。コメントデータの処理方法の詳細はこちらをご覧ください