3

I have a CheckBox that has it's Checked property bound to a bool value. During the CheckedChanged event, some logic runs which uses the bool property on the data source.

My problem is the first time the CheckBox gets checked by the user, the bound data source does not get updated. Subsequent updates work fine though.

Here's some sample code for testing the problem. Just create a blank form and add a CheckBox to it.

public partial class Form1 : Form
{
    private bool _testBool;
    public bool TestBool
    {
        get { return _testBool; }
        set { _testBool = value; }
    }

    public Form1()
    {
        InitializeComponent();

        checkBox1.DataBindings.Add(new Binding("Checked", this, "TestBool"));
        checkBox1.CheckedChanged += new EventHandler(checkBox1_CheckedChanged);
    }

    void checkBox1_CheckedChanged(object sender, EventArgs e)
    {
        checkBox1.BindingContext[this].EndCurrentEdit();

        Debug.WriteLine(TestBool.ToString());
    }
}

The first time I check the box, the TestBool property remains at false, even though checkBox1.Checked is set to true. Subsequent changes do correctly update the TestBool property to match checkBox1.Checked though.

If I add a breakpoint to the CheckedChanged event and check out checkBox1.BindingContext[this].Bindings[0] from the immediate window, I can see that modified = false the first time it runs, which is probably why EndCurrentEdit() is not correctly updating the data source.

The same thing also occurs with using a TextBox and the TextChanged event, so this isn't limited to just CheckBox.Checked.

Why is this? And is there a generic common way of fixing the problem?

Edit: I know of a few workarounds so far, although none are ideal since they are not generic and need to be remembered everytime we want to use a Changed event.

  • setting the property on the datasource directly from the CheckedChanged event
  • finding the binding and calling WriteValue()
  • hooking up the bindings after the control has been loaded

I am more concerned with learning why this is happening, although if someone knows of a standard generic solution to prevent it from happening that does not rely on any special coding in the Changed event, I'd be happy with that too.

Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Might be an order-of-operations issue, try using the `Click()` event. – DonBoitnott Oct 15 '14 at 14:58
  • @DonBoitnott The `Click` event does work, however it doesn't capture when the user checks the box in other ways such as tabbing to it and hitting the spacebar. Overall I think I prefer the workaround of setting the data source value in the `CheckChanged` event, although you are probably onto something with it being an issue with the order of operations. – Rachel Oct 15 '14 at 15:17
  • The data source update is likely triggered by a "validate" call of some kind. When that happens is certainly the key to your problem. I agree Click isn't the ideal method, but it's always my first test for things I suspect are order related, because it almost always happen (near) last. – DonBoitnott Oct 15 '14 at 15:34
  • @DonBoitnott Yes, if I wrap my `EndCurrentEdit` and `Debug.Write` statement in `BeginInvoke`, it has the value updated correctly. I still don't understand why this only occurs during the first time the value changes though, and why such a bug (if it is that) would exist in the first place. I would have expected MS to test the basic functionality of their data binding system a bit better. – Rachel Oct 15 '14 at 15:56

3 Answers3

1

Controls usually want to go through their validation first before writing to the data source, so the writing of the value usually won't happen until you try to leave the control.

You can force the writing of the value yourself:

void checkBox1_CheckedChanged(object sender, EventArgs e) {
  Binding b = checkBox1.DataBindings["Checked"];
  if (b != null) {
    b.WriteValue();
  }
  Debug.WriteLine(TestBool.ToString());
}
LarsTech
  • 80,625
  • 14
  • 153
  • 225
  • Manually toggling focus or running the validation does not work, however `WriteValue` does. I'm not a huge fan of this though because you need to know the right index of the data binding to call `WriteValue` on, and it isn't a very good solution in cases where you have multiple bindings for a control, or multiple controls bound to the same data item. – Rachel Oct 17 '14 at 14:54
  • @Rachel You can use the property name as a key: `checkBox1.DataBindings["Checked"].WriteValue();` – LarsTech Oct 17 '14 at 14:56
  • That's better, although I'm still not entirely happy with having to find a specific binding and update it. The code I'm working with has a custom framework for handling data bindings, and the `BindingContext[_data].EndCurrentEdit` call is part of the inner workings of it. I'll have to see what happens if I refactor it to update specific bindings instead of just calling `EndCurrentEdit` on the bound data object – Rachel Oct 17 '14 at 15:09
  • 1
    @Rachel WinForm's bindings are not as advanced as those in your WPF world. A "generic" solution would be to inherit your own CheckBox and run this same code. There isn't a setting to make that call to WriteValue any earlier, you would have to do this yourself somewhere in your code. – LarsTech Oct 17 '14 at 15:25
  • That's actually a good idea :) We do use a customized CheckBox throughout the application, so it would be much easier to attach a `CheckedChanged` event there that calls `WriteValue` on the checked binding, than it would be to change the existing infrastructure. Thank you – Rachel Oct 17 '14 at 15:42
