代码
我正在为菜单栏构建一个应用程序菜单,我正在使用角色来自动化一些预定义的功能,比如:
{
label: 'File',
submenu: []
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' }
]
},
{
label: 'View',
submenu: []
},
...
问题
下面的代码部分呈现菜单并像魔术一样工作:{ role: 'undo' }
,但仅用于正常操作。
但是,当您以编程方式替换表单控件(textbox、texarea等(的值,然后对该表单控件进行一些手动更改时,Undo功能将不再工作。
我可能错过了什么?这是虫子还是什么?以下是使用的软件版本。
环境
软件 | |
---|---|
电子 | 12.2.3 |
节点.js | 14.16.0 |
MacOS | 10.13.6 |
由于Electron的角色只是Chrome功能的扩展,真正的测试是看看在不使用Electron时是否会发生同样的情况。
我已经在标准html代码库和Electron代码库中测试了您的问题。
标准HTML代码库
在下面的文件中,我有两个文本字段。一个是输入字段,另一个是提供反馈,只是为了确定。
button
是将一些text
动态地附加到输入字段的末尾。
测试:
- 在输入字段中键入一些文本
- 尝试撤消和重做功能。
// Success
- Windows:Ctrl-Z和Ctrl-Y
- MacOS:Cmd-Z和Shift-Cmd-Z
- 现在单击
button
以附加一些文本 - 再次尝试撤消和重做。
// Failure
动态添加文本后,撤消和重做不再有效。为什么?我不知道为什么,但这似乎是Chrome的标准功能。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Undo / Redo Test</title>
</head>
<body>
<div>
<label for="input">Input: </label>
<input type="text" id="input">
<input type="button" id="button" value="Append 'text'">
</div>
<div>
<label for="output">Output: </label>
<input type="text" id="output" disabled>
</div>
</body>
<script>
let input = document.getElementById('input');
let output = document.getElementById('output');
input.addEventListener('keyup', () => {
output.value = input.value;
});
document.getElementById('button').addEventListener('click', () => {
let value = input.value + ' text';
input.value = value;
output.value = value;
});
</script>
</html>
电子代码库
现在,让我们将完全相同的html代码移到Electron应用程序中,这样我们就可以使用Edit
->Undo
/Redo
菜单。
通过键盘快捷键进行测试会产生与上述相同的结果。
让我们用菜单测试一下:
- 在输入字段中键入一些文本
- 选择
edit
->undo
和redo
功能。// Succees
- 现在单击
button
以附加一些文本 - 尝试
edit
->undo
和redo
。// Failure
结果相同。动态添加文本后,撤消和重做将不再有效。标准HTML代码库的反映。
main.js
(主线程(
'use strict';
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const nodePath = require("path");
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
fullscreen: false,
resizable: true,
movable: true,
minimizable: true,
maximizable: true,
enableLargerThanScreen: true,
closable: true,
focusable: true,
fullscreenable: true,
frame: true,
hasShadow: true,
backgroundColor: '#fff',
show: false,
icon: nodePath.join(__dirname, 'icon.png'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
worldSafeExecuteJavaScript: true,
enableRemoteModule: false,
devTools: (! electronApp.isPackaged),
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html')
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
preload.js
(主线程(
'use strict';
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
index.html
(渲染线程(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Undo / Redo Test</title>
</head>
<body>
<div>
<label for="input">Input: </label>
<input type="text" id="input">
<input type="button" id="button" value="Append 'text'">
</div>
<div>
<label for="output">Output: </label>
<input type="text" id="output" disabled>
</div>
</body>
<script>
let input = document.getElementById('input');
let output = document.getElementById('output');
input.addEventListener('keyup', () => {
output.value = input.value;
});
document.getElementById('button').addEventListener('click', () => {
let value = input.value + ' text';
input.value = value;
output.value = value;
});
</script>
</html>
测试使用:
- 电子:17.1.0
- 节点:16.13.0
- 铬:98.04.4758.102
测试日期:
- Windows:10
- MacOS:10.15.7
Memento&命令设计模式
您可以尝试将undo
和redo
与execCommand
之类的东西挂钩,但我认为,如果您想做得好,使其具有可扩展性和可读性,那么您应该真正按照Memento和Command设计模式来实现一些东西。
尽管需要进行一些设置工作,但一旦工作,它就可以应用于页面上的任何<form>
字段,而不仅仅是<input type="text">
和<textarea>
。
但为什么要到此为止,让它也适用于页面或应用程序范围的功能(在可以应用它的地方(。例如:"删除"、"重命名"、"移动"、"发布"、"设置更新"等