如何确保基于RawInputEventArg的OnKeyPressed函数仅触发一次及扫描器添加后功能异常排查
嘿,我来帮你拆解一下你遇到的问题,还有对应的解决思路:
一、为什么添加扫描器后无法退出FrmSettings的OnKeyPressed逻辑?
你的核心问题在于两个窗体(FrmMenu和FrmSettings)都监听了RawInput的KeyPressed事件,再加上状态控制和窗体生命周期的小漏洞,导致了逻辑混乱:
事件监听未及时注销:当你打开FrmSettings并开启添加扫描器模式(
newScanner = true)时,它注册了KeyPressed事件,但添加完成后,即使把newScanner设为false,事件本身并没有从RawInput的事件列表中移除。这意味着后续的扫描操作(甚至普通按键)依然会触发FrmSettings的OnKeyPressed方法,只是因为newScanner为false不执行核心逻辑,让你误以为是“无法退出”这个方法——其实方法是正常执行了,只是没走里面的代码块。FrmSettings的生命周期处理错误:看你的
BtnSettings_Click代码:private void BtnSettings_Click(object sender, EventArgs e) { FrmSettings settings = new FrmSettings(); settings.ShowDialog(); settings.BtnSave_Click(sender, e); settings.Dispose(); }这里有个明显的问题:
ShowDialog()是模态对话框,只有用户关闭FrmSettings后才会执行后面的代码,但你直接调用BtnSave_Click完全不合理——这个方法应该由FrmSettings内部的按钮点击触发,而不是外部强行调用。更关键的是,如果FrmSettings关闭前没注销KeyPressed事件,即使调用了Dispose,也可能因为事件引用导致内存泄漏,或者事件依然被触发。扫描器添加后的状态同步缺失:你成功把扫描器加入字典后,FrmMenu的逻辑已经能识别这个设备,但FrmSettings的事件监听还在运行,导致后续操作依然会触发它的OnKeyPressed方法。
二、如何让基于RawInputEventArg的OnKeyPressed仅触发一次?
要实现“仅触发一次”,你需要在事件处理完成后主动注销事件绑定,同时配合状态变量做双重控制,具体修改方案如下:
1. 重构FrmSettings的事件注册/注销逻辑
只在用户点击“添加扫描器”按钮时注册事件,添加完成后立即注销,确保后续不会再触发:
// 点击添加偶数扫描器按钮的方法 private void BtnAddEvenScanner_Click(object sender, EventArgs e) { EvenOrOdd = true; newScanner = true; // 仅此时注册事件 RawInput.KeyPressed += OnKeyPressed; } // 点击添加奇数扫描器按钮的方法 private void BtnAddOddScanner_Click(object sender, EventArgs e) { EvenOrOdd = false; newScanner = true; RawInput.KeyPressed += OnKeyPressed; } // 修改scannerAnlegen方法 public void scannerAnlegen(string devIDnew) { try { Scanner newScannerObj = EvenOrOdd ? new Scanner("dev3", "even") : new Scanner("dev4", "odd"); FrmMenu.Scanners.Add(devIDnew, newScannerObj); MessageBox.Show("扫描器添加成功!"); } catch (ArgumentException ex) { MessageBox.Show($"该扫描器已存在:{ex.Message}"); } finally { // 无论成功失败,都注销事件,确保不再触发 RawInput.KeyPressed -= OnKeyPressed; newScanner = false; } }
2. 修复FrmMenu打开设置窗体的逻辑
不要强行调用保存方法,让FrmSettings自己处理保存状态:
private void BtnSettings_Click(object sender, EventArgs e) { // 使用using块自动处理Dispose using (FrmSettings settings = new FrmSettings()) { // 只有用户点击保存按钮(设置了DialogResult.OK),才同步数据 if (settings.ShowDialog() == DialogResult.OK) { // 这里可以刷新FrmMenu的扫描器列表等操作 RefreshScannerDisplay(); } } }
然后在FrmSettings的BtnSave_Click方法里添加:
private void BtnSave_Click(object sender, EventArgs e) { // 可以在这里做一些额外的保存逻辑,比如写入配置文件 DialogResult = DialogResult.OK; Close(); }
3. 优化FrmMenu的OnKeyPressed逻辑
去掉冗余判断,让逻辑更清晰:
private void OnKeyPressed(object sender, RawInputEventArg e) { // 设备不在字典里直接返回 if (!Scanners.ContainsKey(e.KeyPressEvent.DeviceName)) { return; } // 只处理按键释放事件(你的原逻辑是跳过"MAKE",应该是按下为MAKE,释放为BREAK) if (e.KeyPressEvent.KeyPressState == "MAKE") { return; } // 不是换行符就累加扫描内容 if (e.KeyPressEvent.VKeyName != "\n") { scanNumber += e.KeyPressEvent.VKeyName; return; } // 扫描完成,执行后续逻辑 string devID = e.KeyPressEvent.DeviceName; Debug.Print(devID); Aufrufen(scanNumber); scanNumber = ""; }
三、额外注意事项
- 线程安全:如果RawInput事件是在非UI线程触发的,要确保
newScanner这类状态变量的访问是线程安全的,比如用lock包裹状态修改逻辑。 - 配置持久化:添加扫描器后最好把设备信息写入配置文件,避免程序重启后丢失。
内容的提问来源于stack exchange,提问作者coffeemug22

