Enabling / disabling save button

Enabling / disabling save button

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


ajj3085 posted on Tuesday, August 05, 2008

Hi,

So the recent change in 3.5.1 RC0 has broken some code.  Basically, because child events are not being "bubbled" anymore, my method of enabling / disabling the save button on a toolbar doesn't work. 

In Windows Forms, what I was doing was listening to the CurrentItemChanged event of the BS which is bound to the root object, then calling a method to approriately enable / disable various toolbar buttons.

In the short term, I fixed this by hooking my event handler to the ChildChanged event off the root object. 

What I would like to know is how this should be handled long term.  I'm using Infragistics toolbars, so I don't think databinding is an option.  So what is the recommended solution?

Thanks
Andy

RockfordLhotka replied on Tuesday, August 05, 2008

One thing leads to another doesn't it? :)

Well, the bubbling of events caused problems that are effectively unsolvable, so the bubbling can't occur like it did. That probably means a change to a child won't cause a CurrentItemChanged - in fact I think that was ultimately the root of the problem solved by not bubbling the event.

I can see two answers, though there are probably others.

One is to do as you suggest - handle the ChildChanged event to trigger your UI logic.

Another is to create an ObjectStatus control similar to the one in WPF, only for Windows Forms. Probably not a control though, since there's no control-to-control binding in Windows Forms - but perhaps just an object that elevates the Is___ properties to a bindable level in the UI.

Of course now that you bring this up, I bet the ObjectStatus control is broken now too, because it relied on the same mechanism :-\

ajj3085 replied on Wednesday, August 06, 2008

RockfordLhotka:
One thing leads to another doesn't it? :)


Yes, change is wonderful.

RockfordLhotka:
Well, the bubbling of events caused problems that are effectively unsolvable, so the bubbling can't occur like it did. That probably means a change to a child won't cause a CurrentItemChanged - in fact I think that was ultimately the root of the problem solved by not bubbling the event.

What were the issues?  Not that I'm advocating going back to the broken behavior, I think I just missed that discussion.  My post was more along the lines of "what's best practice now?"

RockfordLhotka:
I can see two answers, though there are probably others.

One is to do as you suggest - handle the ChildChanged event to trigger your UI logic.

Good, at least my solution made the list.. so I can't be too far off.  Smile [:)]

RockfordLhotka:
Another is to create an ObjectStatus control similar to the one in WPF, only for Windows Forms. Probably not a control though, since there's no control-to-control binding in Windows Forms - but perhaps just an object that elevates the Is___ properties to a bindable level in the UI.

So this would likely be a component?  I'm not sure I'll go this route, simply because I don't think there's a way to use data binding for Tools in an UltraToolBarManager, which is the only object that actually exists.  But perhaps I could make a component similar to the BindingSourceRefresh that raises an event when anything in the object graph changes.  I'll look into that.

Thanks!
Andy

RockfordLhotka replied on Wednesday, August 06, 2008

The issue with bubbling PropertyChanged is that when a new item was added to a grid it was instantly “committed” in memory. Pressing ESC wouldn’t get rid of it. It appears this is because the parent’s bindingsource did a quick currency change based on PropertyChanged being handled, thus effectively causing an EndEdit() on the child (which is not good).

 

Yes, I would think the ObjectStatus concept would be a Component. I haven’t given it any serious thought in terms of design, but I expect it would be similar to the ObjectStatus concept in Csla.Wpf.

 

Rocky

 

amselem replied on Wednesday, August 06, 2008

This change also broke my code. My UI now handles both PropertyChanged and ChildChanged off the root object for activating the Save button.

However, I have problems with grandchildren. I think the problem is that ChildChanged event itself won't bubble up the hierarchy, so I'm receiving events from root and child BO's but not from grandchildren.

Rocky, couldn't the childevent bubble up like 3.5.0, but inside another childevent instead of a propertychanged event?  I hope that makes sense.

Regards

 

RockfordLhotka replied on Thursday, August 07, 2008

This is really unfortunate, I’m sorry for not catching this…

 

Obviously some thought needs to go into this beyond the knee-jerk reactions I’ve done thus far.

 

Yes, I think you are right that the ChildChanged needs to bubble up. The question is how?

 

A child object’s Propertychanged bubbles through a parent list as a ListChanged. If the list has a parent that cascades as a ChildChanged. If that parent is also a child of a list we’re now in trouble.

 

So it seems that BLB may need to raise ChildChanged as well.

 

In other words, it seems like a total parallel eventing system is required along-side the PropertyChanged/ListChanged system.

 

