Chapter 02無料公開

アプリケーションメニュー (Menu, MenuItem)

Kei Touge
Kei Touge
2021.09.18に更新

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

https://www.electronjs.org/docs/api/menu-item

最小構成のアプリケーションメニュー

メインプロセス (menu.ts)

src/menu.ts
import { Menu, MenuItemConstructorOptions } from 'electron';

const template: MenuItemConstructorOptions[] = [
  { role: 'fileMenu' },
  { role: 'editMenu' },
  { role: 'viewMenu' },
  { role: 'windowMenu' },
  { role: 'help', submenu: [{ role: 'about' }] },
];

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

export const menu = Menu.buildFromTemplate(template);

メインプロセス (main.ts)

src/main.ts
import { app, BrowserWindow, Menu } from 'electron';

import { menu } from './menu';

const createWindow = () => {
  const mainWindow = new BrowserWindow();

  Menu.setApplicationMenu(menu);

  mainWindow.loadFile('dist/index.html');
};

app.whenReady().then(createWindow);
app.once('window-all-closed', () => app.quit());

デフォルトのアプリケーションメニュー

Windows・macOS 共用メニュー

template.ts (クリックして展開)
template.ts
import { MenuItemConstructorOptions, shell } from 'electron';

const isDarwin = process.platform === 'darwin';
const electronVersion = process.versions['electron'];

const editSub: MenuItemConstructorOptions['submenu'] = [
  {
    label: 'Undo',
    role: 'undo',
    accelerator: 'CmdOrCtrl+Z',
  },
  {
    label: 'Redo',
    role: 'redo',
    accelerator: isDarwin ? 'Cmd+Shift+Z' : 'Ctrl+Y',
  },
  { type: 'separator' },
  {
    label: 'Cut',
    role: 'cut',
    accelerator: 'CmdOrCtrl+X',
  },
  {
    label: 'Copy',
    role: 'copy',
    accelerator: 'CmdOrCtrl+C',
  },
  {
    label: 'Paste',
    role: 'paste',
    accelerator: 'CmdOrCtrl+V',
  },
  {
    label: 'Delete',
    role: 'delete',
  },
  {
    label: 'Select All',
    role: 'selectAll',
    accelerator: 'CmdOrCtrl+A',
  },
];

if (isDarwin) {
  editSub.splice(6, 0, {
    label: 'Paste and Match Style',
    role: 'pasteAndMatchStyle',
    accelerator: 'Cmd+Option+Shift+V',
  });
  editSub.push(
    { type: 'separator' },
    {
      label: 'Speech',
      submenu: [
        {
          label: 'Start Speaking',
          role: 'startSpeaking',
        },
        {
          label: 'Stop Speaking',
          role: 'stopSpeaking',
        },
      ],
    }
  );
} else {
  editSub.splice(8, 0, { type: 'separator' });
}

const windowSub: MenuItemConstructorOptions['submenu'] = [
  {
    label: 'Minimize',
    role: 'minimize',
    accelerator: 'CmdOrCtrl+M',
  },
  {
    label: 'Zoom',
    role: 'zoom',
  },
  {
    label: 'Close',
    role: 'close',
    accelerator: 'CmdOrCtrl+W',
  },
];

if (isDarwin) {
  windowSub.push(
    { type: 'separator' },
    {
      label: 'Bring All to Front',
      role: 'front',
    },
    { type: 'separator' },
    { role: 'window' }
  );
}

