8

If I turn off automatic updating of a binding data source by setting DataSourceUpdateMode = Never and then use a button to update the whole lot (using binding.WriteValue), a problem occurs - Namely, only the first bound control's data source is updated. All other controls are reset back to the original values.

This is because when the current object changes (as happens after the above WriteValue), if ControlUpdateMode = OnPropertyChange, then all the other controls re-read in the value from the data source.

What is the standard way of avoiding this issue?

One way is to derive a class from BindingSource and add a WriteAllValues method. This method does the following:

(1) For each Binding, save the ControlUpdateMode

(2) For each Binding, set ControlUpdateMode = Never

(3) For each Binding, call the WriteValue Method

(4) For each Binding, reset ControlUpdateMode to the saved value

(5) For each Binding, if ControlUpdateMode = OnPropertyChange, call the ReadValue method.

Can you see any issues with doing this?

If working with your own classes, would implementing IEditableObject resolve the issue?

In another control I'm working on, I implement my own binding. The way I get around the issue in that is with the following code. (I've put in the bare minimum, I hope you can follow it!):

Private Shared ControlDoingExplicitUpdate As MyCustomControl = Nothing

Private Sub UpdateDataSourceFromControl(ByVal item As Object, ByVal propertyName As String, ByVal value As Object)
  Dim p As PropertyDescriptor = Me.props(propertyName)
  Try
    ControlDoingExplicitUpdate = Me
    p.SetValue(item, value)
  Catch ex As Exception
    Throw
  Finally
    ControlDoingExplicitUpdate = Nothing
  End Try
End Sub

Private Sub DataBindingSource_CurrentItemChanged(ByVal sender As Object, ByVal e As System.EventArgs)
  If (ControlDoingExplicitUpdate IsNot Nothing) AndAlso (ControlDoingExplicitUpdate IsNot Me) Then Exit Sub
  Me.UpdateControlFromDataSource() 'Uses ReadValue
End Sub

So, when UpdateDataSourceFromControl is called, all the CurrentItemChanged events will be called for all other controls in the same BindingSource. However, because ControlDoingExplicitUpdate is set, they will not re-read in the value from the data source unless they happen to be the control that did the updating. ControlDoingExplicitUpdate is set to Nothing after all these events have completed, so that normal service resumes.

I hope you can follow this, and - again - I ask, can you see any issues with this?

Victor Zakharov
  • 25,801
  • 18
  • 85
  • 151
Jules
  • 4,319
  • 3
  • 44
  • 72

3 Answers3

5

I have had similar requirements for a form. In my case I only wanted the databinding for all the form's controls to occur when I clicked the form's Save button.

The best solution I found was to set each binding's DataSourceUpdateMode to OnValidation then set the containing form's AutoValidate property to Disable. This prevents binding as you change focus between controls on the form. Then in the Click event for my Save button, I manually validate my form's input and, if it is OK, call the form's ValidateChildren method to trigger the binding.

This method also has the advantage of giving you full control over how you validate your input. WinForms do not include a good way to do this by default.

Victor Zakharov
  • 25,801
  • 18
  • 85
  • 151
achandlerwhite
  • 191
  • 1
  • 5
  • 1
    This method does not fix the problem. When the form's ValidateChildren method is called, still, there is only one control's value is updated. All other controls are reset back to the original values. I tested myself. That is because the same reason as the above question mentioned "when the current object changes, if ControlUpdateMode = OnPropertyChange, then all the other controls re-read in the value from the data source." – Luo Jiong Hui Oct 30 '19 at 13:07
2

I believe I recently read on stackoverflow where this was given as an answer: Disable Two Way Databinding

public static class DataBindingUtils
{
    public static void SuspendTwoWayBinding( BindingManagerBase bindingManager )
    {
        if( bindingManager == null )
        {
           throw new ArgumentNullException ("bindingManager");
        }

        foreach( Binding b in bindingManager.Bindings )
        {
            b.DataSourceUpdateMode = DataSourceUpdateMode.Never;
        }
    }

    public static void UpdateDataBoundObject( BindingManagerBase bindingManager )
    {
        if( bindingManager == null )
        {
           throw new ArgumentNullException ("bindingManager");
        }

        foreach( Binding b in bindingManager.Bindings )
        {
            b.WriteValue ();
        }
    }
}
JoelC
  • 3,664
  • 9
  • 33
  • 38
Ken
  • 2,518
  • 2
  • 27
  • 35
  • This is even worse than the question provided solution. Even if you want to disable 2 way binding. You have to disable ControlUpdateMode, not just DataSourceUpdateMode. And I do not like they way of handling this problem. Because it is fighting with data binding. And data binding supposed to make our job easier. – Luo Jiong Hui Oct 28 '19 at 19:07
  • @LuoJiongHui If Databinding is supposed ot make the Job easier than why does the op have an issue. Obviously it isn't making the job easier - so the op has to create a solution to overcome the fact that 2-way bonding did not make his job easier. BTW: Under the hood of 2 way binding is more code - code created to overcome a problem. There is all kinds of code out there to overcome the issues that the framework does not have, include or function correctly. However a person chooses to get around those issues is up to them. – Ken Oct 30 '19 at 03:59
0

I would suggest not to fight with data binding. Keep DataSourceUpdateMode as OnValidation. Then choose one of the following 2 options.

1, Before change values in controls, make a backup current values. If user click Cancel button, copy backed up values back to the data source. And run userBindingSource.ResetCurrentItem() to refresh values in controls to original values.

2, If user click Cancel button, pull a new object by ID from database. And use the new object to replace the current object in binding source. The code could be something like this:

MyObject myObject = myObjectBindingSource.Current as MyObject;
MyObject originalObject = myObjectRepository.GetOneObjectFromDatabase(myObject.ID);
int currentIndex = myObjectBindingSource.Position;
myObjectBindingSource.Insert(currentIndex, originalObject);
myObjectBindingSource.Position = currentIndex;
myObjectBindingSource.RemoveAt(currentIndex+1);

I personally like the second option since it avoids the work of copying the object, it may require copy each properties and make maintenance harder if the properties changed in the future. It also pulls the latest data , that makes the record very fresh. Also, the first option only saves 1 copy of the record. If you navigate to other records and make other changes, the copy is lost. However, the second option pulls data from database, so you can always navigate back to any previously modified but have not saved record and click the cancel button to bring back the original value.

Luo Jiong Hui
  • 5,527
  • 2
  • 23
  • 18