And this can be done.

 

But then the question is how to best do this? Do lists and objects need different events? Should these events work like exceptions, so they have “inner events” all the way up the chain? That seems like it would be pretty cool – but useful?

 

Basically, if I’m going to do this (and I think I must), I’d rather do it in a well thought out manner.

 

Rocky

 

 

From: amselem [mailto:cslanet@lhotka.net]
Sent: Wednesday, August 06, 2008 10:02 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Enabling / disabling save button

 

This change also broke my code. My UI now handles both PropertyChanged and ChildChanged off the root object for activating the Save button.

However, I have problems with grandchildren. I think the problem is that ChildChanged event itself won't bubble up the hierarchy, so I'm receiving events from root and child BO's but not from grandchildren.

Rocky, couldn't the childevent bubble up like 3.5.0, but inside another childevent instead of a propertychanged event?  I hope that makes sense.

Regards

 



tetranz replied on Thursday, August 07, 2008

This might be a silly suggestion but I wonder how easy it would be to inherit from BindingSource, detect when the problem event is about to happen and "fix" the BindingSource.

ajj3085 replied on Thursday, August 07, 2008

Well, ChildChanged is useful as it is for me.. sometimes I do need to know which property changed on a child BO not in a list.

Maybe the answer is to create another new event, StatusChanged.  It could be raised whenever IsDirty, IsNew, etc. are raised.. since this is ultimately what amselem and I are after.  A reliable way to find out when IsSavable has changed, to enable the buttons. 

Andy

RockfordLhotka replied on Thursday, August 07, 2008

I don’t think it works as-is for you though. Because it won’t cascade up if a grandchild changes. In that case the grandchild’s PC becomes a LC from its parent list, which becomes a CC from the child object. The child’s parent list does nothing, and so neither does the parent. So you get no notification that a grandchild changed.

 

Rocky

 

 

From: ajj3085 [mailto:cslanet@lhotka.net]
Sent: Thursday, August 07, 2008 7:23 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: Enabling / disabling save button

 

Well, ChildChanged is useful as it is for me.. sometimes I do need to know which property changed on a child BO not in a list.

Maybe the answer is to create another new event, StatusChanged.  It could be raised whenever IsDirty, IsNew, etc. are raised.. since this is ultimately what tetranz and I are after.  A reliable way to find out when IsSavable has changed, to enable the buttons. 

Andy


ajj3085 replied on Thursday, August 07, 2008

Well I didn't mean working in regards to enabling / disabling buttons.. the other use I have for ChildChanged is running rules when a certain property on the child BO changes.  That is the part I think is fine.

RockfordLhotka replied on Thursday, August 07, 2008

Ahh, yes.

 

It should be fine for that purpose right now.

 

But to address the from-bottom-to-top event bubbling requirement I suspect I’ll have to do a ChildChanged event on BLB as well.

 

And I suspect having some sort of nested eventargs scheme would be good, but that requires more thought.

 

My idea there, though, is that each level that bubbles a ChildChanged would nest the original inside – so you could use some InnerArgs property to walk from the top level back through the stack of args to fine the original that was raised.

 

Or I could follow the current model, where the originator is in the args, and the sender is whoever raised the event last, and you have no access to the intermediate objects between the originator and the final sender.

 

Rocky

 

From: ajj3085 [mailto:cslanet@lhotka.net]
Sent: Thursday, August 07, 2008 12:10 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: RE: Enabling / disabling save button

 

Well I didn't mean working in regards to enabling / disabling buttons.. the other use I have for ChildChanged is running rules when a certain property on the child BO changes.  That is the part I think is fine.


tmg4340 replied on Thursday, August 07, 2008

My vote would be to keep the entire context chain.  It can make pinpointing your original object easier.

Having said that, I know that is not a trivial implementation.  I've written one before, and I've seen the code some commercial products that use it have implemented.  The question is how much code you want to write around this "context chain".  In one case, there are a number of methods implemented on the base classes that allow you to retrieve information from anywhere in the chain.  Basically, the methods ask for a target type, and they automatically walk the chain until they encounter the type requested or the top/bottom of the hierarchy.  I'm not suggesting you have to go to this level of work in your implementation, but it's certainly something to think about.

The thing is that the biggest arguments I can see for keeping the context chain are (a) being able to insert yourself anywhere in the event-bubbling process, and (b) providing an easier way to find the original object.  (a) can be useful, but probably in somewhat specialized situations - one that comes to mind now is the ability to provide more information to "tag along for the ride".  It could also be useful if you want to stop the bubbling for some reason, but I'm not sure of the value of that either.  (b) is probably only useful if you have a child object that can exist in more than one child list - not a completely uncommon situation, but probably not all that common either.

