Does My Type Subclass a Generic Type?

by Jim Mar 26, 2009 6:42 PM

It’s trivially easy to check if your type subclasses some other type with Type.IsSubclassOf(). But what if that subclass is a generic one? IsSubclassOf() doesn’t work for that.

Enter the IsSubclassOfGeneric() extension method. I’m pretty new to extension methods, and haven’t found a lot of practical uses for then yet, but I like this one. We’re using reflection and generics a lot in my current project and this has been nice in a couple of places…

/// <summary>
/// Check if a type subclasses a generic type
/// </summary>
/// <param name="genericType">The suspected base class</param>
/// <returns>True if this is indeed a subclass of the given generic type</returns>
public static bool IsSubclassOfGeneric(this Type type, Type genericType)
{
    Type baseType = type.BaseType;

    while (baseType != null)
    {
        if (baseType.IsGenericType && 
            baseType.GetGenericTypeDefinition() == genericType)
            return true;
        else
            baseType = baseType.BaseType;
    }
    return false;
}

Tags:

WPF Validation Exceptions

by Jim Mar 15, 2009 5:01 PM

The MSDN binding validation sample would have you bind your validation error messages like so:

<Trigger Property="Validation.HasError" Value="true">
    <Setter Property="ToolTip"
      Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                      Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>

This code will flood your output window with exceptions – and if you have the “break when exception is thrown” option turned on, you'll be doing a whole lot of stepping through these.

A first chance exception of type 'System.FormatException' occurred in mscorlib.dll
A first chance exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll
A first chance exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll
System.Windows.Data Error: 16 : Cannot get 'Item[]' value (type 'ValidationError') from 
'(Validation.Errors)' (type 'ReadOnlyObservableCollection`1'). BindingExpression:Path=(0).[0].ErrorContent; 
DataItem='TextBox' (Name='textBox1'); target element is 'TextBox' (Name='textBox1'); target property is 
'ToolTip' (type 'Object') TargetInvocationException:'System.Reflection.TargetInvocationException: Exception 
has been thrown by the target of an invocation. ---> System.ArgumentOutOfRangeException: Index was out of
range. Must be non-negative and less than the size of the collection.
Parameter name: index
   at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
   at System.ThrowHelper.ThrowArgumentOutOfRangeException()
   at System.Collections.Generic.List`1.get_Item(Int32 index)
   at System.Collections.ObjectModel.Collection`1.get_Item(Int32 index)
   at System.Collections.ObjectModel.ReadOnlyCollection`1.get_Item(Int32 index)
   --- End of inner exception stack trace ---
   . . . 
   at MS.Internal.Data.PropertyPathWorker.RawValue(Int32 k)'

The problem appears to be that when the error is cleared (and HasError is false,) the binding is trying to evaluate again anyway. So the index of zero is out of range.

Josh Smith has a workaround for this that involves a content and data presenter. It’s a little heavy for my taste.

However that solution and this post detailing Microsoft’s failure to fix this known issue started me on the following solution:

<Setter Property="ToolTip" Value="{Binding 
    RelativeSource={RelativeSource Self}, 
    Path=(Validation.Errors).CurrentItem, 
    Converter={StaticResource ValidationErrorConverter}}"/>                        

The CurrentItem property is null if there is nothing in the collection – so no exceptions are thrown evaluating it if there are no errors. It is an instance of ValidationError, however, and doesn't have a useful ToString() override. So I'm using an IValueConverter to get the error message. Here it is in its entirety:

/// <summary>
/// Helps with avoiding of exceptions when setting a tooltip to the validation error
/// </summary>
/// <remarks>
/// Bind to Path=(Validation.Errors).CurrentItem, and use this converter
/// </remarks>
public class ValidationErrorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
        System.Globalization.CultureInfo culture)
    {
        var error = value as ValidationError;

        // If we have an error, return its content
        if (error != null)
            return error.ErrorContent;
        else
            return "";
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
        System.Globalization.CultureInfo culture)
    {
        // Can't convert back to a ValidationError
        throw new Exception("The method or operation is not implemented.");
    }
}

Tags: