00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Finestre Modali e Dialog

Electron offre sia finestre modali personalizzate che dialog nativi del sistema operativo per interagire con l’utente in modo familiare e coerente con la piattaforma.

Finestre Modali

Una finestra modale blocca l’interazione con la finestra genitore finché non viene chiusa:

function createModalWindow(parentWindow) {
  const modal = new BrowserWindow({
    width: 500,
    height: 350,
    parent: parentWindow,    // Obbligatorio per le modali
    modal: true,             // Blocca la finestra genitore
    show: false,
    resizable: false,
    minimizable: false,
    maximizable: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  });

  modal.loadFile('modal.html');

  modal.once('ready-to-show', () => {
    modal.show();
  });

  return modal;
}

Dialog Nativi

Il modulo dialog fornisce accesso ai dialog nativi del sistema operativo.

Dialog Apri File

const { dialog } = require('electron');

async function openFile(parentWindow) {
  const result = await dialog.showOpenDialog(parentWindow, {
    title: 'Seleziona un file',
    defaultPath: app.getPath('documents'),
    buttonLabel: 'Apri',
    filters: [
      { name: 'Documenti', extensions: ['txt', 'md', 'doc'] },
      { name: 'Immagini', extensions: ['png', 'jpg', 'gif'] },
      { name: 'Tutti i file', extensions: ['*'] },
    ],
    properties: [
      'openFile',          // Seleziona file
      // 'openDirectory',  // Seleziona cartelle
      // 'multiSelections',// Selezione multipla
      // 'showHiddenFiles',// Mostra file nascosti
    ],
  });

  if (result.canceled) return null;
  return result.filePaths[0];
}

Dialog Salva File

async function saveFile(parentWindow, content) {
  const result = await dialog.showSaveDialog(parentWindow, {
    title: 'Salva file',
    defaultPath: path.join(app.getPath('documents'), 'documento.txt'),
    buttonLabel: 'Salva',
    filters: [
      { name: 'File di testo', extensions: ['txt'] },
      { name: 'Markdown', extensions: ['md'] },
    ],
  });

  if (result.canceled) return false;

  const fs = require('node:fs');
  fs.writeFileSync(result.filePath, content, 'utf-8');
  return true;
}

Dialog Messaggi

// Messaggio informativo
async function showInfo(parentWindow) {
  await dialog.showMessageBox(parentWindow, {
    type: 'info',           // 'none', 'info', 'error', 'question', 'warning'
    title: 'Informazione',
    message: 'Operazione completata',
    detail: 'Il file è stato salvato con successo.',
    buttons: ['OK'],
  });
}

// Conferma con scelta
async function showConfirm(parentWindow) {
  const result = await dialog.showMessageBox(parentWindow, {
    type: 'question',
    title: 'Conferma',
    message: 'Vuoi salvare le modifiche?',
    detail: 'Le modifiche non salvate andranno perse.',
    buttons: ['Salva', 'Non salvare', 'Annulla'],
    defaultId: 0,           // Bottone selezionato di default
    cancelId: 2,            // Bottone che funge da "annulla"
  });

  // result.response è l'indice del bottone premuto
  switch (result.response) {
    case 0: return 'save';
    case 1: return 'discard';
    case 2: return 'cancel';
  }
}

// Errore
async function showError(parentWindow, error) {
  await dialog.showMessageBox(parentWindow, {
    type: 'error',
    title: 'Errore',
    message: 'Si è verificato un errore',
    detail: error.message,
    buttons: ['OK'],
  });
}

Dialog Errore (Sincrono)

Per errori critici che devono bloccare l’esecuzione:

// Questo dialog è sincrono e può essere usato anche prima che app sia ready
dialog.showErrorBox('Errore Critico', 'L\'applicazione non può essere avviata.');

Integrazione con IPC

Esponi i dialog al renderer tramite il pattern IPC standard:

// main.js
ipcMain.handle('dialog:openFile', async (event) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  return openFile(win);
});

ipcMain.handle('dialog:saveFile', async (event, content) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  return saveFile(win, content);
});

ipcMain.handle('dialog:confirm', async (event, options) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  const result = await dialog.showMessageBox(win, {
    type: options.type || 'question',
    title: options.title,
    message: options.message,
    buttons: options.buttons || ['OK', 'Annulla'],
    defaultId: 0,
    cancelId: 1,
  });
  return result.response;
});
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
  openFile: () => ipcRenderer.invoke('dialog:openFile'),
  saveFile: (content) => ipcRenderer.invoke('dialog:saveFile', content),
  confirm: (options) => ipcRenderer.invoke('dialog:confirm', options),
});
// renderer.js
document.getElementById('delete-btn').addEventListener('click', async () => {
  const response = await window.electronAPI.confirm({
    type: 'warning',
    title: 'Conferma eliminazione',
    message: 'Sei sicuro di voler eliminare questo elemento?',
    buttons: ['Elimina', 'Annulla'],
  });

  if (response === 0) {
    // L'utente ha confermato l'eliminazione
    deleteItem();
  }
});

Dialog Seleziona Cartella

async function selectDirectory(parentWindow) {
  const result = await dialog.showOpenDialog(parentWindow, {
    title: 'Seleziona cartella',
    properties: ['openDirectory', 'createDirectory'],
  });

  if (result.canceled) return null;
  return result.filePaths[0];
}

Conferma Prima della Chiusura

Un pattern comune è chiedere conferma quando l’utente tenta di chiudere l’app con modifiche non salvate:

let hasUnsavedChanges = false;

ipcMain.on('content:changed', () => {
  hasUnsavedChanges = true;
});

ipcMain.on('content:saved', () => {
  hasUnsavedChanges = false;
});

mainWindow.on('close', async (event) => {
  if (hasUnsavedChanges) {
    event.preventDefault();

    const result = await dialog.showMessageBox(mainWindow, {
      type: 'warning',
      title: 'Modifiche non salvate',
      message: 'Hai modifiche non salvate. Cosa vuoi fare?',
      buttons: ['Salva e chiudi', 'Chiudi senza salvare', 'Annulla'],
      defaultId: 0,
      cancelId: 2,
    });

    if (result.response === 0) {
      // Salva e chiudi
      mainWindow.webContents.send('save-and-close');
    } else if (result.response === 1) {
      // Chiudi senza salvare
      hasUnsavedChanges = false;
      mainWindow.close();
    }
    // Se Annulla, non fare nulla (la finestra resta aperta)
  }
});