Forms DataBinding – The magic sequence of BindUI/UnbindUI

If you are developing Windows Forms apps and using Csla you may get errors like “EditLevelMismatch in CopyState” or se that data values are overwritten when the dataobject is connected to the UI, diconnected from the UI or when the Form is disposed. And often/typically this happends when ComboBoxes are used on the form.

The reasion this happends is because you are making logical errors in your code with regards to DataBinding.

Csla 3.6 introduces the CslaActionExtender and additional helper classes to automate BindUI and UnbindUI but there are times when you really need to do this manually and this article will show you how to this in a proper way. The CslaActionExtender is covered in Rockys book “Expert C# 2008 Business Objects” so I will not cover here.

Whenever it is necessary to handle Bund/Unbind manually I always recommend to create the following methods in your form:

private void BindUI()
{
    // Bind ComboBox list datasources first 
    BindingHelper.RebindBindingSource(customerTypeNameValueListBindingSource,
                                        CustomerTypeNameValueList.GetNameValueList());

    // Bind dataobjects - starting with root object, child, grandchild and so on....
    BindingHelper.RebindBindingSource(testRootBindingSource, MyRoot);
}

private void UnbindUI(bool cancel)
{
    // Unbind in the opposite sequence of BindUI
    BindingHelper.UnbindBindingSource(testRootBindingSource, cancel, true);
    BindingHelper.UnbindBindingSource(customerTypeNameValueListBindingSource, false, false);
}

The correct sequence for BindUI is:

  1. First always bind the datasorces for lists used in ComboBoxes
  2. Next bind the root object
  3. If needed bind the child and grand child objects in that order

The correct sequence for Unbind is the reverse sequence of BindUI.

Why is the sequence so important? When databinding is active on one of the properties SelectedValue, SelectedItem or Text on a ComboBox the value vill be written to the underlying boound property when the prtoperty value is changed. This will happen if the list items does not exist or is removed whild databinding is active. So the key is to make sure that list items exists before the dataobject is connected to BindingSource (bound) and list items are removed only after the dataobject has been disconnected (unbound).

The last pitfall is if you (the developer) does not disconnect dataobjects from the UI when the form is closed. The sequence og disposing BindingSource will be undetermined and may or may not cause the same problems. To fix this you should always make sure to call UnbindUI in the FormClosed event.

    private void MyForm_FormClosed(object sender, FormClosedEventArgs e)
    {
      UnbindUI(true);
    }

