Validation rules and ErrorProvider

Validation rules and ErrorProvider

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


dg78 posted on Friday, October 03, 2008

Hi,

 

Three questions about validation rules and ErrorProvider :

 

1) In a  winform, when validation rules are not true, the ErrorProvider appear. I wish to replace it by a MessageBox.

Also I wish, in the UI, the user can't to go to another control until the validation rules are not good on the control (textbox for instance) where the focus is.

How to do this ?

 

2) Since .net 1.0, there is errors on ErrorProvider : if you put the mouse on the small red icon then the message appear but if you click on this icon or if the mouse stay too long time on it, the message disappears. After that, it is not possible this message appear again. Also sometimes the message appears twice.

I hope that will be good in .net 3.5 but it is not. Is there a solution to this problem ?

 

3) often when we do a Validation.CommonRules.StringMaxLength, the value we use is the length of a field in the database. Is it possible to use these value automaticaly (or automagicaly ;)) ?

 

Thanks in advances

 

Dominique

dg78 replied on Friday, October 03, 2008

I answer to my question number 2 :

 

If you do a search on Google ("errorprovider message disappear"), you find many sites which speak about this issue.

There is a solution at  http://www.codeproject.com/KB/dotnet/ErrorProvider_ToolTipFix.aspx

I tried it. It is good but, as his author said, it causes a slight flicker.

 

It is an old issue and Microsoft never solved it. They closed this issue and said "not important problem". It is not normal.  (see https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=188945&wa=wsignin1.0 ).

Without a fixed, the ErrorProvider is not useful. I can't explain to my users why the message appears once.

 

 

rsbaker0 replied on Friday, October 03, 2008

It's pretty easy to use the length of the database field as long as you have a mechanism to retrieve the field length. You would implement your own validation rule though.

I just basically copied the CSLA implementation and called my common routine to fetch the length of the field as opposed to using a value from RuleArg. (You probably want to cache the field lengths somewhere to avoid repeated database accesses).

I can post a sample if this isn't clear.

dg78 replied on Saturday, October 04, 2008

Thanks for your answer.

It is true that I want to cache the field lengths somewhere because there is not a binding between the database and the BO (the binding is between BO and the controls).

If you can post a sample it would be wonderful (allways I believe that a small code is better than many words).

 

rsbaker0 replied on Saturday, October 04, 2008

Here is the sample. It references some things not define here (I'm usign the Wilson ORMapper as a DAL and it provides an IObjectHelper interface for referencing the values of backing fields directly). It would be easy to change this to reflect directly on the property value and use the MaxLengthRuleArgs directly.

namespace Csla.WORMapper.Validation
{
    public class CommonRules
    {
        internal class CustomFieldMaxLengthRuleArgs : Csla.Validation.CommonRules.MaxLengthRuleArgs
        {
            public CustomFieldMaxLengthRuleArgs(string propertyName, string fieldName, int nMaxLength)
                : base(propertyName, nMaxLength)
            {
                this["FieldName"] = fieldName;
            }
        }

        internal static bool CustomFieldMaxLength(object target, Csla.Validation.RuleArgs e)
        {
            DecoratedRuleArgs args = (DecoratedRuleArgs)e;
            int max = (int)args["MaxLength"];
            string value = ((Wilson.ORMapper.IObjectHelper)target)[(string)args["FieldName"]] as string;
            if (!String.IsNullOrEmpty(value) && (value.Length > max))
            {
                e.Description = String.Format("Field {0} exceeds maximum allowed length of {1} characters",
                  RuleArgs.GetPropertyName(e), max);
                return false;
            }
            return true;
        }
    }
}

Here is the code that actually adds the rule definitions in my object's AddBusinessRules. As you can see, sometimes I have to use the special rule above, and sometimes I can just use the built-in CSLA rule. You might be able to just use the CSLA rule and skip the above:

            // Add rules for all string columns for maximum length, and whether required (e.g. not nullable)
            foreach (DataColumnInfo info in ColumnInfoCollection.Columns)
            {
                if (info.IsString)
                {
                    // Add rules for all non-nullable string fields
                    if (!info.Nullable)
                        ValidationRules.AddRule<T>(CommonRules.StringRequired, info.MappedPropertyName);

                    // CSLA doesn't currently support dynamic properties, so use special rule if property can't be found
                    PropertyInfo p = GetType().GetProperty(info.MappedPropertyName);

                    if (p == null)
                    {
                        // Must use internal field
                        ValidationRules.AddRule(Validation.CommonRules.CustomFieldMaxLength,
                            new Validation.CommonRules.CustomFieldMaxLengthRuleArgs(info.MappedPropertyName, info.MappedFieldName, info.ColumnSize));
                    }
                    else
                    {
                        ValidationRules.AddRule(Csla.Validation.CommonRules.StringMaxLength,
                          new Csla.Validation.CommonRules.MaxLengthRuleArgs(info.MappedPropertyName, info.ColumnSize));
                    }
                }
            }

 

dg78 replied on Monday, October 06, 2008

Thanks for your sample.

I think your code is possible only because you use an ORM, it is to said a relation between the properties in the BO and the fields of the database.

Without this relation, the properties of the BO can't know the length of the fields in the database unless to pass them in attributes of the properties or other things.

 

Now, my first question in this thread, always without answer :

1) In a  winform, when validation rules are not true, the ErrorProvider appear. I wish to replace it by a MessageBox. Also I wish, in the UI, the user can't to go to another control until the validation rules are not good on the control (textbox for instance) where the focus is.
How to do this ?

 

In another words : I want to use for some controls (TextBox) an ErrorProvider and for anothers controls a MessageBox and keep the focus on the control where the rule is broken (with the ErrorProvider the focus go on the following control).

 

To use a MessagBox, I can do  e.description=""   to not have an ErrorProvider icon but how to use a MessageBox and  sender.focus  when the ErrorProvider is managed in the business class and there is a binding between the business class and the controls in the form.

 

Thanks for help.

rsbaker0 replied on Tuesday, October 07, 2008

dg78:

Thanks for your sample.

I think your code is possible only because you use an ORM, it is to said a relation between the properties in the BO and the fields of the database.

Without this relation, the properties of the BO can't know the length of the fields in the database unless to pass them in attributes of the properties or other things.

 

Actually, the ORM makes it a little simpler, but it's not all that difficult. I but I'm basically just using an IDataReader when you get right down to it. This code queries a single column, but you can also do the entire table:

 

        public static Util.DataColumnInfo GetDataColumnInfo(TransactionContext context, string tableName, string columnName)
        {
            Wilson.ORMapper.Internals.Connection connection = context.ObjectSpace.Context.Connection;
            DataTable schema;
            string strSQL = "SELECT " + columnName +  " FROM " + tableName;
            Util.DataColumnInfo info = null;

            try
            {
                using (IDataReader reader = connection.GetSchemaReader(Global.GetEntityType(tableName), strSQL))
                {
                    schema = reader.GetSchemaTable();
                }


                if (schema.Rows.Count == 1)
                {
                    info = Util.DataColumnInfoCollection.GetColumnInfo(new Util.Schema.GetSchemaTableProvider(), schema.Rows[0]);
                }
            }
            catch (System.Exception)
            {
            }
            return info;
        }

As far as your ErrorProvider question goes, all you really need to do is (1) don't use an error provider, and (2) Use the ToString() method on your object's BrokenRulesCollection to display the error to the user using the method of your choice (MessageBox, etc.)

Copyright (c) Marimer LLC