1

I have a situation where I am using a multi-value converter. The values passed to it are themselves being converted.

<MenuItem>
    <MenuItem.IsEnabled>
        <MultiBinding Converter="{StaticResource BooleanAndConverter}">
            <Binding Path="Prop1" Converter="{StaticResource Converter1}" />
            <Binding Path="Prop2" Converter="{StaticResource Converter1}" />
        </MultiBinding>
    </MenuItem.IsEnabled>
</MenuItem

Converter1 contains some error checking to confirm that it is called with a valid target type. It throws an exception if not, as this is a developer error and the situation should be fixed.

The problem is that when Converter1 is used in this context the target type is System.Object. Now the BooleanAndConverter requires values of a certain type (Boolean), so how can I get that type passed as the target type of Converter1?

As requested here is the BooleanAndConverter code:

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        foreach (object value in values)
        {
            if (value.GetType() != typeof(bool))
            {
                throw new ArgumentException("BooleanAndConverter can only be used to convert booleans."); // developer error
            }
        }

        if (targetType != typeof(bool))
        {
            throw new ArgumentException("BooleanAndConverter can only convert to a boolean."); // developer error
        }

        foreach (object value in values)
        {
            if ((bool)value == false)
            {
                return false;
            }
        }
        return true;
    }

Let me restate the question as there seems to be some confusion. Converter1 knows what type it can convert from and to. It throws an exception when called with the wrong types. In this situation the targetType is not getting specified and an exception is being thrown. How do I get the targetType specified correctly? When not used in a multi-binding situation is does always get specified correctly based on what is being converted.

denver
  • 2,863
  • 2
  • 31
  • 45
  • Is it just a boxed boolean value? Have you looked at it in the debugger to determine the underlying type of the object passed in? – Ron Beyer Jul 07 '15 at 19:10
  • post the code for BooleanAndConverter please. – Abin Jul 07 '15 at 19:12
  • @AbinMathew I added the convert function to the question. – denver Jul 07 '15 at 19:43
  • @RonBeyer Not sure what you mean. It is a Type not an object. – denver Jul 07 '15 at 19:45
  • 1
    "How do I get the targetType specified correctly?" Not at all, when Converter1 is used in a Binding within a MultiBinding. There is no target property, and hence no target type other than `object`. – Clemens Jul 07 '15 at 19:53
  • I wouldn't worry about the target type and always return a boolean if both the inputs are boolean. Let the upstream side worry about converting the boolean to whatever it needs or excepting on the wrong type. – Ron Beyer Jul 07 '15 at 19:53
  • @RonBeyer What is the point of targetType then? The converter itself seems the correct place to proactively handle errors using the converter, particularly when used within XAML. – denver Jul 07 '15 at 20:00
  • 1
    It changed for some reason from .NET 3.5 in 4.0, see http://stackoverflow.com/questions/19086116/what-has-happened-to-multibinding-between-net-3-5-and-net-4-5 – Ron Beyer Jul 07 '15 at 20:03
  • @RonBeyer Yes, that question describes the same issue I am experiencing. – denver Jul 07 '15 at 20:07

3 Answers3

1

An possible alternative to some comments using a CommandParameter could be a MarkupExtension. Then you could write your MultiBinding like:

<MultiBinding Converter="{StaticResource BooleanAndConverter}">
  <Binding Path="Prop1" Converter="{conv:DebugTypeCheck, CheckType={x:Type sys:Boolean}" />
  <Binding Path="Prop2" Converter="{StaticResource DebugTypeCheckConverter}" CommandParameters="{x:Type sys:Boolean}" />
</MultiBinding>

On StackOverflow you can find several posts about Markupextension-Converters like here.

Sample implementation for both ways:

public class DebugTypeCheck : MarkupExtension, IValueConverter
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public Type CheckType { get; set; }

    [Conditional("DEBUG")]
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value.GetType() != CheckType)
        {
            throw new ArgumentException(); // developer error
        }

        return value;
    }  

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    } 
}

public class DebugTypeCheckConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value.GetType() != (Type)parameter)
        {
            throw new ArgumentException(); // developer error
        }

        return value;
    }  

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    } 
}
Community
  • 1
  • 1
Frederick
  • 86
  • 5
  • I like this solution as it still allows type checking to help avoid the runtime errors. You can make it so that it does the check against the parameter if one is provided, else it will check against the target type. I ended up taking a slightly different approach. I instead chose to check that the type I wanted to return could safely be cast to the target type. if (!targetType.IsAssignableFrom(typeof(TheTypeIWantToReturn))) {} – denver Jul 14 '15 at 13:07
1

Value converter implementations should do an appropriate default conversion if object is the target type. You will get binding debug errors output if a bound converter is returning the wrong type and you can fix the problem accordingly. You also shouldn't be throwing errors in converters, you should output an informative message via Debug.WriteLine and return DependencyProperty.UnsetValue.

Because of situations like this it is generally not recommended to return more than one type of result based on the targetType parameter and it should be obvious what type of value the converter will return. You can either ignore targetType, or check that it is either the correct type or object. In practice it doesn't really make a difference.

Mike Marynowski
  • 3,156
  • 22
  • 32
0

Seems to me that the code in the converter should be casting the object to whatever type it expects.

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    var desired = value as desiredType;
    if (desired != null)
        //do stuff with 'desired' as a parameter
    else
        // error
 }

I do not know of a way get WPF to pass it in as a specific type. If you want the converter to behave differently for different types you could include the type as a converter parameter, or you could use a multivalueconverter to cast each parameter differently

MatrixManAtYrService
  • 8,023
  • 1
  • 50
  • 61
  • 2
    Its not the type of value that is a problem. value is explicitly passed in. It is the targetType that is not being specified. – denver Jul 07 '15 at 19:46