Cross-plattform Apps mit Electron und React Teil 3

electron-react-header

Im zweiten Teil haben wir unserer Electron-App einen zum jeweiligen Betriebssystem passenden Look verpasst. Dabei haben wir je nach Plattform ein passendes Stylesheet verwendet, damit sich die Anwendung gut einpasst.

In diesem Teil werden wir nun das Menü der gtk3-demo-application nachbauen. Dazu werden wir als Erstes das App-Menü der Anwendung bauen. Dieses definieren wir beim Start der App. Als zweiten Schritt werden wir ein Kontext-Menü erstellen, dass zur Laufzeit der Anwendung komponiert wird. Damit unterscheidet es sich in der Art und Weise der Definition vom App-Menü.

Abschließend werden wir noch ein paar Optimierungen für Mac OS vornehmen. Das App-Menü auf einem Mac unterscheidet sich grundlegend von den Menüs unter Windows und Linux.

App-Menü

Das App-Menü erstellen wir in der Datei src/index.js. Dort wird es während des Starts der App geladen. Als Template für das Menü verwenden wir einen Javascript-Array, der Javascript-Objekte enthält. Die Javascript Objekte entsprechen einem Menüpunkt.

Das Menü-Template hat folgende Struktur:

[
  {
    label: 'NAME',
    submenu: [
      { role: 'NAME' },
      {
        label: 'NAME',
        click () { ... }
      }
    ]
  }
]

Jeder Menüpunkt kann wiederum eigene Untermenüs enthalten. Der sich so ergebende Baum stellt das App-Menü da.

Mit der Methode buildFromTemplate erstellen wir dann das Menü-Objekt.

Zusätzlich ergänzen wir src/index.js noch um eine Funktion showMessage, die uns als Dummy-Callback dient.

import { app, BrowserWindow, Menu, dialog } from 'electron';
...
  mainWindow = null;
  });

   const showMessage = message => dialog.showMessageBox({
    type: 'info',
    message: `You activated action: "${message}"`,
    buttons: ['Close'],
  });

   const menu = Menu.buildFromTemplate([
    {
      label: 'Preferences',
      submenu: [
        {
          label: 'Prefer Dark Theme',
          type: 'checkbox',
        },
        {
          label: 'Hide Titlebar when maximized',
          type: 'checkbox',
        },
        {
          label: 'Color',
          submenu: [
            {
              label: 'Red',
              type: 'radio',
              accelerator: 'CmdOrCtrl+R',
              click: () => showMessage('Red'),
            },
            {
              label: 'Green',
              type: 'radio',
              accelerator: 'CmdOrCtrl+G',
              click: () => showMessage('Green'),
            },
            {
              label: 'Blue',
              type: 'radio',
              accelerator: 'CmdOrCtrl+B',
              click: () => showMessage('Blue'),
            },
          ],
        },
        {
          label: 'Shape',
          submenu: [
            {
              label: 'Square',
              type: 'radio',
              accelerator: 'CmdOrCtrl+S',
              click: () => showMessage('Square'),
            },
            {
              label: 'Rectangle',
              type: 'radio',
              accelerator: 'CmdOrCtrl+R',
              click: () => showMessage('Rectangle'),
            },
            {
              label: 'Oval',
              type: 'radio',
              accelerator: 'CmdOrCtrl+O',
              click: () => showMessage('Oval'),
            },
          ],
        },
        {
          label: 'Bold',
          type: 'checkbox',
          accelerator: 'CmdOrCtrl+Shift+B',
          click: () => showMessage('Bold'),
        },
      ],
    },
    {
      label: 'Help',
      submenu: [
        {
          label: 'About',
          accelerator: 'CmdOrCtrl+A',
          click: () => dialog.showMessageBox({
            type: 'info',
            title: 'about',
            message: `GTK+ Code Demos
3.22.30
Running against GTK+ 3.22.30
Program to demonstrate GTK+ functions.
(C) 1997-2013 The GTK+ Team
This program comes with absolutely no warranty.
See the GNU Lesser General Public License, 
version 2.1 or later for details.`,
            buttons: ['Close'],
          }),
        },
      ],
    },
  ]);
  Menu.setApplicationMenu(menu);
