开发者

Adding an ExceptionValidationRule to a Binding in code

I have been developing an ErrorProvider control that inherits from Decorator. It validates any elements within the control that are bound to something. It loops through every binding and within a FrameworkElement and adds an ExceptionValidationRule as well as a DataErrorValidation to the binding's ValidationRules.

This is the method that does the work:

Private Sub ApplyValidationRulesToBindings()
    Dim bindings As Dictionary(Of FrameworkElement, List(Of Binding)) = GetBindings()

    For Each felement In bindings.Keys
        Dim knownBindings As List(Of Binding) = bindings(felement)

        For Each knownBinding As Binding In knownBindings
            'Applying Exception and Data Error validation rules'
            knownBinding.ValidationRules.Add(New ExceptionValidationRule())
            knownBinding.ValidationRules.Add(New DataErrorValidationRule())
        Next

    Next
End Sub

Apparently the DataErrorValidationRule is applied to the binding, but the ExceptionValidationRule is not.

Does anyone know why this might be the case?

Edit: Ok, so a little more info on the problem.

I've been reading tons of MSDN documentation on Validation and the Binding class. The Binding.UpdateSourceExceptionFilter Property allows you to specify a function that handles any exceptions th开发者_如何学编程at occur on a binding if an ExceptionValidationRule has been associated with the Binding.

I added a method for the UpdateSourceExceptionFilter Property and guess what! It was executed. BUT!! Even though I returned the exception, a ValidationError object was NOT added to Validation.Errors collection of the bound element even though the MSDN documentation said that it would be...

I commented out the code that adds the ExceptionValidationRule dynamically and manually added one to the Binding in XAML like so:

<TextBox HorizontalAlignment="Left" Name="TextBox1" VerticalAlignment="Top" 
                                   Width="200">
  <TextBox.Text>
    <Binding Path="Name">
      <Binding.ValidationRules>
        <ExceptionValidationRule />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text> 
</TextBox>

