More Windows DataBinding Woes

More Windows DataBinding Woes

Old forum URL: forums.lhotka.net/forums/t/9485.aspx


NickTaylor posted on Thursday, September 09, 2010

Can anyone tell me where I am going wrong please...?

My problem relates to WinForms databinding and its relationship with a CSLA business object. In fact its CSLA business objects that in turn contain child collections that are really causing me trouble.

I fully understand about the need to disconnect the BO from the UI at the point I wish to cancel or revert changes. There are lots of examples in Rocky's book to demonstrate this. However to make for a pleasing user experience I need to invoke the following functionality.

1). Load the appropriate business object ( and its child collections ). The form obviously contains parent and child bindingsources.

2). Begin the edit.

3). Let the user make as may changes as they like to the BO and any of its child members.

4). Allow the user to save or revert ALL the changes made since the start of the edit session on the current BO. ( so I only want to be able to save/rollback at the parent level )

The only way that I can make this scenario work is to set DisableIEditableObject to true on both the parent and the child business objects. That way I have complete control over when BeginEdit(), ApplyEdit(), or CancelEdit() are called. If I dont do this for some reason the child edit levels get out of sync with the parent ( caused during child navigation via a datagrid no doubt ), and when I call CancelEdit() on the parent, it throws an exception.

I know Rocky says that setting DisableIEditableObject is the wrong road to take and he is ( as always ) right, as some of my bound windows components don't seem to update quite properly with this setting enabled. Surely I dont have to start drilling down into the children and calling CancelEdit() on each one of them do I ?

So, how do I control a save / undo process at the parent level only ?

Sorry if this is going over old ground, but I find windows databinding really frustrating! Crying

Thanks,

Nick

JonnyBee replied on Thursday, September 09, 2010

Hi,

When a business object/list is bound to a BindingSource the BindongSource (and DataBinding) assumes it has full control over the objects.

What this means is:

1. Startup semantics:

2. Save semantics:

3. Undo semantics

YOU MUST NEVER CALL BeginEdit, ApplyEdit or CancelEdit on an object that is bound to a BindingSource.,

 

 

NickTaylor replied on Thursday, September 09, 2010

I totally agree JonnyBee!

The business object is cloned and the bindingsource datasource set to null etc. before I attempt to do anything to the business object itself...

Sorry for not making this clear.

Nick

 

RockfordLhotka replied on Thursday, September 09, 2010

Jonny is right, but it sounds like you've already dug into the Using CSLA .NET 3.0 ebook in some depth and probably have the basic concept.

If you want a top-level Cancel button, you need to manually call BeginEdit on the root object before you bind it to the UI. And then you need to unbind the entire graph from the UI and call either CancelEdit or ApplyEdit on the root to cancel or before saving.

N-level undo will manage the canceling of all changes to the entire object graph - but not automatically. When data binding calls these methods they act differently (specifically to emulate the way a DataSet/DataTable work). So there really is no "n-level" undo with data binding, because data binding only supports one level.

But if you manually call BeginEdit/CancelEdit/AppyEdit on the root (making sure to never do this to a bound object graph) then you actually get n-level undo.

NickTaylor replied on Thursday, September 09, 2010

Yes Rocky, I have done a lot of reading on the subject, but I still find it confusing!

I do indeed call BeginEdit() on the root before it is bound to the UI. I also unbind the root before I Cancel or Apply the edit.

Because I have a child grid bound to the child bindingsource ( as well as the user editable text boxes ), I assume that windows databinding calls BeginEdit() and ApplyEdit() etc on the children automatically because its designed to commit data as the active row changes.

What I am still slightly confused about is why the edit level between the parent and children is out of sync, and I get the error. It appears that my approach is correct, so I should not get the error.

If I manually call BeginEdit() before I bind, I dont care what windows binding does in between, at the point I unbind and then call CancelEdit(), I should be able to get back to where I started.  

NickTaylor replied on Thursday, September 09, 2010

Rocky,

All your examples populate a bindingsource with one business object at a time ( other than child collections of course ). If I wanted to use a bindingsource to hold multiple root objects ( say a list of customers ) and then allow a user to edit those one at a time, is this possible ?