...

Anschließend sollte das Menü zu sehen sein. Das Development-Menü wird nicht mehr angezeigt. Für ein größeres Projekt könnte es sinnvoll sein, ein Development-Menü zu erhalten, dass im Entwicklungsmodus angezeigt wird und bei Auslieferung ausgeschaltet wird.

electron-react-example-global-menu

Kontext-Menu

Die Demo-App besitzt auch ein Kontextmenü zur Textbearbeitung. Um dieses nachzubauen, gehen wir etwas anders vor. Wir erstellen es nicht mehr aus einem Template, sondern komponieren es zur Laufzeit.

Dementsprechend ergänzen wir unseren Code auch nicht mehr in der src/index.js, sondern in src/app.jsx.

Als Erstes definieren wir das Menü im Konstruktor unsere App-Komponente. Weil die Menu-Klasse nur im Hauptprozess zur Verfügung steht, müssen wir auf sie über das Remote-Objekt zugreifen.

Das Menü selbst ist sehr viel einfacher als das App-Menü gehalten. Wir fügen nur ein paar Standardfunktionen und einen Trenner in das Kontext-Menü ein. Wir machen dabei ausgiebig Gebrauch von der role-Option. Diese erlaubt uns, von Electron vordefinierte Standardmenüs zu verwenden.

    const menu = new remote.Menu();
    menu.append(new remote.MenuItem({ role: 'cut' }));
    menu.append(new remote.MenuItem({ role: 'copy' }));
    menu.append(new remote.MenuItem({ role: 'paste' }));
    menu.append(new remote.MenuItem({ role: 'delete' }));
    menu.append(new remote.MenuItem({ type: 'separator' }));
    menu.append(new remote.MenuItem({ role: 'selectall' }));
    this.menu = menu;
...
    this.onContextMenu = this.onContextMenu.bind(this);

Damit wir auf das Kontext-Menü zugreifen können, definieren wir einen onContextMenu-Handler für die Textarea. Dieser macht nichts anderes, als einfach das Kontextmenü zu öffnen und mit dem Hauptfenster zu verbinden.

  onContextMenu(event) {
    event.preventDefault();
    this.menu.popup({ window: remote.getCurrentWindow() });
  }
...
    <textarea id="TextField" onKeyDown={this.onInput} onContextMenu={this.onContextMenu} />

Jetzt kann man über der Textarea mit der rechten Maustaste ein Kontext-Menü aufrufen.

electron-context-example-context-menu

Mac-Menü

Das App-Menü unter Mac OS unterscheidet sich deutlich von den App-Menüs unter Windows und Linux.

In Mac OS gibt es 3 Standard Menüs, die jeder Anwendung bereitstellen sollte:

  • window
  • help
  • services

Zu diesem Zweck stellt Electron unter Mac OS einige Standard-Menüs über Rollen bereit. Diese helfen beim Aufbau des Menüs.

Als Erstes speichern wir in src/index.js das Template für das App-Menü in einer eigenen Variablen.

Dann passen wir die Rolle unsere Hilfe-Menüs an.

Als Nächstes fügen wir die beiden anderen Unter-Menüs in unserem App-Menü ein, falls wir uns auf einem Mac befinden. Dazu verwenden wir Standard-JavaScript-Methoden, um unser Template-Array zu erweitern.

electron-react-example-macos-menu

Wenn wir nun unsere Beispielanwendung auf einem Mac starten, wird das Menü im globalen Menü von Mac OS angezeigt.

Du findest den Source-Code für dieses Tutorial auf Github: https://github.com/rockiger/electron-react-example

Im letzten Teil werden wir uns um die Paketierung mit electron-forge kümmern.

Created with Dictandu