Lastly, consider that whatever you do for your child events probably should be duplicated in the PropertyChanged stack as well.  After all, why limit your flexibility to one set of events?

So - do you want to write a lot of code for potentally not a lot of benefit?  Smile [:)]

HTH

- Scott

RockfordLhotka replied on Friday, August 08, 2008

This is good thinking, I appreciate the analysis.

 

One thing though – is that PropertyChanged is what it is. That event and its behavior is defined by the expectations of data binding. We’re having this discussion because I didn’t follow their strictures quite right – so the idea of doing more complex bubbling/enhancing of that model is right out :)

 

Rocky

ajj3085 replied on Friday, August 08, 2008

I think the nesting is a good idea.  Perhaps you could have a ChildChangedEvent args, which has an InnerEventArgs.  That could be very powerful for those that need it.  I think it also is pretty close to how WPF event bubbling works as well... of course I've only been dabbling in WPF so far, so I could be wrong.  Smile [:)]

jkellywilkerson replied on Friday, August 08, 2008

We have n-level undo.  With InnerEventArgs, it looks like we now have n-level change notification.  I assume the args would contain the level that the change occurred on; how would that look, an int to signify the level or the actual object itself, or ...? (Just thinking out loud here...don't mind me)

Kelly.

RockfordLhotka replied on Friday, August 08, 2008

We have to remember performance too. There are a lot of these events raised in a typical app, and so generating them can’t be expensive or none of us will be happy. The more stuff we cram into the eventargs the more time it will take to create the object – and that’ll happen at each object in the parent-childlist-child-grandchildlist-grandchild level, so the overhead will multiply.

 

But more importantly, it is easier to add things later than to take things away. So I tend to only put things in because they fulfill a specific scenario that is commonly useful.

 

I’m not sure what the scenario would be for including the edit level of each object as the event bubbles up?

 

I can see the value in providing a reference to the originator of the event, and to the PropertyChanged or ListChanged event args of the underlying changed event on that object.

 

I’m still struggling a little with the value of having references to the intermediate objects up the stack. It “feels” useful, but I honestly don’t see where I’m going to use that information.

 

Rocky

tmg4340 replied on Friday, August 08, 2008

I can think of one possible use for having the whole object tree available, and it relates to being able to "insert" yourself into the call chain.  Admittedly, this is a somewhat contrived example, but it's possible.

(Mind you, "inserting yourself into the call chain" means that your events are triggered by overridable methods.  Not sure whether that was clear...)

Being able to manage the event chain means you can run validation rules pretty much anytime you want.  If this ChildChanged event stack (which maybe should get a different name) bubbles through the whole hierarchy, you can pick an arbitrary level in the chain to explicitly call CheckRules() - say, if a change to one child object requires a change to another child object, along with a new check of the business rules.  That kind of logic should probably reside in the parent collection.  Some of this is currently available, and some of it isn't.  You can even choose to run CheckRules() based on what object below the current level was changed.

So why would the intermediate objects be useful?  If you have validation rules on a collection, then presumably those rules are designed to work on the entire collection.  It may be useful to ignore the object(s) that are part of the update chain for purposes of the validation routine.

One other possible use goes back to stopping the bubbling.  I honestly can't think of a good scenario for this, but I could see some value in being able to evaluate changes as they move up the stack, and possibly killing the bubble at some point.  In killing the bubble, you may want to undo the changes currently moving through the stack.  You would need the appropriate objects available to do that.

- Scott

amselem replied on Tuesday, August 19, 2008

Rocky any updates on this one?

RockfordLhotka replied on Tuesday, August 19, 2008

I haven't had time to dig into the code, no.

I can (and might) do the simplest of the implementations in relatively short order. The more sophisticated options are...well...more sophisticated and would take a lot more time - time I don't have.

amselem replied on Tuesday, August 19, 2008

Thanks for your response Rocky, I understand that you're very busy with all the new stuff like the upcoming book and the Csla light fw.

As for the implementation I agree that the simplest implementation would be enough, it's not clear (at least for me) that the more complex option would be very useful.

Regards

Jon replied on Wednesday, February 04, 2009

>> A child object’s Propertychanged bubbles through a parent list as a ListChanged. If the list has a parent, that cascades as a ChildChanged. - Rocky


I have found this to be true if you’re modifying an existing Child in the BLB. But if you add a new Child (or delete a Child), then the ChildChanged event doesn't propagate up to the Parent of the BLB ... not directly anyway. If you are setting default values in your new Child object, then those changes propagate up to the Parent of the BLB as ChildChanged.PropertyChangedArgs.

As stated in other posts, this is easily overcome for now -> In a intermediary MyBLB class, override OnListChanged and if the change is ItemAdded or ItemDeleted then call OnChildChanged. This way the Parent of the BLB only has to worry about subscribing to the ChildChanged event to capture all changes.

Michael replied on Sunday, May 24, 2009

Just to be sure, for those of us not yet using the CslaActionExtender, to enable/disable save buttons we need to ditch the BindingSource CurrentItemChanged event in favour of PropertyChanged and ChildChanged. And because these events don't fire until something changes, we need to call SetButtonsEnabled() or something similar in BindUI(). Is this the recommended approach?

Regards
Michael

RockfordLhotka replied on Monday, May 25, 2009

If you have a deep object hierarchy/graph then this is probably your only option.

tetranz replied on Tuesday, May 26, 2009

Michael:
Just to be sure, for those of us not yet using the CslaActionExtender, to enable/disable save buttons we need to ditch the BindingSource CurrentItemChanged event in favour of PropertyChanged and ChildChanged. And because these events don't fire until something changes, we need to call SetButtonsEnabled() or something similar in BindUI(). Is this the recommended approach


I set my button state in the CurrentItemChanged event of both the parent and child (and grandchild) bindingSources. That seems to work fine. I have some general purpose code that works for all my forms because it assumes the names of the buttons. I don't know if this is worth the effort but I also have a boolean flag that lets me ignore the CurrentItemChanged events during binding and unbinding and any other operation that causes those events to happen an excessive number of times. For those I disable the automatic button updating, do the operation and then update the buttons once. It all works very smoothly.

Ross

ajj3085 replied on Tuesday, May 26, 2009

That sounds very similar to what I've done, except I never made the code generic enough to work in any form.

Michael replied on Tuesday, May 26, 2009

Thanks for your reply, Ross. Handling the CurrentItemChanged event for each binding source is by far the easiest solution. I can't believe I didn't think of it!

Michael

Vinodonly replied on Saturday, July 02, 2011

This is a old post but i think i'm facing the same problem what is mentioned here.

I'm using CSLA 3.8.3 with winforms. I have a generic code which enables / disables save button by subscribing to each bindingsource's CurrentItemChanged.

The problem is that for first record it works fine but when user clicks on Add Record for the second record and enters new record info then currentitemchanged is never triggerred.

This was previously working fine but seems like it has stopped working now.. Any ideas, any suggestionso n how to fix this.

Vinodonly replied on Sunday, July 03, 2011

I have done some more testing and now all of my forms have stopped working properly.. After the first record is added, once i add another record and start doing changes in any properties then currentitemchanged is never fired.. it is only working for the first record.. User has to close the form and open again for it to work.. can anybody help me on this..

Michael replied on Sunday, July 03, 2011

How are you adding the new record? Is it a child BusinessBase object in a BusinessListBase, or a standalone BusinessBase? Are you changing the BindingSource.DataSource or using the binding source to AddNew()?

Vinodonly replied on Monday, July 04, 2011

this is happenning only in standalone businessbase.. this is the code which i'm using in my add records..

 

 

 

 

 

_SrcBLInfo =

 

SrcBLInfo.NewSrcBLInfo(); // Creates a new Object

 

 

 

e.NewObject = _SrcBLInfo;

 

// setting new object to Newly created BO

_SrcBLCtrl.srcBLInfoBindingSource.DataSource = _SrcBLInfo;

 

// Setting Binding source to the New Object

SetBindingSources();

The method SetBindingSource at the end reassings all the child binding sources.. this code is given in this method

 

 

 

TempBindingSource =

 

new BindingSource();

 

 

 

// Save the Existing BindingSource in a Temp Variable and Reassign it to Rebind UI

TempBindingSource = (

 

BindingSource)((BindingSource)ChildBindingSources[BindCount]).DataSource;

((

 

BindingSource)ChildBindingSources[BindCount]).DataSource = TempBindingSource;

 

Michael replied on Monday, July 04, 2011

I don't think you should be creating a new BindingSource, you should just set the DataSource on the existing binding source. If you create a new BindingSource object, you need to subscribe to the CurrentItemChanged event again, because it's the old binding source object which was subscribed. This also means that old binding source object will still be hanging around and won't get cleaned up by the garbage collector.

You might like to have another read through the CSLA samples to make sure you're following the correct way to bind and rebind your UI.

Vinodonly replied on Monday, July 04, 2011

I just checked the project tracker sample but it is applying endedit etc. on the bo itself.. whereas i have read before that calling these methods can call problem..

is it possible for you to share portion of your code specially the part where u unbind and rebind the datasources..

 

Vinodonly replied on Monday, July 04, 2011

i just tried one more thing.. i have temporairly disabled the child binding source code..

 

even now it is not working.. i'm just doing changes in the root object but currentitemchanged is not firing on 2nd record.

Michael replied on Monday, July 04, 2011

// Hope this helps.

protected
 FormMxsProject() {     InitializeComponent();     // In the constructor we hook up the CurrentItemChanged event     // to the BO's child collections     bindServiceTypes.CurrentItemChanged += bindObject_CurrentItemChanged;     bindTemplates   .CurrentItemChanged += bindObject_CurrentItemChanged; }

private void BindUI() { // Sometimes we have binding sources for children which are bound to // another DataSource using the DataMember property. For this form, // that wasn't necessary, so we just explicitly set the DataSource // to the child collection.

    bindObject      .DataSource = BusinessObject = m_mxsProject;     bindServiceTypes.DataSource = m_mxsProject.ServiceTypes;     bindTemplates   .DataSource = m_mxsProject.FlexTemplates; } protected override bool RebindUI(bool saveObject, bool rebind) {     bool completed = false;     bindObject      .RaiseListChangedEvents = false;     bindServiceTypes.RaiseListChangedEvents = false;     bindTemplates   .RaiseListChangedEvents = false;     try     {         UnbindBindingSource(bindObject,       saveObject);         UnbindBindingSource(bindServiceTypes, saveObject);         UnbindBindingSource(bindServiceTypes, saveObject);         if (saveObject)         {             // We call ApplyEdit() directly on the BO             // This is not the same as calling EndEdit() on a binding source             // which you must do when working with grandchild binding sources             m_mxsProject.ApplyEdit();             try             {                 m_mxsProject = m_mxsProject.Save();                 MxsProject.MxsProjectSaved(m_mxsProject);                 completed = true;             }             catch (Exception ex)             {                 FormMessage.ShowError(ex);             }         }         else         {             if (m_mxsProject != null)                 m_mxsProject.CancelEdit();             completed = true;         }     }     finally     {         if (rebind && completed)             BindUI();         bindObject      .RaiseListChangedEvents = true;         bindServiceTypes.RaiseListChangedEvents = true;         bindTemplates   .RaiseListChangedEvents = true;         if (rebind && completed)         {             bindObject      .ResetBindings(false);             bindServiceTypes.ResetBindings(false);             bindTemplates   .ResetBindings(false);         }     }     return completed; }

Vinodonly replied on Monday, July 04, 2011

thanks for the reply.. kindly note the snapshot shows a call to method unbindbindingsource but code of that is not visible in the snapshot, pls advs..

Michael replied on Monday, July 04, 2011

public static void UnbindBindingSource(BindingSource source, bool apply)
{
    IEditableObject current = source.Current as IEditableObject;
 
    if (!(source.DataSource is BindingSource))
        source.DataSource = null;
 
    if (current != null)
    {
        if (apply)
            current.EndEdit();
        else
            current.CancelEdit();
    }
}

Vinodonly replied on Monday, July 04, 2011

i tried your code as well as tried the cslaactionextender but none of that is working..

this is the line, i'm giving for using csla action extender (taken from the sample)..

 

_SrcBLCtrl.cslaActionExtender1.ResetActionBehaviors(_SrcBLInfo);

 

there are two specific points for my forms which i also want to write here.

a. these are user controls not forms.

b. i'm using bindingnavigator

i don't know if this makes any difference.. i'm now out of options.. pls help..

 

Michael replied on Monday, July 04, 2011

We use a lot of user controls, too, so that shouldn't be a problem.

Are you using the binding navigator to bind to a child collection inside your main BusinessBase business object?

Perhaps you should create a new test class inheriting from BusinessBase, with just a couple of CSLA properties. You don't need to implement the data access. Then create a new form or user control and try to get the binding to work correctly, following the ProjectTracker sample. Once you've got that working, you can add a collection of children and try to get the binding to work for that.

It's not meant to be difficult, but you do need to follow the patterns. If you haven't bought the book, you should consider doing so. It's 800 pages—a wealth of information.

Kind regards
Michael

Vinodonly replied on Tuesday, July 05, 2011

i have the book..

i will test it out today to see where the problem lies n get  back.

Copyright (c) Marimer LLC