In other words if I load a bindingsource with a bunch of customers ( so as to provide a nice easy way of navigating around ) by just adding them one at a time, could I do something like the following when the user navigates to a new customer in the list:-

int position = bindingSourceCustomers.Position;

bindingSourceCustomers.RaiseListChangedEvents = false;

BoCustomer currentCustomer = (BoCustomer)bindingSourceCustomers[position];

bindingSourceCustomers[position] = null;  // Unbind

currentCustomer.BeginEdit(); // Start the edit

bindingSourceCustomers[position] = currentCustomer; // Rebind the UI

bindingSourceCustomers.RaiseListChangedEvents = true;

bindingSourceCustomers.ResetCurrentItem();

Sorry if that sounds mad, but what I am trying to do is have a navigation ( i.e. a bound treeview ) that keeps up to date with the changes being made to customer data. By trying to work with one list only this becomes much easier. I dont feel the need for a dedicated CSLA list as this seems overkill.

If this is a bad idea I'll not take it any further...!

Thanks,

Nick

JonnyBee replied on Thursday, September 09, 2010

Hi Nick,

No - this is not the proper way to handle bind/unbind or interact with a list.

How do you handle bind/unbind of BindingSources?

Is your problem that you get exception with EditLevel mismatch?

Typical problem is that you must unbind in the opposite order of bind and Bind should keep this order:

I use the following helper extension methods in my apps and very rarely have problems with editlevel mismatch.

    public static class BindingSourceExtensions
    {

        /// <summary>
        /// Unbinds the binding source and the Data object. Use this Method to safely disconnect the data object from a BindingSource before saving data.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="cancel">if set to <c>true</c> then call CancelEdit else call EndEdit.</param>
        /// <param name="isRoot">if set to <c>true</c> this BindingSource contains the Root object. Set to <c>false</c> for nested BindingSources</param>
        public static void Unbind(this 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 == nullreturn;

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

        /// <summary>
        /// Rebinds the binding source.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="data">The data.</param>
        public static void Rebind(this BindingSource source, object data)
        {
            source.Rebind(data, false);
        }


        /// <summary>
        /// Rebinds the binding source.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="data">The data.</param>
        /// <param name="metadataChanged">if set to <c>true</c> then metadata (object/list type) was changed.</param>
        public static void Rebind(this 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);
        }
    }

 

 

 

NickTaylor replied on Thursday, September 09, 2010

Hmmm, you may be on to something there I think...!

Yes, I'm guilty as charged in that its an EditLevel mismatch exception...!

Fortunately my list example was only hypothetical, BUT, you may well be right regarding the order in which my unbind was happening. I will investigate and report back.

Out of interest do you think that a bindingsource can be used as a suitable container to hold a list of BOroot objects ?

Thanks!

NickTaylor replied on Thursday, September 09, 2010

JonnyBee, I'm sure we have moved a step closer, but its stil complaining of an "Edit level mismatch in UndoChanges" error.

This is the code I am using to populate the bindingsource:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

// stop the flow of events

 

 

 

this.bindingSourceActivity.RaiseListChangedEvents = false;

 

 

this.bindingSourceExpenses.RaiseListChangedEvents = false;

 

 

// Grab the object

_activity = BoCustomerCRMActivity.GetByCustomerCRMActivityID(1);

 

 

// Begin the edit

_activity.BeginEdit();

 

 

 

// Rebind the UI

 

 

 

this.bindingSourceActivity.DataSource = null;

 

 

this.bindingSourceActivity.DataSource = _activity;

 

 

this.bindingSourceExpenses.DataSource = this.bindingSourceActivity;

 

 

this.bindingSourceActivity.RaiseListChangedEvents = true;

 

 

this.bindingSourceExpenses.RaiseListChangedEvents = true;

 

 

this.bindingSourceActivity.ResetBindings(false);

 

 

this.bindingSourceExpenses.ResetBindings(false);

In this case bindingSourceActivity is the parent, and bindingSourceExpenses in the child. As per earlier in the post I am beginning an edit before I bind the BO so that I can return here when the edit is over.

As for the cancel or apply method ( note that applyChanges is obviously a boolean parameter passed from outside the method ):-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

// stop the flow of events

 

 

this.bindingSourceActivity.RaiseListChangedEvents = false;

 

 

