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:
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
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:
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..