Validation and the DataGridView

Validation and the DataGridView

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


bgilbert posted on Friday, February 09, 2007

I'm unclear about how to use Validation Rules to run interference in the UI. I have an Editable Root List that contains Editable Children. I have rules on the child class that successfully return errors to rows in my DataGridView. What I want is to disallow creation of new rows when errors exist on any existing rows. Should I do this in the UI or somehow in the collection class? Can I use the AllowNew property to turn this on and off? What's the best practice for doing this?

I should also add that I think this question is related to this thread. I'm still not sure how to check rules for an object that has not had any property set calls. If I do a ValidationRules.CheckRules() in the constructor, it will fail every property that hasn't yet been written to. The side effect of this is that the error provider displays an "error" even though the user has not typed/selected anything yet. Is there some kind of event I can use at the collection class level to indicate that the user has navigated to another member so that I can fire the CheckValues? Does this make sense?

Thanks for any help, Barry

RockfordLhotka replied on Friday, February 09, 2007

To do it right, you need to have both the collection and UI involved.

The collection needs to prevent addition of a new child when existing children are invalid. If your base class is BusinessListBase you can just use its IsValid property. If you base class is ERLB you'll have to loop through the child items yourself to check each one's IsValid property.

I'd build this decision logic into a read-only property on the collection called IsAddAllowed or something like that.

Either way, your AddNewCore() override can throw an exception to prevent the addition of a new item by checking this IsAddAllowed property.

While "correct", only doing that would lead to a poor user experience. So the UI also needs to be involved. It can handle the ListChanged event, which is raised any time the list or its children change. At that point the UI can check IsAddAllowed and enable/disable the UI from allowing the user to try and add a new row.

This is good separation of concerns: the actual decision logic and final check is in the business layer, but the UI is able to also give the user cues as to what is and isn't going to work.

bgilbert replied on Monday, February 12, 2007

Thanks. I had already figured out the same solution.

yuhnkee replied on Tuesday, February 27, 2007

Is their a reason we can't override the save method and check those rules that are required?

RockfordLhotka replied on Tuesday, February 27, 2007

That is a good point - you could certainly override Save sort of like this:

public override object Save()
{
  ValidationRules.CheckRules();
  return base.Save()
}

 

bgilbert replied on Thursday, March 01, 2007

Would this work? I didn't think you could CheckRules from a BusinessList-derived class.

I think I got this to work correctly by doing ValidationRules.CheckRules at the beginning of the Insert and Update routines. Since, in this case, I only care about validity when persisting the data, I think this will work.

Jimbo replied on Friday, March 02, 2007

You could expose a .CheckRules or custom validation helper method in the List BO that the UI may call at the appropriate point in the cycle. Or you could make good use of an ApplyAuthorizationRules method to control the enabling of  the Save/apply button without necessarily having to invoke the ErrorProvider based on property changes..  You can also set IsValid to false by default in the ctor - so that the user must at least begin to enter something that activates checkrules() to get access to the save button.



TooMuch replied on Tuesday, April 08, 2008

I like the solution proposed by Rocky here for this situation, but I'm struggling with the implementation details.

If you throw an exception in AddNewCore(), where do you catch it in the UI?  If I set a break in AddNewCore, I don't see any events that I can use in the UI to put a try-catch.  Has anybody out there handled this scenario?  Where would you catch the exception from AddNewCore()?

 

bgilbert replied on Tuesday, April 08, 2008

I would suggest the DataError event from either the BindingSource or the dgv.

TooMuch replied on Tuesday, April 08, 2008

Thanks for the suggestion, I just tried putting both the DataGridView DataError and BindingSource DataError events in my UI and they don't catch the exception.  When I look at the call stack in my breakpoint in AddNewCore all I get is the infamous [External Code] so I'm still not sure where I can catch the exception in the UI... 

I am now wondering if I should remove the AddNewCore() from my BusinessListBase and instead use the BindingSource AddingNew event to call a method in my BusinessListBase that would do the same thing as AddNewCore.  This would allow me to catch the exception in the UI. Will this work? 

 

Another question in regards to the UI side.  When I do the following:

public override object Save()
{
  ValidationRules.CheckRules();
  return base.Save()
}

My object is correctly setting IsValid = False, but the ErrorProvider exclamations are not appearing next to the controls.  The ErrorProvider's DataSource is set to the BindingSource.  How do I make the exclamations appear in this scenario?

FatPigeon replied on Wednesday, April 09, 2008

I also have found that an error in the AddNewCore is not caught by the data error event on the data source or the data grid view. So far, the only thing I know that seems to work is to register a handler with the Application.ThreadException event.

 

Patrick

RockfordLhotka replied on Friday, February 09, 2007

bgilbert:
I should also add that I think this question is related to this thread. I'm still not sure how to check rules for an object that has not had any property set calls. If I do a ValidationRules.CheckRules() in the constructor, it will fail every property that hasn't yet been written to. The side effect of this is that the error provider displays an "error" even though the user has not typed/selected anything yet. Is there some kind of event I can use at the collection class level to indicate that the user has navigated to another member so that I can fire the CheckValues? Does this make sense?

Unforutnately this is the intended behavior. I'm not sure how you'd suppress this behavior in any meaningful way.

To be safe, your object does have to check all its rules on creation, otherwise IsValid could be true when it really should be false, and the user could save a bad object.

At the same time, what you are asking for is that the object be invalid and not say why. In short, you want the IDataErrorInfo.Item property (or indexer in C#) to return "" for properties the user hasn't touched yet, even if those properties are actually invalid.

At the moment, IDataErrorInfo.Item isn't virtual, so you can't override it. However, maybe if it was virtual you could override it in your code to somehow keep a list of properties the user has visited so you could turn on error reporting for each property in turn.

The user experience could still be bad though, because if the user never visits a property that's in error the object would remain invalid (unsavable) and the user wouldn't have a visual indication why they couldn't save their data.

My suggestion is to experiment: change Csla\Core\BusinessBase so the IDataErrorInfo.Item (or indexer in C#) method is virtual/Overridable, then see if you can come up with a meaningful way to decide on a property-by-property basis whether to return "" or allow the normal behavior to occur.

This keeps the changes to CSLA .NET limited: just one line changes. And allows you to see if this is a workable solution at all.

Copyright (c) Marimer LLC