this.bindingSourceExpenses.RaiseListChangedEvents = false;

 

 

// Disconnect the UI

UnbindBindingSource(bindingSourceExpenses, applyChanges,

false);

UnbindBindingSource(bindingSourceActivity, applyChanges,

true);

 

 

if

(applyChanges)

_activity.ApplyEdit();

 

else

_activity.CancelEdit();

 

 

// Rebind the UI

 

 

this.bindingSourceActivity.DataSource = _activity;

 

 

this.bindingSourceExpenses.DataSource = this.bindingSourceActivity;

 

 

this.bindingSourceActivity.RaiseListChangedEvents = true;

 

 

this.bindingSourceExpenses.RaiseListChangedEvents = true;

 

 

this.bindingSourceActivity.ResetBindings(false);

 

 

this.bindingSourceExpenses.ResetBindings(false);

The code in bold is the code which generates the error. If I'm reading Rocky's reply correctly, this should work, but it doesn't.

Any thoughts ?

Nick

JonnyBee replied on Thursday, September 09, 2010

Hi Nick,

i'd like to know a little more about your program:

Which version of Csla are you using?

Do you add new items to the child/grandchild list?

You may also look at the RootChildGrandchildWinFormTest project in the samples folder. This project show an implementation of "DumpEditLevels" that you can implement in your objects and helps you to easier determinate which object(s) have Editlevel out of sync.

NickTaylor replied on Friday, September 10, 2010

Hi Jonny,

I currently run CSLA version 3.8.1.

I am also using Infragistics UI components such as grids and treeviews, and in this instance the child collection has both a grid and a treeview associated with its bindingsource so that the user can navigate around the collection.

Yes, the Child and GrandChild lists need to be editable by the user, so they are subject to having rows added and removed from the collections.

Forgive my ignorance, but where is the samples folder ?!

Cheers,

Nick

 

JonnyBee replied on Friday, September 10, 2010

Hi,

The samples is packaged in own corresponding downloads available on the download page: http://lhotka.net/cslanet/Download.aspx

I'd also recommend the DataBindingFAQ from WindowsClient.net if you are not already familiar with this document.

Is the grid and treeview connected to the same BindingSource?

I'd recommend to investigate which objects are out of sync on EditLevel after unbind.This  should give you some clue as to where EditLevel gets out of sync.

Some "classic" problems I have experienced:

NickTaylor replied on Friday, September 10, 2010

Great, got them now thanks...

As it happens, we also appear to have fixed my problem too. Smile

You were definitely correct in that I wasn't unbinding in the right sequence. But, looking at your prompt about the grid binding I realised that I had messed up as I had another grid on the form bound to the wrong bindingsource and I belive this was fouling up the edit levels. Now this has been corrected the problem has gone, and my parent level undo is working!

I presume the DumpEditLevels method is placed in the required BO and then simply called when required ?

For some reason, the line of code:-

gc.DumpEditLevels(sb);

Shows as an invalid method. Do you know if this was added after version 3.8.1 or am I missing the point ?

Cheers,

Nick

 

JonnyBee replied on Friday, September 10, 2010

Good to hear that you fixed the problem.

The DumpEditLevels may be implemented by your BOs (not a part of CSLA framework classes nor the GarbageCollector) and would most likely be used after you have done Unbind to discover which object(s) in your object graph are out of sync on EditLevel.

Not much point in calling this method when DataBinding is active as BeginEdit/EndEdit etc is called repeatedly by DataBinding.

Munish Sehgal replied on Sunday, October 31, 2010

Hi Jonny, Thanks for your help. this thread helped me to solve the "Edit Level mismatch error".

I am using the BindingSourceExtensions as told by you and it really worked to unbind and rebind safely.

But, there is another problem that it don't Mark Root as old after save()....When i call save for a New Root. It runs DataPortal_Insert (), and when i again make changes to object and call save, ite again runs dataportal_insert instead of DataPortal_Update....!!

Actually After Save, the Root object property IsNew not sets False..???

My Code is: 

 

#region Binding Methods

public void BindUI()

{

try

{

_Root.BeginEdit();

RootBindingSource.DataSource =_Root;

OnBinded();

}

catch (Exception ex)

{

ControlsGlobal.WriteToTrace(ex.GetBaseException().Message, true); ;

}

}

 

 

 

