非常感谢您的支持。根据您的建议和一些研究,我开发了一个可行的解决方案并愿意与您分享。
这两个链接对我帮助很大:
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 关闭
- 查看应用程序中的数据
教程流程的代码和sn-p的代码参考:
电子应用:main.js(主进程)renderer.js(渲染进程)devicepicker GUI:devicepicker.js(渲染进程)devicepicker.html & layout.css(GUI)
1) 用一个 GUI(我用过,两个)和一个脚本创建 devicepicker
2) 在您的 main.js 中,在您的应用程序对象的 'ready' 事件内创建一个 select-bluetooth-device 事件(上面链接中的文档)当您在 renderer.js 中启动 navigator.bluetooth.requestDevice() 时,事件 get 被触发并且设备列表在主进程中。使用console.log(deviceList),它在外壳中可见。要处理它,您需要将其发送到渲染器进程(您的应用程序窗口)。
3) 为此,我们在 webContents.on 事件中实现 BrowserWindow 对象的webContents.send。现在主进程每次通过频道channelForBluetoothDeviceList发现新设备时都会发送一个设备列表
4) 在 renderer.js 中创建 startDevicePicker()。 devicePicker() 必须在与navigator.bluetooth.requestDevice() 相同的函数中启动。 startDevicePicker() 实例化一个新的 BrowserWindow() 对象,该对象加载 devicepicker.html
5) 要从主进程获取列表,必须在startDevicePicker() 中实现ipcRenderer.on() 侦听器,该侦听器侦听主进程的channelForBluetoothDeviceList 通道。现在我们可以在我们的电子应用程序(渲染器进程)中获取列表。要将其发送到 devicepicker UI,我们需要将其从电子应用程序(渲染器进程)转发到 devicepicker(也是渲染器进程)
6) 为了实现这一点,我们需要devicePicker() 中的ipcRenderer.sendTo() 发送器,它将消息从渲染器进程转发到特定的其他渲染器进程。除了频道bluetoothDeviceDiscoverList,我们还需要设备选择器的BrowserWindow.id。由于我们只是实例化了它,我们可以使用我们的 devicepicker 对象。我有一个只发送一次的设备,主要过程比构建 devicepicker 更快,我的列表从未发送到 devicepicker。所以我使用Promise() 等待ipcRenderer.sendTo() 直到设备选择器准备好使用。
7) 要在我们的 devicepicker GUI 上接收设备列表,我们需要使用 ipcRenderer.on() (devicepicker.js) 监听 bluetoothDeviceDiscoverList。我现在将设备列表插入到设备选择器的<option>,当然你可以使用其他元素(devicepicker.html)。请注意:实现一个将发送列表与当前列表进行比较的查询。否则,您将获得多个设备,并且您的选择会变得冗长。我仍然需要这样做,它还没有完成:-)
8) 要选择navigator.bluetooth.requestDevice() (renderer.js) 得到解析的设备,我们需要将我们选择的设备的BluetoothDevice.deviceId 发送回我们回调“callback”的主进程' 以 deviceId 作为回调参数 (main.js)。
9) 现在我们可以使用ipcRenderer.sendTo() 将选中的BluetoothDevice.deviceId 发送到主进程(devicepicker.js)。
10) 在我们的电子应用程序的主进程(main.js)中,我们用ipcMain.on() 监听频道channelForSelectingDevice,并用收到的BluetoothDevice.deviceId 回调。设备发现停止,navigator.bluetooth.requestDevice() 得到解决,我们在应用程序 (renderer.js) 中接收来自设备的数据。要取消对设备的发现,请在另一个频道 channelForTerminationSignal 中使用 ipcMain.on() 侦听,这只是向主进程 (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();
}}