Скопируйте данные буфера обмена между приложениями React в нескольких окнах.
Несколько окон в Electron не используют одну и ту же модель данных и должны использовать межпроцессное взаимодействие для обмена данными.
Несмотря на то, что все окна происходят из одного и того же приложения, каждое окно является экземпляром BrowserWindow
, содержащим уникальное веб-представление Chromium. Отправка сообщений между окнами аналогична вкладкам или окнам в веб-браузере.
В веб-браузере вы можете общаться с помощью BroadcastChannels.
После определения канала опубликованные сообщения отправляют события для слушателей:
const channel = new BroadcastChannel("mychannel"); // Send a message: channel.postMessage("Hello, world"); // Receive a message: channel.onmessage = (event) => { console.log(event); });
В Electron аналогичная стратегия используется путем отправки сообщений из процесса рендеринга в основной процесс. Затем основной процесс отправляет сообщения всем окнам рендеринга.
Пример буфера обмена
Например, рассмотрим расширенные данные буфера обмена, которые мы хотим разделить между окнами. В каждом окне могут быть запущены уникальные приложения; или несколько экземпляров одного и того же приложения.
Я собираюсь продолжить свой пост о приложении Multiple Window Electron:
В этом примере новые окна можно открывать с помощью Файл ≫ Новое окно, что фактически создает новый экземпляр точно такого же приложения React.
Отправка данных
Из веб-контента любого окна сериализуйте полезную нагрузку данных в JSON и отправьте ее через ipcRenderer
в нужный канал. В этом примере клиент собирается отправить данные буфера обмена, используя clipboard-send
.
const { ipcRenderer } = require('electron'); const copy = (data) => { const json = JSON.stringify(data); ipcRenderer.send("clipboard-send", json); }
В основном потоке (main.dev.js
) создайте прослушиватель на ipcMain
для обработки событий канала clipboard-send
, которые были отправлены из процесса рендеринга любого окна. Затем передайте эти данные обратно во все окна, отправив события канала clipboard-receive
:
const { ipcMain } = require('electron'); ipcMain.on('clipboard-send', (event, json) => { windows.forEach(window => { window.send('clipboard-receive', json); }); });
Получение данных
Теперь, когда основной поток отправляет сообщение всем окнам, каждое окно должно прослушивать события на канале clipboard-receive
:
const { ipcRenderer } = require('electron'); let clipboard = {}; ipcRenderer.on('clipboard-receive', (event, json) => { if (!json) return; const data = JSON.parse(json); clipboard = data; });
Поток рендеринга каждого окна теперь может десериализовать полезные данные и кэшировать их локально в буфере обмена.
Полный жизненный цикл:
- Поток рендеринга одного окна отправляет сообщение основному потоку
- Основной поток повторяет сообщение во всех окнах
- Все окна получают событие с полезными данными
Новые окна
Одна ошибка здесь — окна, созданные после того, как данные были переданы. Продолжая пример с буфером обмена, скажем:
- Одно окно скопировало данные в буфер обмена
- Новое окно было открыто
- Пользователь пытается вставить в новое окно — это окно не получило никаких событий, так как оно было создано после того, как сообщение было передано.
В этом случае вы можете создать локальный кеш в основном рендерере; затем автоматически отправлять данные буфера обмена при создании новых окон.
const { ipcMain } = require('electron'); let clipboardCache; export const createWindow = async () => { let newWindow = new BrowserWindow(); // ... newWindow.webContents.on('did-finish-load', () => { // Send clipboard data newWindow.send('clipboard-receive', clipboardCache); }); }
Собираем все вместе
В коде JavaScript, отображаемом в ваших окнах, например в приложении React, отправляйте и получайте события в основной поток. Это напоминает общение между клиентом и сервером:
const { ipcRenderer } = require('electron'); // Local clipboard cache let clipboard = {}; // Send clipboard data to the main thread const copy = (data) => { const json = JSON.stringify(data); ipcRenderer.send("clipboard-send", json); }; // Listen for incoming clipboard data ipcRenderer.on('clipboard-receive', (event, json) => { if (!json) return; const data = JSON.parse(json); clipboard = data; }); // Do something with the clipboard data const paste = () => { if (!clipboard) return; // ... };
В основном потоке (main.dev.js
), добавляя к предыдущему множественному примеру:
const { ipcMain } = require('electron'); // Clipboard cache let clipboard; const windows = new Set(); export const createWindow = async () => { if ( process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true' ) { await installExtensions(); } let x, y; const currentWindow = BrowserWindow.getFocusedWindow(); if (currentWindow) { const [currentWindowX, currentWindowY] = currentWindow.getPosition(); x = currentWindowX + 24; y = currentWindowY + 24; } let newWindow = new BrowserWindow({ show: false, width: 1200, height: 812, x, y, webPreferences: { nodeIntegration: true } }); newWindow.loadURL(`file://${__dirname}/app.html`); newWindow.webContents.on('did-finish-load', () => { if (!newWindow) { throw new Error('"newWindow" is not defined'); } if (process.env.START_MINIMIZED) { newWindow.minimize(); } else { newWindow.show(); newWindow.focus(); } // Send clipboard data to new window newWindow.send('clipboard-receive', clipboard); }); newWindow.on('closed', () => { windows.delete(newWindow); newWindow = null; }); newWindow.on('focus', () => { const menuBuilder = new MenuBuilder(newWindow); menuBuilder.buildMenu(); }); windows.add(newWindow); return newWindow; }; app.on('activate', () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (windows.size === 0) createWindow(); }); // Echo clipboard data back to all windows: ipcMain.on('clipboard-send', (event, json) => { clipboard = json; windows.forEach(window => { window.send('clipboard-receive', json); }); });