BindingSource producing excessive ListChanged (Reset) events

BindingSource producing excessive ListChanged (Reset) events

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


tetranz posted on Tuesday, July 25, 2006

Hi all

I've noticed something a little disconcerting about the behavior of multiple connected bindingSources.

Consider a typical parent / child collection bindingSource arrangement like projectBindingSource and resourcesBindingSource on the ProjectEdit control in ProjectTracker. Any time a property changes on the parent object, the PropertyChanged event hitting the parent bindingSource causes the collection bindingSource to send the grid a ListChanged event of type Reset. I think this means that any editing of the parent, probably unrelated to the collection causes the grid to reload all its data.

I guess its following the general of rule "one property changes so refresh them all" but it seems a little excessive and something that should perhaps be controllable with bindingSource properties. Perhaps this way of cascading bindingSources is not the way to go. Connecting the collection bindingSource directly to the collection rather than via the parent bindingSource seems to work but I'm not sure what other side effects that might cause. Cascaded bindingSources is the way Visual Studio generates code with drag and drop.

I only discovered this when debugging a problem with the Janus GridEx connected to CSLA collections. If I allow the grid to AddNew then when the user adds a row to the grid, the collection correctly fires a ListChanged (ItemAdded) event which is hooked by my parent object which fires PropertyChanged which sends ListChanged (Reset) back to the grid which crashes the grid. The Janus people are looking at that for me. They're pretty quick at fixing things. Unfortunately it usually fires an ItemChanged in addition to ItemAdded so we actually get two Resets when someone adds to the grid. I haven't yet tried kludgy ways of disabling events when the user starts to add a new row etc but something like that might be possible athough rather messy.

Does anyone have any thoughts on this?

Cheers
Ross

RockfordLhotka replied on Tuesday, July 25, 2006

I'm not following that last bit. Your root object raises PropertyChanged - I get that. But who does the reset on the list? Data binding?

tetranz replied on Tuesday, July 25, 2006

RockfordLhotka:
I'm not following that last bit. Your root object raises PropertyChanged - I get that. But who does the reset on the list? Data binding?
Yes. If you look at the ListChanged events of the collection bindingSource, it sends resets whenever the parent bindingSource gets a
PropertyChanged. I assume the grid sees the bindingSource ListChange.

Ross

RockfordLhotka replied on Tuesday, July 25, 2006

So is the conclusion here that the parent object should NOT raise PropertyChanged as a result of a child collection's ListChanged?
 
Rocky

tetranz replied on Tuesday, July 25, 2006

Maybe, but I'm using that to enable / disable buttons for saving etc. Perhaps I need to find a different method for that.

Its not just events that bubble up from the child collection. Any property change on the parent sends a reset to the grid. I guess that's consistent with refreshing other simple properties although most of the time it seems unnecessary.

Ross

RockfordLhotka replied on Tuesday, July 25, 2006

Yeah, I've been doing some work in this area based on some (unfortunately incorrect) info from a friend at Microsoft... I'm really starting to think the only right answer for this (handing IsValid/IsDirty/IsSavable/etc) is to handle the bindingsource control's CurrentItemChanged event. And given our discussion here, perhaps this really means routing all the CurrentItemChanged events from all bindingsource controls to a single handler that updates the display for these properties.
 
Rocky
Maybe, but I'm using that to enable / disable buttons for saving etc. Perhaps I need to find a different method for that.
 

tetranz replied on Tuesday, July 25, 2006

