Showing posts with label MVVM. Show all posts
Showing posts with label MVVM. Show all posts

Friday, January 4, 2013

Validate all controls in XAML

Ever thought how to validate all controls in WPF when a user submits a form?

With WPF, you have access to the visual tree and with this in hand, I created my little validator called 'BadAssValidation' :) which enumerates down the tree to find controls to validate.

What I do is that on the submit button in my application, I use the 'Command Parameter' and I pass in the Grid where all the controls that I want to validate exists.
 
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,6,26" Name="btSave" VerticalAlignment="Bottom" Width="75" 
Command="{Binding Path=SaveOrderCommand}" CommandParameter="{Binding ElementName=OrderGrid}" Content="Save" />

Note that each individual control needs to have been bound to their own validator in order for the BadAssValidation to check if they have erroros.

<Binding.ValidationRules>
<validate:valMXvarchar300/>
</Binding.ValidationRules>

In the View Model, I now run the BadAssValidation on the Grid. The validator will enumerate through the visual tree to find matching controls. It will then check if any of these controls have failed validation.

'Validate with BadAssValidationDim validationObj As New Validator.BadAssValidation
If Not validationObj.enumVisual(param) Then
...


And here is the BadAssValidator class:

  Public Class BadAssValidation
  
     Private hasErrors As Boolean = False
  
     ''' <summary>
  
     ''' <para>Validation for all textboxes and comboboxes on UserControl.
  
     ''' </para>
  
     ''' <para>This method loops through textboxes and comboboxes on usercontrol
  
     ''' and calls their Binding validation rule if one exists.</para>
  
     ''' </summary>
  
     ''' <param name="visual">Visual</param>
  
     ''' <returns>Boolean: True if all controls pass validation : else false.</returns>
  
     ''' <remarks></remarks>
  
     Public Function enumVisual(ByVal visual As Visual) As Boolean
  
       Try
  
         ''Count children in visual tree node
  
         Dim j As Integer = VisualTreeHelper.GetChildrenCount(visual) - 1
  
         ''Loop through visual child
  
         For i As Integer = 0 To j
  
           Dim childvisual As Visual = CType(VisualTreeHelper.GetChild(visual, i), Visual)
  
           ''Check if child has a name
  
           If Not childvisual.DependencyObjectType.Name Is Nothing OrElse Not childvisual.DependencyObjectType.Name = "" Then
  
             ''Check if child is of 'TextBox'
  
             If childvisual.DependencyObjectType.Name = "TextBox" Then
  
               Dim txtbox As TextBox = childvisual
  
               ''Update source of control
  
               If Not txtbox.GetBindingExpression(TextBox.TextProperty) Is Nothing Then txtbox.GetBindingExpression(TextBox.TextProperty).UpdateSource()
  
               ''Check if control has errors
  
               If System.Windows.Controls.Validation.GetHasError(txtbox) Then hasErrors = True
  
               'for testing, need to create a new object before use   ': st.Append(txtbox.Name & vbNewLine) : MessageBox.Show(st.ToString)
  
             End If
  
             ''Check if child is of 'ComboBox'
  
             If childvisual.DependencyObjectType.Name = "ComboBox" Then
  
               Dim combox As ComboBox = childvisual
  
               ''Update source of control --Tag
  
               If Not combox.GetBindingExpression(ComboBox.TagProperty) Is Nothing Then combox.GetBindingExpression(ComboBox.TagProperty).UpdateSource()
  
               ''Update source of control --SelectedValue
  
               If Not combox.GetBindingExpression(ComboBox.SelectedValueProperty) Is Nothing Then combox.GetBindingExpression(ComboBox.SelectedValueProperty).UpdateSource()
  
               ''Check if control has errors
  
               If System.Windows.Controls.Validation.GetHasError(combox) Then hasErrors = True
  
               'for testing, need to create a new object before use   ': st.Append(combox.Name & vbNewLine) : MessageBox.Show(st.ToString)
  
             End If
  
             ''Check if child is of 'DatePicker'
  
             If childvisual.DependencyObjectType.Name = "DatePicker" Then
  
               Dim dPicker As DatePicker = childvisual
  
               ''Update source of control
  
               If Not dPicker.GetBindingExpression(DatePicker.SelectedDateProperty) Is Nothing Then dPicker.GetBindingExpression(DatePicker.SelectedDateProperty).UpdateSource()
  
               ''Check if control has errors
  
               If System.Windows.Controls.Validation.GetHasError(dPicker) Then hasErrors = True
  
               'for testing, need to create a new object before use   ': st.Append(txtbox.Name & vbNewLine) : MessageBox.Show(st.ToString)
  
             End If
  
           End If
  
           enumVisual(childvisual)
  
         Next i
  
       Catch ex As Exception
  
         Dim resultObj As New Results
  
         resultObj.displayResult(ex)
  
       End Try
  
       Return hasErrors
  
     End Function
  
     'Private st As System.Text.StringBuilder
  
   End Class
  
   Public Class val0ApprovedRegions
  
     Inherits ValidationRule
  
     Public Overrides Function Validate(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As System.Windows.Controls.ValidationResult
  
       Dim valChar As Char = CChar(value)
  
       If valChar = "T" Then
  
         Return New ValidationResult(False, "Approved regions must be selected.")
  
       Else
  
         Return New ValidationResult(True, Nothing)
  
       End If
  
     End Function
  
   End Class  

Monday, March 26, 2012

Nasty Exceptions with Threaded MVVM Application

Consider to work with the private variable of a property versus the public property itself if you are implementing INotifyPropertyChanged. More specifically, if you have your OnPropertyChanged event in the Set block of your property as this makes a call to the Main Thread of your application to update the view (which you probably already knew).

Here is an example…

This property calls the OnPropertyChanged event every time the property is set.

        Private _salesOrderItemView As DataView = Nothing

        Public Property SalesOrderItemView As DataView

            Get

                Return _salesOrderItemView

            End Get

            Set(ByVal value As DataView)

                _salesOrderItemView = value

                OnPropertyChanged("SalesOrderItemView")

            End Set

        End Property

                                                         

And if you populate the property

SalesOrderItemView = dsTemp.Tables(0).DefaultView



Then add a column

SalesOrderItemView.Table.Columns.Add("NewColumn")



And do a For Each loop

For Each row As DataRow In SalesOrderItemView.Table.Rows

       ‘’Do something

Next



If it is quick enough, you will get a nasty exception.

Why? I am not really sure, but I think it has to do with adding a row and be in the iteration of the For Each loop. It seems to me that if you are adding the row, which will call the OnPropertyChanged event and update the view in the main thread, it will continue to execute the background thread. If it is fast enough to get into the For Each loop while the view is being updated, it will take away the object reference to the view.

Though, if I put a couple of lines of code between the adding a column statement and the For Each loop, the view will have enough time to get updated and no exception will be thrown.

Commenting out the OnPropertyChanged event will also avoid an exception being thrown.
Any comments on this?