CSLA .NET

Vibrant discussion about CSLA .NET and using the framework to build great business applications.

Forum has moved

New location: CSLA .NET forum


CSLA .NET Resources:
  • CSLA .NET forum
  • CSLA .NET home page
  • More Windows DataBinding Woes

    rated by 0 users
    Answered (Not Verified) This post has 0 verified answers | 22 Replies | 3 Followers

    Top 150 Contributor
    62 Posts
    NickTaylor posted on Thu, Sep 9 2010 5:47 AM

    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

    All Replies

    Top 10 Contributor
    2,279 Posts

    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:

    • Get your root object instance
    • Call root.BeginEdit()
    • "bind" BO's to BindingSources

    2. Save semantics:

    • "Unbind" BO from bindingsources 
    • Call root.ApplyEdit()
    • Call root.Save()

    3. Undo semantics

    • "Unbind" BO from bindingsources 
    • Call root.CancelEdit()
    • repeat step1 if user shall stay in form and start new edit session

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

     

     

    Jonny Bekkum, Norway CslaContrib Coordinator

    Top 150 Contributor
    62 Posts

    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

     

    Top 10 Contributor
    9,475 Posts

    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.

    Rocky

    Top 150 Contributor
    62 Posts

    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.  

    Top 150 Contributor
    62 Posts

    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

    Top 10 Contributor
    2,279 Posts
    Suggested by JonnyBee

    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:

    • namevaluelists (for comboboxes)
    • root object
    • child list
    • grandchild list.

    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);
            }
        }

     

     

     

    Jonny Bekkum, Norway CslaContrib Coordinator

    Top 150 Contributor
    62 Posts

    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!

    Top 150 Contributor
    62 Posts

    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

    Top 10 Contributor
    2,279 Posts

    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.

    Jonny Bekkum, Norway CslaContrib Coordinator

    Top 150 Contributor
    62 Posts

    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

     

    Top 10 Contributor
    2,279 Posts

    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:

    • new child added with EditLevel = 0 (EditLevel should normally be rest to correct level by Csla when new items are added to list)
    • bind/unbind not following proper sequence (doesn't seem to be the cause here)

    Jonny Bekkum, Norway CslaContrib Coordinator

    Top 150 Contributor
    62 Posts

    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

     

    Top 10 Contributor
    2,279 Posts

    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.

    Jonny Bekkum, Norway CslaContrib Coordinator

    Not Ranked
    13 Posts

    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

    Page 1 of 2 (23 items) 1 2 Next > | RSS

    Copyright (c) 2006-2014 Marimer LLC. All rights reserved.
    Email admin@lhotka.net for support.
    Powered by Community Server (Non-Commercial Edition), by Telligent Systems