The UpdateSourceException method was executed (I didn't change it) and the error was added to the Validation.Errors as the MSDN stated.

What is curious about this whole thing is the fact that the ExceptionValidationRule is in fact added to the binding when done through the VB.NET code (or else the the UpdateSourceException would never have executed); however, the Validate.Errors is not updated with the error.

As I stated earlier, the DataErrorValidationRule is added to the binding and works properly...I'm just having problems with the ExceptionValidationRule.

My Solution:

It turns out that I had to call the BindingOperations.SetBinding Method for the binding and the property to apply the validation rules to the binding. I had no way of retrieving the DependencyProperty that for the element/binding in my ApplyValidationRulesToBindings method so I moved the code that applied rules to the call back method provided my method that retrieves all of the bindings recursively.

Here is my solution:

''' <summary>'
''' Gets and returns a list of all bindings. '
''' </summary>'
''' <returns>A list of all known bindings.</returns>'
Private Function GetBindings() As Dictionary(Of FrameworkElement, List(Of Binding))
    If _bindings Is Nothing OrElse _bindings.Count = 0 Then
        _bindings = New Dictionary(Of FrameworkElement, List(Of Binding))
        FindBindingsRecursively(Me.Parent, AddressOf RetrieveBindings)
    End If
    Return _bindings
End Function


''' <summary>'
''' Recursively goes through the control tree, looking for bindings on the current data context.'
''' </summary>'
''' <param name="element">The root element to start searching at.</param>'
''' <param name="callbackDelegate">A delegate called when a binding if found.</param>'
Private Sub FindBindingsRecursively(ByVal element As DependencyObject, ByVal callbackDelegate As FoundBindingCallbackDelegate)

    ' See if we should display the errors on this element'
    Dim members As MemberInfo() = element.[GetType]().GetMembers(BindingFlags.[Static] Or BindingFlags.[Public] Or BindingFlags.FlattenHierarchy)

    For Each member As MemberInfo In members
        Dim dp As DependencyProperty = Nothing
        ' Check to see if the field or property we were given is a dependency property'
        If member.MemberType = MemberTypes.Field Then
            Dim field As FieldInfo = DirectCast(member, FieldInfo)
            If GetType(DependencyProperty).IsAssignableFrom(field.FieldType) Then
                dp = DirectCast(field.GetValue(element), DependencyProperty)
            End If
        ElseIf member.MemberType = MemberTypes.[Property] Then
            Dim prop As PropertyInfo = DirectCast(member, PropertyInfo)
            If GetType(DependencyProperty).IsAssignableFrom(prop.PropertyType) Then
                dp = DirectCast(prop.GetValue(element, Nothing), DependencyProperty)
            End If

        End If
        If dp IsNot Nothing Then
            ' we have a dependency property. '
            'Checking if it has a binding and if so, checking if it is bound to the property we are interested in'
            Dim bb As Binding = BindingOperations.GetBinding(element, dp)
            If bb IsNot Nothing Then
                ' This element has a DependencyProperty that we know of that is bound to the property we are interested in. '
                ' Passing the information to the call back method so that the caller can handle it.'
                If TypeOf element Is FrameworkElement Then
                    If Me.DataContext IsNot Nothing AndAlso DirectCast(element, FrameworkElement).DataContext IsNot Nothing Then
                        callbackDelegate(DirectCast(element, FrameworkElement), bb, dp)
                    End If
                End If
            End If
        End If
    Next

    'Recursing through any child elements'
    If TypeOf element Is FrameworkElement OrElse TypeOf element Is FrameworkContentElement Then
        For Each childElement As Object In LogicalTreeHelper.GetChildren(element)
            If TypeOf childElement Is DependencyObject Then
                FindBindingsRecursively(DirectCast(childElement, DependencyObject), callbackDelegate)
            End If
        Next
    End If
End Sub

''' <summary>'
''' Called when recursively populating the Bindings dictionary with FrameworkElements(key) and their corresponding list of Bindings(value)'
''' </summary>'
''' <param name="element">The element the binding belongs to</param>'
''' <param name="binding">The Binding that belongs to the element</param>'
''' <param name="dp">The DependencyProperty that the binding is bound to</param>'
''' <remarks></remarks>'
Sub RetrieveBindings(ByVal element As FrameworkElement, ByVal binding As Binding, ByVal dp As DependencyProperty)
    'Applying an exception validation and data error validation rules to the binding' 
    'to ensure that validation occurs for the element'
    If binding.ValidationRules.ToList.Find(Function(x) GetType(ExceptionValidationRule) Is (x.GetType)) Is Nothing Then
        binding.ValidationRules.Add(New ExceptionValidationRule())
        binding.ValidationRules.Add(New DataErrorValidationRule())
        binding.UpdateSourceExceptionFilter = New UpdateSourceExceptionFilterCallback(AddressOf ReturnExceptionHandler)
        'Resetting the binding to include the validation rules just added'
        BindingOperations.SetBinding(element, dp, binding)
    End If

    ' Remember this bound element. This is used to display error messages for each property.'
    If _bindings.ContainsKey(element) Then
        DirectCast(_bindings(element), List(Of Binding)).Add(binding)
    Else
        _bindings.Add(element, New List(Of Binding)({binding}))
    End If
End Sub

Thanks!

-Frinny


I'm not really following how your code works but you can't modify a Binding after it has been used, so you can't add ValidationRules to an existing Binding. I think you'll have to copy the Binding, Property for Property and then add the ValidationRules and then set the new copied Binding with BindingOperations.SetBinding(...).

Another approach may be to create your own subclassed Binding where you add the ValidationRules directly e.g

public class ExBinding : Binding
{
    public ExBinding()
    {
        NotifyOnValidationError = true;
        ValidationRules.Add(new ExceptionValidationRule());
        ValidationRules.Add(new DataErrorValidationRule());
    }
}

Useable like

<TextBox Text="{local:ExBinding Path=MyProperty}"/>

Update

I don't think you understood my answer. You can't modify a Binding once it is in use so what you're trying to do won't work. Here's a C# sample app that shows this. It contains three TextBoxs where

  • First TextBox adds the Binding with the ExceptionValidationRule in Xaml
  • Second TextBox adds the Binding in Xaml and adds the ExceptionValidationRule in its Loaded event
  • Third TextBox adds the Binding with the ExceptionValidationRule in the Loaded event

The ExceptionValidationRule will work for TextBox 1 and 3 but not for 2. Uploaded the sample here: http://www.mediafire.com/?venm09dy66q4rmq

Update 2
You could get this to work if you set the Binding again after you've added the Validation Rule like

BindingExpression bindingExpression = textBox.GetBindingExpression(TextBox.TextProperty);
Binding textBinding = bindingExpression.ParentBinding;
textBinding.ValidationRules.Add(new ExceptionValidationRule());
// Set the Binding again after the `ExceptionValidationRule` has been added
BindingOperations.SetBinding(textBox, TextBox.TextProperty, textBinding);

I'm not sure how your GetBindings method look, but maybe you could add a SetBindings method where you set the Bindings again and call that method after you've added the ExceptionValidationRules

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