非常感谢您的支持。根据您的建议和一些研究,我开发了一个可行的解决方案,并希望与您分享。
这两个链接对我帮助很大:
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/10764
尤其是 MarshallOfSound 的这篇文章——描述得很好:
- 主进程挂钩事件
- 使用设备列表向渲染器进程发送消息
- 在渲染器进程中显示 UI
- 将选定的设备发送到主进程
- 来电回拨
要获取有关主进程和渲染器进程、事件及其 API 的更多信息,请阅读以下内容:
https://www.electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes
https://www.electronjs.org/docs/api/ipc-main
https://www.electronjs.org/docs/api/web-contents#contentssendchannel-args
https://www.electronjs.org/docs/api/ipc-renderer
https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device(已由 Gerrit 发布)
https://www.electronjs.org/docs/api/structures/bluetooth-device
对于我的应用程序,我想要一个设备选择器,如 Chrome 中所示。我想实现的顺序是:
- 开始申请
- 搜索设备
- 设备选择器弹出
- 选择设备
- Devicepicker 关闭
- 查看应用程序中的数据
参考教程流程的代码和代码片段:
电子应用程序:main.js(主进程)renderer.js(渲染进程)devicepicker GUI:devicepicker.js(渲染进程)devicepicker.html & layout.css(GUI)
1)用一个GUI(我用过两个)和一个脚本创建devicepicker
2)在您的 main.js 中,在您的应用程序对象的事件中创建一个select-bluetooth-device
事件'ready'
(上面链接中的文档)当您navigator.bluetooth.requestDevice()
在 renderer.js 中启动时,该事件被触发并且设备列表位于主进程中。它在console.log(deviceList)
外壳中可见。要处理它,您需要将其发送到渲染器进程(您的应用程序窗口)。
3)为此,我们webContents.send
在 webContents.on 事件中实现 BrowserWindow 对象。现在主进程每次通过通道发现新设备时都会发送一个设备列表channelForBluetoothDeviceList
4)在 renderer.js 中创建startDevicePicker()
。devicePicker()
必须在与 相同的功能中启动navigator.bluetooth.requestDevice()
。startDevicePicker()
实例化一个BrowserWindow()
加载的新对象devicepicker.html
5)要从主进程中获取列表,ipcRenderer.on()
必须在其中实现一个监听startDevicePicker()
器来监听channelForBluetoothDeviceList
我们主进程的通道。现在我们可以在我们的电子应用程序(渲染器进程)中获取列表。要将其发送到 devicepicker UI,我们需要将其从电子应用程序(渲染器进程)转发到 devicepicker(也是渲染器进程)
6)为此,我们需要ipcRenderer.sendTo()
发送者 in devicePicker()
,它将消息从渲染器进程转发到特定的其他渲染器进程。除了bluetoothDeviceDiscoverList
我们需要BrowserWindow.id
devicepicker 的频道之外。由于我们只是实例化了它,我们可以使用我们的 devicepicker 对象。我有一个只发送一次的设备,主要过程比构建 devicepicker 更快,我的列表从未发送到 devicepicker。所以我用 aPromise()
等待ipcRenderer.sendTo()
直到设备选择器准备好使用。
7)要在我们的 devicepicker GUI 上接收设备列表,我们需要监听bluetoothDeviceDiscoverList
with ipcRenderer.on()
(devicepicker.js)。我现在将 devicelist 插入到<option>
了 devicepicker 中,当然你可以使用其他元素(devicepicker.html)。请注意:实现一个将发送列表与当前列表进行比较的查询。否则,您将获得多个设备,并且您的选择会变得冗长。我仍然需要这样做,它还没有完成:-)
8)要选择一个navigator.bluetooth.requestDevice()
(renderer.js)被解析的设备,我们需要将BluetoothDevice.deviceId
我们选择的设备发送回主进程,在主进程中我们用deviceId作为回调参数(main.js)回调“callback”。
9)现在我们可以使用ipcRenderer.sendTo()
将选择BluetoothDevice.deviceId
的内容发送到主进程(devicepicker.js)。
10)在我们的电子应用程序的主进程(main.js)中,我们用接收到的来监听频道channelForSelectingDevice
和ipcMain.on()
回调BluetoothDevice.deviceId
。设备发现被停止,navigator.bluetooth.requestDevice()
得到解决,我们在应用程序(renderer.js)中从我们的设备接收数据。要取消设备的发现,请ipcMain.on()
在另一个通道中监听channelForTerminationSignal
主进程 (main.js) 的信号,例如在单击 (devicepicker.js) 后使用空字符串调用回调(如文档中所写)
我承认如果没有设备选择器,它可以做得更简单。然后只需将设备列表从主进程(main.js)发送到您的应用程序(渲染器进程)。但这对我理解电子过程有很大帮助。我希望本教程对您有用:-)!
片段:
main.js
const { ipcMain, app, BrowserWindow } = require('electron')
let win = null;
var callbackForBluetoothEvent = null;
// Create the browser window.
function createWindow () {
win = new BrowserWindow({
webPreferences: {
nodeIntegration: true //to activate require()
}
})
win.maximize()
win.show()
//This sender sends the devicelist from the main process to all renderer processes
win.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault(); //important, otherwise first available device will be selected
console.log(deviceList); //if you want to see the devices in the shell
let bluetoothDeviceList = deviceList;
callbackForBluetoothEvent = callback; //to make it accessible outside createWindow()
win.webContents.send('channelForBluetoothDeviceList', bluetoothDeviceList);
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
//cancels Discovery
ipcMain.on('channelForTerminationSignal', _ => {
callbackForBluetoothEvent(''); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Discovery cancelled");
});
//resolves navigator.bluetooth.requestDevice() and stops device discovery
ipcMain.on('channelForSelectingDevice', (event, DeviceId) => {
callbackForBluetoothEvent(sentDeviceId); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Device selected, discovery finished");
})
renderer.js
function discoverDevice() {
navigator.bluetooth.requestDevice()
startDevicepicker()
}
function startDevicepicker(){
let devicepicker = null;
let mainProcessDeviceList = null;
devicepicker = new BrowserWindow({
width: 350,
height: 270,
show: false, //needed to resolve promise devicepickerStarted()
webPreferences: {
nodeIntegration: true
}
})
devicepicker.loadFile('devicePicker.html');
//electron application listens for the devicelist from main process
ipcRenderer.on('channelForBluetoothDeviceList', (event, list) => {
mainProcessDeviceList = list;
devicepickerStarted.then(_=> {
console.log("Promise resolved!");
ipcRenderer.sendTo(devicepicker.webContents.id, 'bluetoothDeviceDiscoverList', mainProcessDeviceList);
})
})
//Promise that ensures that devicepicker GUI gets the list if the device only sends once
var devicepickerStarted = new Promise(
function (resolve, reject) {
console.log("Promise started");
devicepicker.once('ready-to-show', () => {
devicepicker.show();
resolve();
console.log("Devicepicker is ready!")
})
}
)
//remove listeners after closing devicepicker
devicepicker.on('closed', _ => {
devicepicker = null;
ipcRenderer.removeAllListeners('channelForBluetoothDeviceList');
ipcRenderer.removeAllListeners('currentWindowId');
ipcRenderer.removeAllListeners('receivedDeviceList');
})
}
devicepicker.js
//save received list here
var myDeviceList = new Array();
//Html elements
const devicePickerSelection = document.getElementById("devicePickerSelection");
const buttonSelect = document.getElementById("Select");
const buttonCancel = document.getElementById("Cancel");
//eventListeners for buttons
buttonSelect.addEventListener('click', selectFromDevicePicker);
buttonCancel.addEventListener('click', cancelDevicePicker);
//listens for deviceList
ipcRenderer.on('receivedDeviceList', (event, bluetoothDeviceDiscoverList) => {
console.log("list arrived!")
//code: add list to html element
});
function selectFromDevicePicker() {
let selectedDevice = devicePickerSelection.value;
let deviceId = //depends on where you save the BluetoothDevice.deviceId values
//sends deviceId to main process for callback to resolve navigator.bluetooth.requestDevice()
ipcRenderer.send('channelForSelectingDevice', deviceId);
ipcRenderer.removeAllListeners('receivedDeviceList');
closeDevicePicker();
}
function cancelDevicePicker() {
ipcRenderer.send('channelForTerminationSignal');
closeDevicePicker();
}
function closeDevicePicker() {
myDevicePicker.close();
}}