export const template: MenuItemConstructorOptions[] = [
  {
    label: 'File',
    submenu: [
      {
        label: isDarwin ? 'Close Window' : 'Exit',
        role: isDarwin ? 'close' : 'quit',
        accelerator: isDarwin ? 'Cmd+W' : undefined,
      },
    ],
  },
  {
    label: 'Edit',
    submenu: editSub,
  },
  {
    label: 'View',
    submenu: [
      {
        label: 'Reload',
        role: 'reload',
        accelerator: 'CmdOrCtrl+R',
      },
      {
        label: 'Force Reload',
        role: 'forceReload',
        accelerator: 'CmdOrCtrl+Shift+R',
      },
      {
        label: 'Toggle Developer Tools',
        accelerator: isDarwin ? 'Cmd+Option+I' : 'Ctrl+Shift+I',
        role: 'toggleDevTools',
      },
      { type: 'separator' },
      {
        label: 'Actual Size',
        role: 'resetZoom',
        accelerator: 'CmdOrCtrl+0',
      },
      {
        label: 'Zoom In',
        role: 'zoomIn',
        accelerator: 'CmdOrCtrl+Plus',
      },
      {
        label: 'Zoom Out',
        role: 'zoomOut',
        accelerator: 'CmdOrCtrl+-',
      },
      { type: 'separator' },
      {
        label: 'Toggle Full Screen',
        role: 'togglefullscreen',
        accelerator: isDarwin ? 'Cmd+Ctrl+F' : 'F11',
      },
    ],
  },
  {
    label: 'Window',
    submenu: windowSub,
  },
  {
    label: 'Help',
    submenu: [
      {
        label: 'Learn More',
        click: async (): Promise<void> => {
          await shell.openExternal('https://electronjs.org');
        },
      },
      {
        label: 'Documentation',
        click: async (): Promise<void> => {
          await shell.openExternal(
            `https://github.com/electron/electron/tree/v${electronVersion}/docs#readme`
          );
        },
      },
      {
        label: 'Community Discussions',
        click: async (): Promise<void> => {
          await shell.openExternal('https://discord.com/invite/electron');
        },
      },
      {
        label: 'Search Issues',
        click: async (): Promise<void> => {
          await shell.openExternal(
            'https://github.com/electron/electron/issues'
          );
        },
      },
    ],
  },
];

if (process.platform === 'darwin') {
  template.unshift({
    label: 'Electron',
    submenu: [
      {
        label: 'About the app',
        role: 'about',
      },
      { type: 'separator' },
      { role: 'services' },
      { type: 'separator' },
      {
        label: 'Hide',
        role: 'hide',
        accelerator: 'Cmd+H',
      },
      {
        label: 'Hide Others',
        role: 'hideOthers',
        accelerator: 'Cmd+Option+H',
      },
      {
        label: 'Show All',
        role: 'unhide',
      },
      { type: 'separator' },
      {
        label: 'Quit',
        role: 'quit',
        accelerator: 'Cmd+Q',
      },
    ],
  });
}

メインプロセス

main.ts
import { Menu } from 'electron';
import { template } from './template';

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

スクリーンショット

fig01

メニューバーを隠す Windows

main.ts
const mainWindow = new BrowserWindow({
  autoHideMenuBar: true,
});

コンテクスト・メニューを表示する

アプリケーションメニューを隠し、コンテクストメニューを備える必要があるとき。

メインプロセス

main.js
const createWindow = () => {
  const mainWindow = new BrowserWindow({
    autoHideMenuBar: true,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  });

  const menu = Menu.buildFromTemplate([
    {
      label: 'File',
      submenu: [
        {
          label: 'Close',
          accelerator: 'CmdOrCtrl+W',
          role: 'close',
        },
      ],
    },
    {
      label: 'Help',
      submenu: [
        {
          label: 'Console Log',
          click: () => console.log('context-menu'),
        },
      ],
    },
  ]);

  ipcMain.on('show-context-menu', () => menu.popup());

  mainWindow.loadFile('index.html');
};

プリロードスクリプト

preload.js
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('myAPI', {
  showContextMenu: () => ipcRenderer.send('show-context-menu'),
});

レンダラープロセス

app.js
window.addEventListener('contextmenu', (e) => {
  e.preventDefault();
  window.myAPI.showContextMenu();
});

メニューバー表示のトグル Windows

main.ts
const mainWindow = new BrowserWindow();

const template: MenuItemConstructorOptions[] = [
  {
    label: 'Window',
    submenu: [
      label: 'Toggle Menubar',
      accelerator: 'Ctrl+Shift+T',
      click: () => {
        mainWindow.setMenuBarVisibility(!mainWindow.menuBarVisible);
      },
    ],
  },
];

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

チェックボックス

id プロパティを定義しないと、このアイテムへの位置属性による参照として使用できません。

main.ts
import { MenuItemConstructorOptions, nativeTheme } from 'electron';

const template: MenuItemConstructorOptions[] = [
  {
    label: 'Window'
    submenu: [
      {
        label: 'Toggle Darkmode',
        accelerator: 'Ctrl+Shift+D',
        type: 'checkbox',
        id: 'darkmode',
        checked: nativeTheme.shouldUseDarkColors,
        click: () => {
          nativeTheme.themeSource = nativeTheme.shouldUseDarkColors
            ? 'light'
            : 'dark';
        },
      },
    ],
  },
];