0

Apparently, the CheckedChanged event is too early in the process.

But you can leverage BindingComplete:

public partial class Form1 : Form
{
    private Boolean _testBool;
    public Boolean TestBool
    {
        get { return _testBool; }
        set { _testBool = value; }
    }

    public Form1()
    {
        InitializeComponent();

        checkBox1.DataBindings.Add(new Binding("Checked", this, "TestBool", true, DataSourceUpdateMode.OnPropertyChanged));
        checkBox1.DataBindings[0].BindingComplete += Form1_BindingComplete;
    }

    private void Form1_BindingComplete(Object sender, BindingCompleteEventArgs e)
    {
        Debug.WriteLine("BindingComplete:  " + TestBool.ToString());
    }
}

Note that the event will fire at startup as the initial bind linkage occurs. You will have to deal with that possible unintended consequence, but otherwise, it works on the first click and every click.

Also note that the true (format) is required in the Binding constructor to make the event fire.

DonBoitnott
  • 10,787
  • 6
  • 49
  • 68
  • The `BindingComplete` event does not get run when the item changes for the first time. I'm not sure if it matters or not, but I'm on .Net 3.5. – Rachel Oct 15 '14 at 17:48
  • My little test app is also set to 3.5 and it worked there. Can't say what the difference might be. – DonBoitnott Oct 15 '14 at 18:02
  • Ah I see the difference, I'm not specifying the 5th parameter of `DataSourceUpdateMode.OnPropertyChanged`. I think I found a explanation for the original problem posted [here](http://www.infragistics.com/community/forums/t/50983.aspx). I'm still looking for some Microsoft reference to back this claim up, however a quick test shows that if I hook up the Changed event during the Load event, it works correctly. I'll probably post this as an answer – Rachel Oct 15 '14 at 18:11
0

The closest I can find for an explanation to this behavior is this 3rd party explanation

Basically, this is an issue of timing. The way binding works in DotNet is actually very simple. There's no magic in the DotNet framework that tells the BindingManager when something changes. What it does is, when you bind to a property (such as CheckedValue) The BindingManager looks for an event on the control called propertynameChanged (e.g. "CheckedValueChanged"). This is the same event your code is hooking into on your sample form.

When the control fires the event, the order in which the listeners receive the event is arbitrary. There's no reliable way to tell whether the BindingManager will get the event first or the Form will.

My CheckBox1_CheckChanged event is running before the BindingManager handles the changed event, so the data source hasn't been updated at this time.

My best guess as to why this only happens the first time is that the control isn't visible yet, so some code doesn't get run that should fix the order events get handled in. I've seen other posts about not being able to bind to non-visible items due to the handle not being created yet, and one answer states

Until the control is visible for the first time some back-end initialization never happens, and part of that initialization is enabling the data binding.

So I suspect that this is somehow related.

I can verify that if I attach the Changed handler later on such as during the Load event, it works as I would expect.

public partial class Form1 : Form
{
    private bool _testBool;
    public bool TestBool
    {
        get { return _testBool; }
        set { _testBool = value; }
    }

    public Form1()
    {
        InitializeComponent();

        checkBox1.DataBindings.Add(new Binding("Checked", this, "TestBool"));
        Load += new EventHandler(Form1_Load);
    }

    void Form1_Load(object sender, EventArgs e)
    {
        checkBox1.CheckedChanged += new EventHandler(checkBox1_CheckedChanged);
    }

    void checkBox1_CheckedChanged(object sender, EventArgs e)
    {
        // Not needed anymore
        //checkBox1.BindingContext[this].EndCurrentEdit();

        Debug.WriteLine(TestBool.ToString());
    }
}
Community
  • 1
  • 1
Rachel
  • 130,264
  • 66
  • 304
  • 490