You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何确保基于RawInputEventArg的OnKeyPressed函数仅触发一次及扫描器添加后功能异常排查

问题分析与解决方案

嘿,我来帮你拆解一下你遇到的问题,还有对应的解决思路:

一、为什么添加扫描器后无法退出FrmSettings的OnKeyPressed逻辑?

你的核心问题在于两个窗体(FrmMenu和FrmSettings)都监听了RawInput的KeyPressed事件,再加上状态控制和窗体生命周期的小漏洞,导致了逻辑混乱:

  1. 事件监听未及时注销:当你打开FrmSettings并开启添加扫描器模式(newScanner = true)时,它注册了KeyPressed事件,但添加完成后,即使把newScanner设为false,事件本身并没有从RawInput的事件列表中移除。这意味着后续的扫描操作(甚至普通按键)依然会触发FrmSettings的OnKeyPressed方法,只是因为newScanner为false不执行核心逻辑,让你误以为是“无法退出”这个方法——其实方法是正常执行了,只是没走里面的代码块。

  2. 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,也可能因为事件引用导致内存泄漏,或者事件依然被触发。

  3. 扫描器添加后的状态同步缺失:你成功把扫描器加入字典后,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 20:02:47