Click here to Skip to main content
15,888,218 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hello everyone,

I have a ValidatableBindableBase class that implements INotifyDataErrorInfo interface, and INotifyPropertyChanged interface.

The validations depending on data annotations and the custom validations are working fine.

The problem is when the text box fails to convert the input to the bound property's data type, the error message is displayed "failed to convert from string 'ab' to integer" but the viewmodel's errors are not updated with this error and therefore the command that should be disabled on error is not disabled..


My main goal is to disable the command when a conversion error occurs and I want to do it from the viewmodel if possible

The command's can-execute (which determines if the button is enabled or not), that method depends on the errors collection of the view model,
when a conversion exception occurs (like when you enter a text in a text box bound to deciaml) the errors collection is not affected (because the property setter is never called) and hence the can execute of the command doesn't return false..

The Code:

VB
Public Class BindableBase
    Implements INotifyPropertyChanged
    Protected Overridable Sub SetProperty(Of T)(ByRef member As T,
                                                ByVal val As T,
                                                <CallerMemberName> Optional propertyName As String = Nothing)

        If Object.Equals(member, val) Then Return
        member = val
        RaisePropertyChanged(propertyName)
    End Sub
    Sub RaisePropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
End Class




VB
Public Class ValidatableBindableBase
    Inherits BindableBase
    Implements INotifyDataErrorInfo
    Private _errors As New Dictionary(Of String, List(Of String))

    Public ReadOnly Property HasErrors As Boolean Implements INotifyDataErrorInfo.HasErrors
        Get
            Return _errors.Count > 0
        End Get
    End Property

    Public Event ErrorsChanged As EventHandler(Of DataErrorsChangedEventArgs) Implements INotifyDataErrorInfo.ErrorsChanged

    Public Function GetErrors(propertyName As String) As IEnumerable Implements INotifyDataErrorInfo.GetErrors
        If propertyName Is Nothing Then Return Nothing
        If _errors.ContainsKey(propertyName) Then
            Return _errors(propertyName)
        Else
            Return Nothing
        End If
    End Function

    Protected Overrides Sub SetProperty(Of T)(ByRef member As T, val As T, <CallerMemberName> Optional propertyName As String = Nothing)
        MyBase.SetProperty(member, val, propertyName)
        ClearErrors(propertyName)
        ValidateDataAnnotationsForProperty(propertyName, val)
        ValidateCustomErrors(propertyName)
    End Sub

    Private Sub ValidateDataAnnotationsForProperty(Of T)(ByVal propertyName As String, ByVal value As T)
        Dim results As New List(Of ValidationResult)
        Dim context As New ValidationContext(Me)
        context.MemberName = propertyName
        Validator.TryValidateProperty(value, context, results)
        If results.Any() Then
            _errors(propertyName) = results.Select(Function(r) r.ErrorMessage).ToList()
        Else
            _errors.Remove(propertyName)
        End If
        RaiseErrorsChanged(propertyName)
    End Sub
    Sub RaiseErrorsChanged(ByVal propertyName As String)
        RaiseEvent ErrorsChanged(Me, New DataErrorsChangedEventArgs(propertyName))
    End Sub

    Private Sub ValidateCustomErrors(ByVal propertyName As String)
        Dim errors = ValidateProperty(propertyName)
        If errors IsNot Nothing Then
            For Each e In errors
                AddError(propertyName, e)
            Next
        End If
    End Sub
    Protected Sub AddError(ByVal propertyName As String, ByVal err As String)
        If Not _errors.ContainsKey(propertyName) Then _errors(propertyName) = New List(Of String)
        If Not _errors(propertyName).Contains(err) Then
            _errors(propertyName).Add(err)
            RaiseErrorsChanged(propertyName)
        End If
    End Sub
    Protected Sub ClearErrors(ByVal propertyName As String)
        If _errors.ContainsKey(propertyName) Then
            _errors.Remove(propertyName)
            RaiseErrorsChanged(propertyName)
        End If
    End Sub
    Protected Overridable Function ValidateProperty(ByVal propertyName As String) As IEnumerable(Of String)
        Return Nothing
    End Function
End Class



and this is a property from the viewmodel inhering validatableBindableBase:

VB
Private _price As Decimal
<Required(ErrorMessage:="سعر الصنف لا يمكن أن يكون فارغا")>
<Range(0.0, Double.MaxValue, ErrorMessage:="سعر الصنف لا يمكن أن يكون سالبا")>
Public Property Price As Decimal
    Get
        Return _price
    End Get
    Set(value As Decimal)
        SetProperty(_price, value)
    End Set
End Property


What I have tried:

There is a proposed solution here:
Validation in WPF[^]

but it uses validation logic in the view, I need to make all validation in the viewmodel if possible.

and also when I tried this, it changed only the message shown below the text box (I use an error template), but also didn't make the command that should be disabled on error disabled..
Posted
Updated 15-Nov-19 4:36am
v4

1 solution

You don't disable commands - you disable the controls that invoke them.

The view should be able to detect the error status and disable a control based on that status.

That's how it's supposed to work.
 
Share this answer
 
Comments
Richard Deeming 14-Nov-19 11:25am    
That's not the WPF way. Buttons/menus/etc. are bound to commands, and the command determines whether or not it can execute. The control is enabled or disabled based on the command, not the other way round.

Commanding Overview | Microsoft Docs[^]
Ahmad_kelany 15-Nov-19 10:28am    
Yes,I am aware of that,
Maybe I didn't clarify my problem well, what I mean is the command's can execute which determines if the button is enabled or not, that method depends on the errors collection of the view model,
when a conversion exception occurs (like when you enter a text in a text box bound to deciaml) the errors collection is not affected (because the property setter is never called) and hence the can execute of the command doesn't return false..

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900