The sematics for Save operation is as follows:

  private void SaveMyRoot()
  {
    // assumes dataobject is valid for save, should thest for IsSavable

    UnbindUI(false);
    try
    {
      MyRoot = MyRoot.Save();
    }
    catch (DataPortalException ex)
    {
      // Handle exception the way you waant
    }
    finally
    {
      BindUI();
    }

If you download the code from CslaContrib you will find a small helper class in MyCsla.Windows.BindingHelper that helps you to do a correct Bind and Unbind:

public static class BindingHelper
{

	/// 
	/// Unbinds the binding source and the Data object. Use this Method to safely disconnect the data object from a BindingSource before saving data.
	/// 
	/// The source.
	/// if set to true then call CancelEdit else call EndEdit.
	/// if set to true this BindingSource contains the Root object. Set to false for nested BindingSources
	public static void UnbindBindingSource(BindingSource source, bool cancel, bool isRoot)
	{
		IEditableObject current = null;
		// position may be -1 if bindigsource is already unbound which results in Exception when trying to address current
		if ((source.DataSource != null) && (source.Position > -1)) {
			current = source.Current as IEditableObject;
		}

		// set Raise list changed to True
		source.RaiseListChangedEvents = false;
		// tell currency manager to suspend binding
		source.SuspendBinding();

		if (isRoot) source.DataSource = null;
		if (current == null) return;

		if (cancel)
		{
			current.CancelEdit();
		}
		else
		{
			current.EndEdit();
		}
	}

	/// 
	/// Rebinds the binding source.
	/// 
	/// The source.
	/// The data.
	public static void RebindBindingSource(BindingSource source, object data)
	{
		RebindBindingSource(source, data, false);
	}


	/// 
	/// Rebinds the binding source.
	/// 
	/// The source.
	/// The data.
	/// if set to true then metadata (ovject/list type) was changed.
	public static void RebindBindingSource(BindingSource source, object data, bool metadataChanged)
	{
		if (data != null)
		{
			source.DataSource = data;
		}

		// set Raise list changed to True
		source.RaiseListChangedEvents = true;
		// tell currency manager to resume binding 
		source.ResumeBinding();
		// Notify UI controls that the dataobject/list was reset - and if metadata was changed 
		source.ResetBindings(metadataChanged);
	}
}

Advertisements

9 thoughts on “Forms DataBinding – The magic sequence of BindUI/UnbindUI

  1. Abbas Malik

    Hello
    I have a EditableRootList of Users. Each user has a child list of UserGroups.
    I have tried the above technique. It works first time for a row. But DOES NOT work second time on the same row.
    My code is something like this:
    BindingHelper.UnbindBindingSource(bsUser, true, true);
    BindingHelper.UnbindBindingSource(bsUserGroup, true, false);
    _allUsers.CancelEdit();
    BindingHelper.RebindBindingSource(bsUser, _allUsers);
    BindingHelper.RebindBindingSource(bsUserGroup, bsUser);

    I am quite frustrated for trying it for last 2-3 days.

    Reply
    1. jonnybekkum Post author

      Before you rebind you should call _allUsers.BeginEdit to create a snapshot for a possible next _allUsers.CancelEdit or _allUsers.ApplyEdit.

      Once you hve called _allUsers.CancelEdit the snaphot is gone.

      Reply
      1. Abbas Malik

        _allUsers.BeginEdit() gives an error “Edit level mismatch in CopyState”
        Please note I am also calling BindUI() in form load event.

        private void BindUI()
        {
        _allUsers.BeginEdit();
        this.bsUser.DataSource = _allUsers;
        }

        public void Cancel()
        {
        if (hasChanges())
        {
        BindingHelper.UnbindBindingSource(bsUser, true, true);
        BindingHelper.UnbindBindingSource(bsUserGroup, true, false);
        _allUsers.CancelEdit();
        _allUsers.BeginEdit(); // Edit level mismatch in CopyState
        BindingHelper.RebindBindingSource(bsUser, _allUsers, true);
        BindingHelper.RebindBindingSource(bsUserGroup, bsUser, true);
        }
        return;
        }

  2. jonnybekkum Post author

    You must also unbind in the reverse sequence of Bind. In our cancel method you do the same sequece. This may be why you get the EditLevel mismatch exception.

    Reply
  3. Abbas Malik

    Also tried that but there is no way to get rid of Edit Level error.

    BindingHelper.UnbindBindingSource(bsUserGroup, true, false);
    BindingHelper.UnbindBindingSource(bsUser, true, true);
    _allUsers.CancelEdit();
    bool dirty = _allUsers.IsDirty;
    _allUsers.BeginEdit(); // Edit level mismatch in CopyState
    BindingHelper.RebindBindingSource(bsUser, _allUsers, true);
    BindingHelper.RebindBindingSource(bsUserGroup, bsUser, true);

    Reply
    1. jonnybekkum Post author

      In that case you need to investigate which object is out of sync on EditLevel as this indicates that an object may still be databound.

      Reply
  4. Abbas Malik

    Thanks Jonny! It has started working after I removed an extra bindingsource from the form which was not being taken care of.
    BUT it works only if I remain on the same row in the devex grid. If I move to another row, edit it and then Cancel, it does not work. Same Edit level mismatch error appears.

    Reply
  5. Abbas Malik

    No, it is not a grid issue. I have removed the grid but issue is still the same.
    If I issue CancelEdit() and BeginEdit() on current record, it works expectedly. But when I call these on _allUsers, it gives the EditLevel mismatch error. Please see the following code. It works.

    BindingHelper.UnbindBindingSource(bsUserGroup, true, false);
    BindingHelper.UnbindBindingSource(bsUser, true, true);
    //_allUsers.CancelEdit();
    //_allUsers.BeginEdit();
    _currentUser.CancelEdit();
    _currentUser.BeginEdit();
    BindingHelper.RebindBindingSource(bsUser, _allUsers, true);
    BindingHelper.RebindBindingSource(bsUserGroup, bsUser, true);

    Thanks & best regards.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s