Appearance
Kapitel 17: Weitere Lernrichtungen
In diesem Kapitel erkunden wir fortgeschrittene Themen für die weitere Entwicklung als Electron-Entwickler.
17.1 Fortgeschrittene Electron-API-Anwendungen
Tray-Menü fortgeschritten
Dynamisches Tray-Menü
javascript
// main.js
const { Tray, Menu, BrowserWindow } = require('electron');
const path = require('path');
let tray;
let isLoggedIn = false;
function createTray() {
tray = new Tray(path.join(__dirname, 'assets', 'tray-icon.png'));
// Dynamisches Menü basierend auf Status
updateTrayMenu();
tray.setToolTip('Meine App');
tray.on('click', toggleWindow);
}
function updateTrayMenu() {
const menuTemplate = [
{
label: isLoggedIn ? 'Abmelden' : 'Anmelden',
click: () => {
isLoggedIn = !isLoggedIn;
updateTrayMenu(); // Menü aktualisieren
const win = BrowserWindow.getFocusedWindow();
if (win) {
win.webContents.send('auth-changed', isLoggedIn);
}
}
},
{ type: 'separator' },
{
label: 'Benachrichtigungen',
submenu: [
{
label: 'Alle anzeigen',
click: () => {
const win = BrowserWindow.getFocusedWindow();
if (win) {
win.webContents.send('show-notifications');
}
}
},
{
label: 'Stummschalten',
type: 'checkbox',
checked: false,
click: (menuItem) => {
menuItem.checked = !menuItem.checked;
}
}
]
},
{ type: 'separator' },
{
label: 'Beenden',
click: () => {
app.quit();
}
}
];
const menu = Menu.buildFromTemplate(menuTemplate);
tray.setContextMenu(menu);
}
function toggleWindow() {
const win = BrowserWindow.getAllWindows()[0];
if (win.isVisible()) {
win.hide();
} else {
win.show();
win.focus();
}
}Tray-Icon animieren
javascript
// Tray-Icon animieren (Laden-Animation)
let animationInterval;
let currentFrame = 0;
const frames = [
'assets/tray-frame1.png',
'assets/tray-frame2.png',
'assets/tray-frame3.png'
];
function startTrayAnimation() {
if (animationInterval) return;
animationInterval = setInterval(() => {
currentFrame = (currentFrame + 1) % frames.length;
tray.setImage(frames[currentFrame]);
}, 300);
}
function stopTrayAnimation() {
if (animationInterval) {
clearInterval(animationInterval);
animationInterval = null;
tray.setImage('assets/tray-icon.png'); // Zurück zum Standard
}
}Systemdialoge anpassen
Benutzerdefinierte Dialoge
javascript
// main.js
const { dialog, BrowserWindow } = require('electron');
// Dialog mit benutzerdefiniertem Inhalt
function showCustomDialog(parentWindow) {
const dialogWindow = new BrowserWindow({
width: 400,
height: 300,
parent: parentWindow,
modal: true,
frame: false,
transparent: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
dialogWindow.loadFile('dialog.html');
// Ergebnis empfangen
ipcMain.once('dialog-response', (event, response) => {
console.log('Dialog-Antwort:', response);
dialogWindow.close();
});
}Erweiterte Dateidialoge
javascript
// main.js
ipcMain.handle('show-advanced-open-dialog', async (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
const result = await dialog.showOpenDialog(win, {
title: 'Dateien auswählen',
defaultPath: app.getPath('documents'),
buttonLabel: 'Auswählen',
filters: [
{ name: 'Bilder', extensions: ['jpg', 'png', 'gif'] },
{ name: 'Dokumente', extensions: ['pdf', 'doc', 'txt'] },
{ name: 'Alle Dateien', extensions: ['*'] }
],
properties: [
'openFile',
'openDirectory',
'multiSelections',
'showHiddenFiles'
],
message: 'Bitte wählen Sie Dateien aus' // macOS
});
if (!result.canceled) {
return {
success: true,
paths: result.filePaths
};
}
return { success: false, error: 'Abgebrochen' };
});17.2 Desktop-Anwendungs-Performance-Optimierung
Hauptprozess-Optimierung
1. Blockierende Operationen vermeiden
javascript
// FALSCH: Blockiert den Hauptprozess
ipcMain.handle('heavy-operation', (event, data) => {
const result = heavyCPUOperation(data); // Blockiert UI!
return result;
});
// RICHTIG: Asynchrone Verarbeitung
ipcMain.handle('heavy-operation', async (event, data) => {
// In separaten Thread auslagern
const { Worker } = require('worker_threads');
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js');
worker.postMessage(data);
worker.on('message', resolve);
worker.on('error', reject);
});
});2. Caching implementieren
javascript
// main.js
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 }); // 10 Minuten TTL
ipcMain.handle('get-data', (event, key) => {
// Zuerst im Cache suchen
const cached = cache.get(key);
if (cached) {
return { success: true, data: cached, fromCache: true };
}
// Daten abrufen
const data = fetchDataFromSource(key);
cache.set(key, data);
return { success: true, data, fromCache: false };
});Renderer-Prozess-Optimierung
1. DOM-Operationen minimieren
javascript
// FALSCH: Viele DOM-Operationen
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Element ${i}`;
document.body.appendChild(div); // Reflow jedes Mal!
}
// RICHTIG: DocumentFragment verwenden
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Element ${i}`;
fragment.appendChild(div);
}
document.body.appendChild(fragment); // Einmaliger Reflow2. Virtuelles Scrollen für große Listen
javascript
// Verwendung einer Bibliothek wie react-window oder vue-virtual-scroller
// Beispiel mit einfachem virtuellem Scrollen
class VirtualList {
constructor(container, itemHeight, items) {
this.container = container;
this.itemHeight = itemHeight;
this.items = items;
this.visibleItems = Math.ceil(container.clientHeight / itemHeight);
this.container.style.height = `${items.length * itemHeight}px`;
this.render = this.render.bind(this);
this.container.addEventListener('scroll', this.render);
this.render();
}
render() {
const scrollTop = this.container.scrollTop;
const startIndex = Math.floor(scrollTop / this.itemHeight);
const endIndex = Math.min(
startIndex + this.visibleItems + 1,
this.items.length
);
// Nur sichtbare Elemente rendern
const visibleData = this.items.slice(startIndex, endIndex);
// ... Rendering-Logik
}
}17.3 Enterprise-Level Electron-Anwendungsentwicklung
Große Projektarchitektur
enterprise-app/
├── src/
│ ├── main/ # Hauptprozess
│ │ ├── index.js # Eintrittspunkt
│ │ ├── windows/ # Fensterverwaltung
│ │ ├── ipc/ # IPC-Handler
│ │ ├── services/ # Geschäftslogik
│ │ └── utils/ # Hilfsfunktionen
│ ├── preload/ # Preload-Skripte
│ │ ├── main.js
│ │ └── secondary.js
│ ├── renderer/ # Renderer-Prozesse
│ │ ├── main-window/
│ │ ├── settings-window/
│ │ └── shared/
│ └── shared/ # Gemeinsamer Code
│ ├── constants.js
│ └── utils.js
├── resources/ # Ressourcen
│ ├── icons/
│ └── config/
├── tests/ # Tests
├── docs/ # Dokumentation
└── build/ # Build-KonfigurationModulare IPC-Architektur
javascript
// src/main/ipc/handlers.js
const { ipcMain } = require('electron');
class IPCHandlerManager {
constructor() {
this.handlers = new Map();
}
register(module) {
const handlers = module.getHandlers();
for (const [channel, handler] of Object.entries(handlers)) {
if (this.handlers.has(channel)) {
console.warn(`IPC-Kanal überschrieben: ${channel}`);
}
this.handlers.set(channel, handler);
}
}
setup() {
for (const [channel, handler] of this.handlers) {
if (handler.invoke) {
ipcMain.handle(channel, handler.invoke);
}
if (handler.on) {
ipcMain.on(channel, handler.on);
}
}
}
}
module.exports = new IPCHandlerManager();javascript
// src/main/ipc/modules/userModule.js
class UserModule {
getHandlers() {
return {
'user-get': {
invoke: async (event, userId) => {
const user = await this.userService.findById(userId);
return user;
}
},
'user-save': {
invoke: async (event, userData) => {
const saved = await this.userService.save(userData);
return saved;
}
}
};
}
}
module.exports = new UserModule();17.4 Electron mit nativen Anwendungen integrieren
Aufruf von C++/Python-Skripten
Python-Skript aufrufen
javascript
// main.js
const { spawn } = require('child_process');
const path = require('path');
function callPythonScript(scriptPath, args = []) {
return new Promise((resolve, reject) => {
const pythonProcess = spawn('python', [scriptPath, ...args]);
let result = '';
let error = '';
pythonProcess.stdout.on('data', (data) => {
result += data.toString();
});
pythonProcess.stderr.on('data', (data) => {
error += data.toString();
});
pythonProcess.on('close', (code) => {
if (code === 0) {
try {
const parsed = JSON.parse(result);
resolve(parsed);
} catch (e) {
resolve(result);
}
} else {
reject(new Error(error || `Python-Skript fehlgeschlagen mit Code ${code}`));
}
});
});
}
// IPC-Handler
ipcMain.handle('run-python-script', async (event, { script, args }) => {
try {
const scriptPath = path.join(__dirname, 'scripts', script);
const result = await callPythonScript(scriptPath, args);
return { success: true, result };
} catch (error) {
return { success: false, error: error.message };
}
});C++-Addon kompilieren und verwenden
bash
# binding.gyp für node-gyp
{
"targets": [
{
"target_name": "myaddon",
"sources": ["src/addon.cc"],
"include_dirs": ["<!(node -e \"require('nan')\")"]
}
]
}javascript
// addon.cc
#include <nan.h>
void Method(const Nan::FunctionCallbackInfo<v8::Value>& info) {
info.GetReturnValue().Set(Nan::New("Welt").ToLocalChecked());
}
void Init(v8::Local<v8::Object> exports) {
exports->Set(Nan::New("hallo").ToLocalChecked(),
Nan::New<v8::Function>(Method));
}
NODE_MODULE(addon, Init)javascript
// main.js - Addon verwenden
try {
const addon = require('./build/Release/myaddon');
console.log(addon.hallo()); // "Welt"
} catch (error) {
console.error('Addon konnte nicht geladen werden:', error);
}17.5 Plattformübergreifende Anpassung
Unterschiede zwischen Betriebssystemen behandeln
Plattformerkennung und -anpassung
javascript
// src/shared/platform.js
const { platform } = require('os');
const currentPlatform = platform();
const isWindows = currentPlatform === 'win32';
const isMacOS = currentPlatform === 'darwin';
const isLinux = currentPlatform === 'linux';
module.exports = {
currentPlatform,
isWindows,
isMacOS,
isLinux,
// Plattform-spezifische Einstellungen
getPlatformSettings() {
if (isWindows) {
return {
icon: 'assets/icon.ico',
trayIcon: 'assets/tray.ico',
shortcuts: {
quit: 'Ctrl+Q',
reload: 'Ctrl+R'
}
};
} else if (isMacOS) {
return {
icon: 'assets/icon.icns',
trayIcon: 'assets/tray.png',
shortcuts: {
quit: 'Cmd+Q',
reload: 'Cmd+R'
}
};
} else {
return {
icon: 'assets/icon.png',
trayIcon: 'assets/tray.png',
shortcuts: {
quit: 'Ctrl+Q',
reload: 'Ctrl+R'
}
};
}
}
};Plattform-spezifisches Menü
javascript
// main.js
const { Menu } = require('electron');
const { isMacOS } = require('./shared/platform');
function createMenu() {
const template = [
{
label: 'Datei',
submenu: [
{
label: 'Beenden',
accelerator: isMacOS ? 'Cmd+Q' : 'Ctrl+Q',
click: () => app.quit()
}
]
},
{
label: 'Bearbeiten',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' }
]
}
];
// macOS-spezifisches Menü
if (isMacOS) {
template.unshift({
label: app.getName(),
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
});
}
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}Zusammenfassung
In diesem Kapitel haben Sie erkundet:
- Fortgeschrittene Electron-API-Anwendungen (Tray, Dialoge)
- Desktop-Anwendungs-Performance-Optimierung (Haupt- und Renderer-Prozess)
- Enterprise-Level Electron-Anwendungsentwicklung (Architektur, modulare IPC)
- Integration mit nativen Anwendungen (C++, Python)
- Plattformübergreifende Anpassung (Unterschiede behandeln)
Im nächsten Kapitel werden wir Lernressourcen empfehlen.
