7

I'm creating a simple form in C# Windows Forms and run into a common situation where one control can alter the state of another, but both controls' events trigger some other method.

For example, consider a Checkbox and NumericUpDown where the state or value of either one should trigger something to redraw. The NumericUpDown is dependent upon the Checkbox meaning it could be disabled or ignored unless the Checkbox is checked.

However, it is convenient for the user to change the NumericUpDown value, and have the Checkbox be automatically checked if it wasn't already.

So here are the methods in question:

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

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

The problem of course, is that changing the NumericUpDown value causes RedrawStuff() to fire twice.

My workaround is to introduce a boolean where I effectively can negate this behavior, but sometimes it can get messy:

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;
}

Is this the best way to handle this situation?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JYelton
  • 35,664
  • 27
  • 132
  • 191
  • I simplified the example code; perhaps `RedrawStuff` was a poor choice of name. In this case it involves re-querying a database, changing several databindings, and changing some control and panel states such as hiding groups, etc. – JYelton Jul 07 '11 at 00:56
  • I've use flags like that dozens of times, and haven't found an alternative I consider better for the general case. – xpda Jul 07 '11 at 02:53
  • Unsubscribe from the events you don't want to fire (in this case, chkState_CheckedChanged) for the duration that you do not want them to fire. – Tom Apr 08 '16 at 21:49

4 Answers4

7

For the more general case, if you have complex relations between the controls, you could check which control fired the event by checking its own Focused property:

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();
}

This way, each object is only dependent on itself.

As far as I know, the object must have focus to fire a user event, and there can only be one object with focus at any given time. However, I'm not sure it will work in all cases so this still needs testing.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Yanir Kleiman
  • 377
  • 1
  • 8
  • I believe you can programmatically cause a control to fire. If you change a textbox's text from within a checkedChanged handler, for example, the focus will remain on the checkbox when TextChanged is fired. – xpda Jul 07 '11 at 02:49
  • 1
    That's exactly the point; if the event was fired from code we don't want to handle it. I assume it will only receive focus when the user fired the event from the UI. However as I suspected this doesn't always work - I ran a test and when "clicking" a button using a keyboard shortcut it doesn't get focus. Most other mouse and keyboard events does cause focus (for example when checking a checkbox using a keyboard shortcut it does get focus). – Yanir Kleiman Jul 07 '11 at 09:59
  • @Yanir: This is a very good solution, and as you indicate there are some potential issues with keyboard shortcuts and situations in which focus isn't on the control being changed by the user. Implemented with these pitfalls in mind, it is quite useful. – JYelton Jul 07 '11 at 16:31
1

Just move RedrawStuff(); to an else clause. This way it's called in both situations, but only once.

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();
}
Nahydrin
  • 13,197
  • 12
  • 59
  • 101
  • 1
    This is simple and works well, but can get complicated when there are many controls interacting. – JYelton Jul 07 '11 at 16:21
1

You could "un-wire" the change events at the beginning of your "RedrawStuff" method and wire them up again at the end.


private void RedrawStuff() { // un-wire the change events 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);

}

(I've used this approach a few times as it seems to achieve the a solution to the problem you've described, but I would be happy if someone was to point out why, for reasons I may not understand, that it may be a bad approach.)

Stuart Helwig
  • 9,318
  • 8
  • 51
  • 67
  • I've done this before, also. It seems to work okay, but occasionally I have had weird timing issues where the event (un)subscription seems to not take effect precisely when expected. – JYelton Jul 07 '11 at 01:48
1

I think that CheckedChanged on a CheckBox does not fire if the control is disabled, so do this:

private void numStateValue_ValueChanged(object sender, EventArgs e) 
{     
    if (!chkState.Checked)      
    {
        chkState.Enabled = false;
        chkState.Checked = true;    
        chkState.Enabled = true;
    }
    RedrawStuff();
} 
miken32
  • 42,008
  • 16
  • 111
  • 154
MusiGenesis
  • 74,184
  • 40
  • 190
  • 334
  • Unfortunately this didn't work in my tests; the disabled checkbox still fired the CheckedChanged event. Maybe a disabled control fires certain events but not others? – JYelton Jul 07 '11 at 16:27
  • A more likely explanation is that I'm insane. – MusiGenesis Jul 07 '11 at 16:46
  • Not at all, you've provided many answers (maybe not directly) that have helped me. Maybe you can alter this answer for upvotes and cookies. Or at least upvotes! – JYelton Jul 07 '11 at 17:08