如何在C# Windows Forms中更改控件状态时忽略简单的事件触发?

7
我正在使用C# Windows Forms创建一个简单的表单,在其中遇到了一个常见情况,即一个控件可以改变另一个控件的状态,但是这两个控件的事件都会触发其他方法。
例如,考虑一个复选框和一个数字选择框,其中任何一个的状态或值都应该触发某些重绘操作。数字选择框依赖于复选框,这意味着除非复选框被选中,否则它可能会被禁用或忽略。
然而,用户更改数字选择框的值并自动选中复选框(如果尚未选中)是很方便的。
因此,以下是相关的方法:
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 是一个不太好的命名选择。在这种情况下,它涉及重新查询数据库,更改多个数据绑定,并更改一些控件和面板状态,例如隐藏组等。 - JYelton
我已经使用这样的标志数十次了,而且在一般情况下,我没有找到更好的替代方案。 - xpda
取消订阅您不想触发的事件(在本例中为chkState_CheckedChanged),以便在您不希望它们触发的时间内不会触发它们。 - Tom
4个回答

7

对于更一般的情况,如果控件之间存在复杂的关系,您可以通过检查其自身的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事件时,焦点将仍然停留在复选框上。 - xpda
1
这正是关键所在;如果事件是从代码中触发的,我们不想处理它。我认为只有当用户从UI中触发事件时,它才会接收到焦点。 然而,正如我所怀疑的那样,这并不总是有效 - 我进行了测试,当使用键盘快捷键“点击”按钮时,它不会获得焦点。大多数其他鼠标和键盘事件确实会导致焦点(例如,使用键盘快捷键选中复选框时,它确实会获得焦点)。 - Yanir Kleiman
@Yanir:这是一个非常好的解决方案,正如您所指出的,键盘快捷键和用户未将焦点放在正在更改的控件上的情况可能存在一些潜在问题。但是,如果考虑到这些缺陷并实施,它会非常有用。 - JYelton

1
只需要将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();
}

1
这很简单并且运作良好,但是当有许多控件相互交互时可能会变得复杂。 - JYelton

1

我认为当CheckBox控件被禁用时,其CheckedChanged事件不会触发,因此可以这样处理:

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

不幸的是,在我的测试中这并没有起作用;禁用的复选框仍然触发了 CheckedChanged 事件。也许禁用的控件会触发某些事件,但不会触发其他事件? - JYelton
更有可能的解释是我疯了。 - MusiGenesis
完全不是这样,你提供了许多答案(也许不是直接的),对我很有帮助。也许你可以修改一下这个回答,以获得赞和饼干。或者至少赞! - JYelton

1
你可以在“RedrawStuff”方法的开头“取消连接”更改事件,然后在结尾重新连接它们。

私有无返回值的 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... */
// 重新绑定改变事件 chkState.CheckedChanged += new System.EventHandler(chkState_CheckedChanged); numStateValue.ValueChanged += new System.EventHandler(chkState_CheckedChanged);

}

(我已经多次使用过这种方法,因为它似乎能够解决你所描述的问题,但如果有人指出由于某些原因我可能不理解的原因,这种方法可能是不好的,我会很高兴。)


我之前也做过这个。看起来正常运作,但有时会出现奇怪的时间问题,事件(取消)订阅似乎没有在预期的时间精确生效。 - JYelton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接