Saturday, 23 February 2013

Microsoft Test Manager fails to Record Clicks on Bound WPF DataGridView

I recently ran into a problem when using Microsoft Test Manager for UI testing. When clicking on a row in a Bound DataGridView, I would receive a warning that a step couldn’t be recorded.

Then, when I imported the test into Visual Studio when I wanted to convert it to an Automated Test, that step would report;

Last action on Row was not recorded because the control does not have any good identification property.

After a quick search, I came across a Microsoft blog which describes how to rectify this issue;

http://blogs.msdn.com/b/gautamg/archive/2010/03/10/how-to-get-automation-working-properly-on-data-bound-wpf-list-or-combo-box.aspx

Basically, the issue is that the Test Manager resolves clicks in DataGrids and Lists by calling the .ToString() method on the item. If this function doesn’t return anything meaningful that the Test Manager can use, then the step isn’t recorded.

As I had bound my WPF DataGridView to an Observable Collection of Entities, I was simply able to override the .ToString() method in the Buddy Class for the Entity, which solved the problem quite nicely.

Sunday, 3 February 2013

WPF Error Adorner Visibility Binding MVVM Style

While working on an Application for a Client recently, I found that I needed to be able to control the visibility of the WPF Error Adorners.

These error adorners are a great way of giving the end user a visual representation of where any Validation Errors exist. However, as a result of their design, these Adorners will always appear ontop of all other elements. This causes a problem of course, when using anything such as a Customer Message box. The very thing required to notify the User of any Validation Errors on Save for example.

So, I decided to look at ways of controlling, globally, the Visibility of the Error Adorners. This became rather troublesome, After some trial and error I realised that I could reach the DataContext that the Error Adorner was placed within by refering to the AdornedElement’s Parent.

Once I realised this, I added a ShowErrors Boolean Property to my DataContext, and bound the Visibility of the AdornedElement to this.

The XAML Code was;

<Converters:BolVisibilityConverter x:Key="MyBolVisibilityConverter"/>
<Style TargetType="{x:Type TextBox}">
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="Margin" Value="0,2,40,2" />
      <Setter Property="Validation.ErrorTemplate">
          <Setter.Value>
              <ControlTemplate>
                        <DockPanel LastChildFill="true" Visibility="{Binding ElementName=customAdorner, Path=AdornedElement.Parent.DataContext.ShowErrors, Converter={StaticResource MyBolVisibilityConverter}, Mode=TwoWay}">
                            <Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
                            ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                                <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
                                </TextBlock>
                          </Border>
                          <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
                         <Border BorderBrush="red" BorderThickness="1" />
                       </AdornedElementPlaceholder>
                   </DockPanel>
               </ControlTemplate>
           </Setter.Value>
      </Setter>
</Style>


I needed to use a Value Converter here, to convert my Boolean to a Visibility Value. The Converter Code was;



Namespace Converters
 
    Public Class BolVisibilityConverter
        Implements IValueConverter
 
        Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.Convert
 
            If value Is Nothing OrElse value = False Then
 
                Return Visibility.Hidden
 
            Else
 
                Return Visibility.Visible
 
            End If
 
        End Function
 
        Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
            Return DirectCast(value, Boolean)
 
        End Function
    End Class
 
End Namespace

I was then able to control the Adorner Visibility simply by setting the ShowErrors Property to either True or False