DataBinding object property to static list in ComboBox is calling EndEdit on model containing said property.
See original GitHub issue- .NET Core Version: .NET 5
- Have you experienced this same bug with .NET Framework?: Haven’t tested yet, will try tomorrow.
Problem description: So I’ve been Googling this for several hours and I haven’t found much, so I’m not sure if I am doing it wrong or if this is a bug.
I have a class with a string property, this property can be one of two values, for sake of example we will say A or B:
internal class Model : IEditableObject
{
public string TestString { get; set; } = "A";
public void BeginEdit()
{
Debug.WriteLine("BeginEdit");
}
public void CancelEdit()
{
Debug.WriteLine("CancelEdit");
}
public void EndEdit()
{
Debug.WriteLine("EndEdit");
}
}
In my form I setup a databinding for SelectedItem
on TestString
:
BindingSource bindingSource = new BindingSource(new Model(), "");
comboBox1.DataSource = new BindingSource(new string[] { "A", "B" }, ""); // Tried this with and without a BindingSource, probably don't need it.
comboBox1.DataBindings.Add("SelectedItem", bindingSource, "TestString");
The issue is, whenever the combobox runs it’s OnValidation
call setup via the binding, it is calling EndEdit
in Model
even though the DataSource
for the combobox is not the model, it’s supposed to be a static list.
Either I am missing something super obvious, or I’ve done something wrong, cause I am super confused. I followed the same pattern for TextBoxes
and NumericUpDowns` and those work fine. Not sure yet if this only applies to .NET 5, I will check tomorrow.
Expected behavior:
The property on Model
should be updated and editing should not be completed.
Minimal repro: See above code snippets.
Issue Analytics
- State:
- Created 3 years ago
- Comments:8 (4 by maintainers)
If you don’t implement
INotifyPropertyChanged
the binding source will do it for you. In this case every write of the binding source to the model will raise the change event but without knowing which property might have changed it will pull all properties from the model just to be safe.In simple scenarios this is fine as the values are usually the same as before, but if you have dependent bindings and event handlers at the same time it makes your life more complicated, because you have to pay attention to the order of updates you do. You can just do that (write your code being aware of the order you do things in), or if you don’t want to pay this attention you can implement
INotifyPropertyChanged
and take over the responibility of notifying the binding engine of which exact properties change.Alternatively use distinct binding sources instead of sharing the binding source, this also breaks notification dependencies.
As an example, if your
SelectedIndexChanged
handler updates theMaximum
and you fixed it to write back to the model, then model will tell all other bindings to refresh themselves in case something changed. Now ifSelectedIndexChanged
handler was called before theSelectedItem
binding wrote back the current selection, this refresh would flip the combo box back to the old value triggering yourSelectedIndexChanged
recursively.You can work around that by being order-aware or appropriately suspending bindings or using some flag to prevent recursion, there are many techniques, but personally I prefer to avoid all this complexity by implementing
INotifyPropertyChange
and managing dependencies between properties on the model itself.There are several things happening making everything very complicated:
OnValidation
mode, that is they will write back the value to the model on validation (usually when focus leaves the control)Minimum
immediately when the combobox selection changes; theValue
of theNumericUpDown
is updated but not written back because all bindings are inOnValidation
modeSelectedItem
is written back to the model (not theValue
of theNumericUpDown
because it is not the control being validated)INotifyPropertyChanged
on your model the binding system refreshes all bindings to make sure everything is up to date after writing back the valueTestInteger = 1
and tries to refresh theNumericUpDown
but this is no longer possible becauseMinimum = 50
EndEdit
to recover from the binding errorRecommendations:
OnPropertyChange
mode or make yourSelectedIndexChanged
handler flush relevant bindings; the way its currently written (mixingOnValidation
bindings with immediate event handlers) is probably not what you intendedINotifyPropertyChanged
on the model, otherwise you will have to deal with recursive refreshes