Добавление ExceptionValidationRule в привязку в коде

Я занимаюсь разработкой элемента управления ErrorProvider, который наследуется от Decorator. Он проверяет любые элементы в элементе управления, которые к чему-то привязаны. Он проходит через каждую привязку и внутри FrameworkElement и добавляет ExceptionValidationRule, а также DataErrorValidation к ValidationRules привязки.

Это метод, который делает свою работу:

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

Очевидно, DataErrorValidationRule применяется к привязке, а ExceptionValidationRule - нет.

Кто-нибудь знает, почему это может быть так?

Изменить: Итак, еще немного информации о проблеме.

Я читал тонны документации MSDN по валидации и классу привязки. Свойство Binding.UpdateSourceExceptionFilter позволяет вам чтобы указать функцию, которая обрабатывает любые исключения, возникающие в привязке, если ExceptionValidationRule был связан с привязкой.

Я добавил метод для свойства UpdateSourceExceptionFilter и угадайте, что! Он был казнен. НО!! Несмотря на то, что я вернул исключение, объект ValidationError НЕ был добавлен в коллекцию Validation.Errors связанного элемента, хотя в документации MSDN сказано, что это будет ...

Я закомментировал код, который динамически добавляет ExceptionValidationRule, и вручную добавил его в привязку в XAML следующим образом:

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

Был выполнен метод UpdateSourceException (я его не менял), и ошибка была добавлена ​​в Validation.Errors, как указано в MSDN.

Что любопытно во всем этом, так это тот факт, что ExceptionValidationRule фактически добавляется к привязке, когда это делается с помощью кода VB.NET (иначе UpdateSourceException никогда бы не сработал); однако Validate.Errors не обновляется с ошибкой.

Как я уже говорил ранее, DataErrorValidationRule добавляется к привязке и работает правильно ... У меня просто проблемы с ExceptionValidationRule.

Мое решение:

Оказывается, мне пришлось вызвать BindingOperations. SetBinding Method для привязки и свойство для применения правил проверки к привязке. У меня не было возможности получить DependencyProperty для элемента / привязки в моем методе ApplyValidationRulesToBindings, поэтому я переместил код, который применял правила к методу обратного вызова, предоставив мой метод, который рекурсивно извлекает все привязки.

Вот мое решение:

''' <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

Спасибо!

-Фринни


person Frinavale    schedule 18.02.2011    source источник


Ответы (1)


Я на самом деле не слежу за тем, как работает ваш код, но вы не можете изменить привязку после того, как она была использована, поэтому вы не можете добавить ValidationRules к существующей привязке. Я думаю, вам придется скопировать привязку, свойство для свойства, а затем добавить ValidationRules, а затем установить новую скопированную привязку с помощью BindingOperations.SetBinding(...).

Другой подход может заключаться в создании собственного подкласса Binding, в который вы напрямую добавляете ValidationRules, например

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

Можно использовать как

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

Обновить

Не думаю, что вы поняли мой ответ. Вы не можете изменить привязку, когда она используется, поэтому то, что вы пытаетесь сделать, не сработает. Вот пример приложения на C #, которое это демонстрирует. Он содержит три TextBox, где

  • Сначала TextBox добавляет привязку с ExceptionValidationRule в Xaml
  • Второй TextBox добавляет Binding в Xaml и добавляет ExceptionValidationRule в свое событие Loaded.
  • Третий TextBox добавляет привязку с правилом ExceptionValidationRule в событие Loaded

ExceptionValidationRule будет работать для TextBox 1 и 3, но не для 2. Образец загружен здесь: http://www.mediafire.com/?venm09dy66q4rmq

Обновление 2
Вы можете заставить это работать, если снова установите привязку после добавления правила проверки, например

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);

Я не уверен, как выглядит ваш GetBindings метод, но, возможно, вы могли бы добавить SetBindings метод, в котором вы снова устанавливаете привязки, и вызывать этот метод после добавления ExceptionValidationRules

person Fredrik Hedblad    schedule 18.02.2011
comment
Мне не нравилась идея создания для этой цели собственного класса привязки, потому что для его использования мне потребовалось бы отредактировать МНОГО XAML. Все дело в том, чтобы избежать этого процесса. Кроме того, метод ValidationRules.Add действительно работает с существующей привязкой (это даже указано в документации: msdn.microsoft.com/en-us/library/) Клянусь, я схожу с ума от всего этого! Прошу прощения, если я плохо справляюсь, но, хоть убей, я не могу заставить это работать! - person Frinavale; 19.02.2011
comment
Я обновляю свой пост, чтобы включить в него дополнительную информацию о том, что я пытался обойти эту проблему. - person Frinavale; 19.02.2011
comment
@Frinavale: Я не уверен, что вы поняли мой ответ. Привязки, к которым вы добавляете ExceptionValidationRules, уже используются, поэтому их нельзя изменить. Смотрите мой обновленный ответ - person Fredrik Hedblad; 19.02.2011
comment
@Frinavale: Я прочитал ссылку MSDN, которую вы разместили, и не смог найти ничего, говорящего о том, что вы можете Добавить и / или удалить ValidationRule из используемой привязки. Я мог его упустить, поэтому поправьте меня, если я ошибаюсь. С другой стороны, если в нем действительно указано, что это должно быть возможно, я бы сказал, что документация неверна, как показано в загруженном мной образце. - person Fredrik Hedblad; 19.02.2011
comment
В той ссылке на документацию, которую я опубликовал, есть примечание в разделе примечаний, которое читается следующим образом: Это свойство может быть установлено в Extensible Application Markup Language (XAML) только с использованием показанного синтаксиса коллекции или путем доступа к объекту коллекции и использования его различные методы, такие как Добавить. Свойство доступа к объекту коллекции доступно только для чтения, тогда как сама коллекция предназначена для чтения и записи. (Обратите внимание на часть ИЛИ) - person Frinavale; 22.02.2011
comment
@Frinavale: Да, но в нем ничего не говорится о добавлении или удалении ValidationRules в привязке, которая уже используется. Выполнение этого перед установкой привязки вполне допустимо. - person Fredrik Hedblad; 22.02.2011
comment
Поскольку у меня не было возможности получить свойство зависимости (например, TextBox.TextProperty в вашем примере) в моем методе ApplyValidationRulesToBindings ... я переместил код, который добавляет ExceptionValidationRule и DataValidationErrorRule к Binding в методе обратного вызова, который я использую для рекурсивного извлечения привязки. Таким образом, у меня есть доступ к DependencyProperty. Я использовал BindingOperations.SetBinding для привязки элемента в этот момент, и все начало работать. - person Frinavale; 22.02.2011
comment
Спасибо за вашу помощь :) Я добавлю рабочее решение, как только почищу его достаточно, чтобы публика могла понять :) - person Frinavale; 22.02.2011
comment
Разместил свое решение (я знаю, что вам было интересно узнать, как я получаю свои привязки в первую очередь). Еще раз спасибо Meleak - person Frinavale; 22.02.2011