protected void RebindUI(bool saveObject, bool rebind)

{

try

{

// unbind the UI

BindingSourceExtensions.Unbind(this.RootLineBindingSource, !saveObject, false);

BindingSourceExtensions.Unbind(this.RootBindingSource, !saveObject, true);

this.RootLineBindingSource.DataSource = this.RootBindingSource;

// save or cancel changes

if (saveObject)

{

_Root.ApplyEdit();

try

{

if (_AddCharge.IsValid)

{

OnSaving();

_Root.Save();

ControlsGlobal.WriteToTrace("Record Saved.", true);

this.errorProvider1.BlinkStyle = ErrorBlinkStyle.NeverBlink;

OnSaved();

}

else

{

this.errorProvider1.BlinkStyle = ErrorBlinkStyle.AlwaysBlink;

}

}

catch (Exception ex)

{

ControlsGlobal.WriteToTrace(ex.GetBaseException().Message, true); ;

}

}

else

{

_Root.CancelEdit();

}

}

finally

{

// rebind UI if requested

if (rebind)

BindUI();

if (rebind)

{

// refresh the UI if rebinding

BindingSourceExtensions.Rebind(RootBindingSource,_Root, false);

BindingSourceExtensions.Rebind(RootLineBindingSource,RootBindingSource, false);

}

}

}

#endregion

JonnyBee replied on Sunday, October 31, 2010

Hi,

The Save method actually returns a new instance of your root object (and structure) as the result of  Save so you code should be:

_Root = _Root.Save();

That should fix it.

Munish Sehgal replied on Monday, November 01, 2010

Thanks Jonny. you solved my problem.....

jlfernandezmiranda replied on Thursday, November 04, 2010

Hi jonny, do you know why in RootChildGrandchildWinFormTest example this code is needed?

  void childrenBindingSource_CurrentChanged(object sender, EventArgs e)
    {
      this.grandchildrenBindingSource.EndEdit();
    }

how does it works?

Munish Sehgal replied on Tuesday, November 16, 2010

it solved the problem for ParentChild Relation....

But not for parentChildGrandchild Relation...

When i use

_Root = _Root.Save(); for oneParent with Multiple children it work fine.

but when i use it for one parent with multiple children further multiple grandchildren for children it dont marks the _Root.IsDirty to false....

JonnyBee replied on Tuesday, November 16, 2010

Then you should inspect your object graph to find which object(s) are Dirty

Could be some ValidationRule or Save code that sets a value and makes the object dirty.

Tatjana replied on Friday, July 19, 2013

Hello,

I am using the above  helper extension methods in my apps as well, but what I would like to know is if it is possible to cancel the entries without having to unbind and then bind datasources again.

I have something like this in my code

case actionList.Cancel:        

UnbindAll();

            try

            {

              if (_receipt.IsNew)

                _receipt = null;

              else

                _receipt = Receipt.GetEditableRoot(_receipt.Id);

            }

            finally

            {

              BindAll();

            } 

            break;

 

What I would like is to simply call _receipt.CancelEdit() and all entered data in the grids to be erased without having to rebind the datasources again.  I am running into performance issues when unbind/rebind datasources.

Thanks in advance,

Tanja

JonnyBee replied on Saturday, July 20, 2013

Hi,

You should think about this as your list may have edited items, removed items and added/new items.

So would it be faster to raise a LOT of changed events inline with the UI or do a restore without the UI? I'd say the last alternative is a LOT safer (and maybe even the first is impossible to get the sequence correct as items must be reordered as well).

So the fastest solution should be

 

 

So when you run into performance issues you should look at the events and handling in the UI and/or inspect the design of your form if there is a LOT of data retrieved that cause the app to have poor performance. 

Oh, I noticed that you get the object again in Cancel. I would suggest that you look at the N-level undo mechanism. See other posts here on the forum as well - as this will allow you to create a snapshot and later restore the snapshot in a cancel operation.

Tatjana replied on Wednesday, July 24, 2013

JonnyBee,

Thank you for your quick answer; I will do more research on the "N-level undo mechanism" and see if that will help me fix the issue I am dealing with.  

 

Copyright (c) Marimer LLC