【问题标题】:How do I ignore simple events firing when changing control states in C# Windows Forms?在 C# Windows 窗体中更改控件状态时,如何忽略触发的简单事件?
【发布时间】:2026-01-17 22:00:02
【问题描述】:

我在 C# Windows 窗体中创建了一个简单的窗体,遇到了一种常见情况,即一个控件可以改变另一个控件的状态,但两个控件的事件都会触发其他方法。

例如,考虑CheckboxNumericUpDown,其中任何一个的状态或值都应该触发重绘。 NumericUpDown 取决于 Checkbox,这意味着除非选中 Checkbox,否则它可以被禁用或忽略。

但是,用户可以方便地更改NumericUpDown 的值,如果还没有Checkbox,则自动检查它。

以下是有问题的方法:

private void chkState_CheckedChanged(object sender, EventArgs e)
{
    RedrawStuff();
}

private void numStateValue_ValueChanged(object sender, EventArgs e)
{
    if (!chkState.Checked)
        chkState.Checked = true;
    RedrawStuff();
}

当然,问题是更改NumericUpDown 值会导致RedrawStuff() 触发两次。

我的解决方法是引入一个布尔值,我可以有效地否定这种行为,但有时它会变得混乱:

bool _preventStateChange;

private void chkState_CheckedChanged(object sender, EventArgs e)
{
    if (_preventStateChange)
        return;
    RedrawStuff();
}

private void numStateValue_ValueChanged(object sender, EventArgs e)
{
    _preventStateChange = true;
    if (!chkState.Checked)
        chkState.Checked = true;
    RedrawStuff();
    _preventStateChange = false;
}

这是处理这种情况的最佳方法吗?

【问题讨论】:

  • 我简化了示例代码;也许RedrawStuff 是一个糟糕的名字选择。在这种情况下,它涉及重新查询数据库、更改多个数据绑定以及更改某些控件和面板状态,例如隐藏组等。
  • 我已经使用了几十次这样的标志,但没有找到我认为更适合一般情况的替代方案。
  • 在您不想触发的时间内取消订阅您不想触发的事件(在本例中为 chkState_CheckedChanged)。

标签: c# winforms events


【解决方案1】:

对于更一般的情况,如果控件之间有复杂的关系,则可以通过检查其自己的Focused 属性来检查哪个控件触发了事件:

private void chkState_CheckedChanged(object sender, EventArgs e)
{
    if (chkState.Focused)
        RedrawStuff();
}

private void numStateValue_ValueChanged(object sender, EventArgs e)
{
    if (!chkState.Checked)
        chkState.Checked = true;

    if (numStateValue.Focused)
        RedrawStuff();
}

这样,每个对象都只依赖于自己。

据我所知,对象必须具有焦点才能触发用户事件,并且在任何给定时间只能有一个对象具有焦点。但是,我不确定它是否适用于所有情况,因此仍需要测试。

【讨论】:

  • 我相信您可以通过编程方式触发控件。例如,如果您从 checkedChanged 处理程序中更改文本框的文本,则在触发 TextChanged 时焦点将保持在复选框上。
  • 这正是重点;如果事件是从代码中触发的,我们不想处理它。我假设它只会在用户从 UI 触发事件时获得焦点。但是,我怀疑这并不总是有效 - 我进行了测试,当使用键盘快捷键“单击”按钮时,它没有获得焦点。大多数其他鼠标和键盘事件确实会引起焦点(例如,当使用键盘快捷键检查复选框时,它确实会获得焦点)。
  • @Yanir:这是一个非常好的解决方案,正如您所指出的,键盘快捷键和焦点不在用户更改的控件上存在一些潜在问题。考虑到这些陷阱来实施,它非常有用。
【解决方案2】:

只需将 RedrawStuff(); 移动到 else 子句即可。这样在两种情况下都会调用它,但只调用一次。

private void chkState_CheckedChanged(object sender, EventArgs e)
{
    RedrawStuff();
}

private void numStateValue_ValueChanged(object sender, EventArgs e)
{
    if (!chkState.Checked)
        chkState.Checked = true;
    else
        RedrawStuff();
}

【讨论】:

  • 这很简单,效果很好,但是当有很多控件交互时会变得复杂。
【解决方案3】:

我认为如果控件被禁用,CheckBox 上的 CheckedChanged 不会触发,所以这样做:

private void numStateValue_ValueChanged(object sender, EventArgs e) 
{     
    if (!chkState.Checked)      
    {
        chkState.Enabled = false;
        chkState.Checked = true;    
        chkState.Enabled = true;
    }
    RedrawStuff();
} 

如果我记错了,请投反对票。

【讨论】:

  • 不幸的是,这在我的测试中不起作用;禁用的复选框仍然触发 CheckedChanged 事件。也许禁用的控件会触发某些事件但不会触发其他事件?
  • 一个更可能的解释是我疯了。
  • 一点也不,您提供了许多对我有帮助的答案(可能不是直接的)。也许您可以更改此答案以获取赞成票和 cookie。或者至少赞成!
【解决方案4】:

您可以在“RedrawStuff”方法的开头“取消连接”更改事件,并在最后重新连接它们。

private void RedrawStuff() { // 取消连接更改事件 chkState.CheckedChanged -= new System.EventHandler(chkState_CheckedChanged); numStateValue.ValueChanged -= new System.EventHandler(chkState_CheckedChanged);

/* .... do you thing...including setting the state of both / either controls based on the state of things, without fear of triggering recursive changes... */ // wire them back up chkState.CheckedChanged += new System.EventHandler(chkState_CheckedChanged); numStateValue.ValueChanged += new System.EventHandler(chkState_CheckedChanged);

}

(我已经多次使用这种方法,因为它似乎可以解决您所描述的问题,但如果有人指出为什么,出于我可能不明白的原因,我会很高兴这可能是一个不好的方法。)

【讨论】:

  • 我以前也这样做过。它似乎工作正常,但偶尔我会遇到奇怪的时间问题,即事件(取消)订阅似乎没有在预期的时候准确生效。