views:

266

answers:

2

I am creating a WPF application that will use IDataErrorInfo data validation, which my business objects implement. I have been working with this demo from a blog post to understand ValidatesOnDataErrors.

The demo is a trivial app that binds a couple of text boxes to a Contact object and implements IDataErrorInfo validation, using ValidatesOnDataErrors=True in the data binding. To be valid, a text box has to have at least five characters.

Here is my problem: in the demo, validation is triggered whenever a text box loses focus. In fact, the text boxes initialize to an invalid state (red borders) when the application launches. My app has to put off validation until the user clicks the OK button to submit the page. At that time, the text boxes should be validated and put into an error state if invalid.

So, how would I defer validation on the text boxes until the user clicks the OK button? Thanks for your help.

A: 

Data binding has an UpdateSourceTrigger property. As the name implies, it specifies when to update the source of the binding. For the Text property, this is set to LostFocus by default. You could set this to Explicit and call the UpdateSource method of the BindingExpression in code. On the other hand, you could also defer the raising of the PropertyChanged event. However, these will not solve the problem of validation at application launch, I think. Hope this helps somehow.

Michael Detras
That's a partial solution--My databindings wouldn't update until I submitted. However, I appreciate the help, so it's acepted by me. I have detailed a more complicated solution below that keeps the bindings synchronized but defers validation until page submission.
David Veeneman
A: 

Thanks to Michael for his help. Unfortunately, I need bindings to remain constantly synchronized, with only validation deferred. Otherwise, Michael's solution would do the job. So, I went in a slightly different direction. Here is the solution I ultimately implemented.

Simple demo: Let's start with the simplest case: My business objects implement IDataErrorInfo; let's assume I can modify how it is implemented. I give each business object an additional boolean property, ValidationEnabled, and I modify the IDataErrorInfo implementation to always return a null result if that property is false.

The demo I mentioned in my original post uses a Contact object with two properties; FirstName and LastName. I added a ValidationEnabled property and modified the IDataErrorInfo implementation looks like this:

#region IDataErrorInfo Members

public string Error
{
    get { throw new System.NotImplementedException(); }
}

public string this[string columnName]
{
    get 
    {
        // Initialize
        string result = null;

        // Perform validation only if enabled
        if (ValidationEnabled)
        {
            switch (columnName)
            {
                // Validate 'First Name' 
                case "FirstName":
                    if (string.IsNullOrEmpty(FirstName))
                    {
                        result = "First name has to be set";
                    }
                    else if(FirstName.Length < 5)
                    {
                        result = "First name must be at least five characters";
                    }
                    break;

                // Validate "Last Name"
                case "LastName":
                    if (string.IsNullOrEmpty(LastName))
                    {
                        result = "Last name has to be set";
                    }
                    else if (LastName.Length < 5)
                    {
                        result = "Last name must be at least five characters";
                    }
                    break;
            }
        }

        // Set return value
        return result;
    }
}

#endregion

In my version of the simple demo, I connect the Submit button to an event handler in code-behind that enables validation and refreshes the bindings for both text boxes:

private void OnButtonClick(object sender, RoutedEventArgs e)
{
    var contact = (Contact) DataContext;
    contact.ValidationEnabled = true;
    var binding = FirstNameBox.GetBindingExpression(TextBox.TextProperty);
    binding.UpdateSource();
    binding = LastNameBox.GetBindingExpression(TextBox.TextProperty);
    binding.UpdateSource();
}

Now the red outlines don't appear until I try to submit the form.

Real world: In my production app, I don't control the business objects, and I can't modify them as I did in my simple demo. So, I create a simple pass-through wrapper for the business object that exposes the properties to be databound to the view and links those properties to the corresponding properties of the wrapped business object. The wrapper implements IDataErrorInfo and contains the ValidationEnabled property.

The wrapper's IDataErrorInfo implementation always returns null if validation is not enabled. If validation is enabled, then the wrapper calls the IDataErrorInfo implementation on the wrapped object and returns what it gets from there.

This approach will be familiar to anyone who uses the Model-View-ViewModel pattern. What we are doing is creating a view-model wrapper for a business object, which is considered an MVVM best practice by many developers. It separates the UI concern (supressing red borders until the page is submitted) from the business model concern (simple object validation).

Also, my production app will not use event handlers in code-behind. Per MVVM, the button will be wired to an ICommand, which will contain the logic from the OnButtonClick() handler in the simple demo.

I hope that's helpful to anyone else researching this issue down the road.

David Veeneman