One way to defer validation is to set the UpdateSourceTrigger=Explicit property in the bindings. If you do this, the bindings will not update the original objects and, therefore, will not cause validation errors until you explicitly specify the bindings for this. When you click the button, you force the bindings to be updated using the following line for each element:
someTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
Then you have your property developers throwing exceptions for invalid data.
This approach can be a little painful if there are enough controls to force the binding to be updated.
In addition, forced binding updates must be performed in the control code of the control. If you also use a command with a button, you may run into a problem. Buttons can have either a Command or Click handler, and both will be executed when the button is clicked, but I donโt know in what order this happens or even if the order can be guaranteed. A quick experiment showed that the event handler was executed before the command, but I do not know if this behavior is undefined. Therefore, it is likely that the command will be run before the bindings are updated.
The programming approach for creating validation tooltips is to associate another property of the text field, and then deliberately cause an error with this binding.
'sapient' posted the complete solution, including code on the Silverlight forums (search for message from 08/07/2009 4:56 PM). In short, it creates an auxiliary object with a property whose receiver throws an exception, binds the Tag property of the text field to this auxiliary object, and then forces the binding to be updated.
'sapient code was written before the release of Silverlight 4. We will upgrade his / her code to Silverlight 4. The ControlValidationHelper class will become:
public class ControlValidationHelper : IDataErrorInfo { public string Message { get; set; } public object ValidationError { get; set; } public string Error { get { throw new NotImplementedException(); } } public string this[string columnName] { get { return Message; } } }
It's easy enough to knock out a quick demo app to try this. I created the following three controls:
<TextBox x:Name="tbx" Text="{Binding Path=Text, ValidatesOnDataErrors=True, NotifyOnValidationError=True, Mode=TwoWay}" /> <Button Click="ForceError_Click">Force error</Button> <Button Click="ClearError_Click">Clear error</Button>
The Text property and event handlers for two buttons live in the code and look like this:
public string Text { get; set; } private void ForceError_Click(object sender, RoutedEventArgs e) { var helper = new ControlValidationHelper() { Message = "oh no!" }; tbx.SetBinding(Control.TagProperty, new Binding("ValidationError") { Mode = BindingMode.TwoWay, NotifyOnValidationError = true, ValidatesOnDataErrors = true, UpdateSourceTrigger = UpdateSourceTrigger.Explicit, Source = helper }); tbx.GetBindingExpression(Control.TagProperty).UpdateSource(); } private void ClearError_Click(object sender, RoutedEventArgs e) { BindingExpression b = tbx.GetBindingExpression(Control.TagProperty); if (b != null) { ((ControlValidationHelper)b.DataItem).Message = null; b.UpdateSource(); } }
The Power Error button should make a validation error in the text box, and the Clear Error button should go away.
One potential drawback of this approach arises if you use ValidationSummary . ValidationSummary will list all validation errors with a ValidationError instead of the name of each property.