RockfordLhotka:
I'm really starting to think the only right answer for this (handing IsValid/IsDirty/IsSavable/etc) is to handle the bindingsource control's CurrentItemChanged event. And given our discussion here, perhaps this really means routing all the CurrentItemChanged events from all bindingsource controls to a single handler that updates the display for these properties.
Thanks Rocky. That's food for thought. I guess what you're saying in relates to:
http://http://www.lhotka.net/Article.aspx?area=4&id=5faaee8e-8496-4845-86f7-787c6b64096c
(BTW, the URL insert thingy doesn't work in FireFox)

I'm using the "Intermediate Object" method which seemed like a cool idea at the time Smile [:)] but I'll have a play with the other method. As far as I can remember, the only reason I usually raise PropertyChanged on the parent when ListChanged occurs on the collection is to control those buttons via databinding so if I eliminate that, I'll at least avoid a reset event to the grid when adding a row. It also avoids the need to rehook on deserialization etc because I don't usually need to hook the event at all then.
 
Putting aside the issue of those buttons, what do you think in general about the fact that a property change on the parent causes a ListChanged (Reset) on the collection? It seems like that would create a lot of activity on a form that has a header area bound to an object and a detail grid (or two) bound to child collections.
 
Cheers
Ross

RockfordLhotka replied on Tuesday, July 25, 2006

Yes, that is the article I'm thinking about - and I'm researching specifically because I don't think that article gives the best advice at the moment... I discuss the CurrentItemChanged option, but don't recommend it - and I think I'll be changing that article in the near future...

Regarding a PropertyChanged resetting the list - what must be actually happening is that the PropertyChanged at the parent level tells data binding to refresh the controls. As part of that process it must tell child bindingsource objects to refresh as well, and that in turn must somehow be triggering the reset of the collection.

I can easily think of a scenario where this is required: order entry. Suppose the tax rate or discount rate are on the Order (parent) and you change it. All the items in the LineItems collection would need to recalculate and be redisplayed - and I'm sure data binding is trying to help you make sure this happens.

tetranz replied on Wednesday, July 26, 2006

At the risk of repeating myself, my experience since yesterday makes me feel the need to say to all:

Propagating child collection ListChanged events up to ProperyChanged on a parent should be avoided if possible, especially if you're using a winforms grid with direct editing. I was really only doing it to make the buttons work nicely when IsDirty etc changed. It also just seemed a reasonable thing to do since IsDirty is a property used for data binding so lets do the standard thing to inform the UI when it changes.

As well as highlighting some bugs with the Janus grid, I was unaware that this was causing some of my performance hassles with deletes and cancels. Doing something on the grid caused PropertyChanged on the parent which sent ListChanged (Reset) back to the grid via the bindingSources. Sometimes I'd get multiple resets depending on how the grid worked.

Eliminating that propagation of events has made everything SO much smoother. Smile [:)] and I've eliminated some hacks which attempted to make the deletes and cancel work better.

I now use Rocky's other recommended way of controlling the buttons and it works absolutely fine. Since I name them the same on each form, I made a helper function to make it really simple to call from all bindingSource CurrentItemChanged events.

Cheers
Ross

public static void SetActionButtons(Csla.Core.BusinessBase bo, Form form)
{
    SetActionButtons(bo, (Button) form.Controls["btnOK"], (Button)form.Controls["btnApply"],
    (Button)form.Controls["btnCancel"], (Button) form.Controls["btnDelete"]);
}

public static void SetActionButtons(Csla.Core.BusinessBase bo, Button btnOK, Button btnApply, Button btnCancel, Button btnDelete)
{
    if (btnOK != null)
    {
        btnOK.Enabled = bo.IsSavable;
    }

    if (btnApply != null)
    {
        btnApply.Enabled = bo.IsSavable;
    }

    if (btnCancel != null)
    {
        btnCancel.Enabled = bo.IsDirty;
    }

    if (btnDelete != null)
    {
        btnDelete.Visible = !bo.IsNew;
    }
}

dmorton replied on Friday, January 26, 2007

I encountered this as well, while trying to build a Windows.Forms repeater control. After struggling through the hell of trying to get nested datasources to work, I then find the same thing as you: a PropertyChanged at the top level causes ListChanged events to be fired off to all the sub-bindingsources.

The solution, for me, was for my Repeater control, when it recieves a ListChanged message, to take a look at the list and see if its really has changed. If it hasnt, the message is ignored.

Copyright (c) Marimer LLC