0

I was trying to chain converters as Town's answer in Is there a way to chain multiple value converters in XAML??

I like to make individual converters more strict by having targetType check as well :-

if (targetType != typeof(bool))
        throw new InvalidOperationException("The target must be a     
boolean");

But the chain fails as the end target type is different from the target at each stage.

I can remove the type check to make less strict as given in most of examples on SO, but I would prefer a chaining which respects each converter's type check as well. E.g. for better unit testing etc.

Also the interface IValueConverter doesn't expose the target type, I find it difficult to add that check myself.

 public class InverseBooleanConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
        if (targetType != typeof(bool))
            throw new InvalidOperationException("The target must be a boolean");

        if (!(value is bool))
            throw new ArgumentException("Argument 'value' must be of type bool");

        return !(bool)value;
        }
         ....
     }

[ValueConversion(typeof(bool), typeof(Visibility))]
public class VisibilityFromBoolConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
        if (targetType != typeof(Visibility))
            throw new InvalidOperationException("The target must be a Visibility");

        if (!(value is bool))
            throw new ArgumentException("Argument 'value' must be of type bool");

        var isVisible = (bool)value;
        return isVisible ? Visibility.Visible : Visibility.Collapsed;
        }
        ....
     }

And the composite is like :-

            <Converters:ValueConverterGroup x:Key="InvertAndVisible">
                  <Converters:InverseBooleanConverter />
                  <Converters:VisibilityFromBoolConverter />
            </Converters:ValueConverterGroup>

But I get exception "The target must be a boolean" from InverseBooleanConverter as it expects target to be bool instead of Visibility (the end target of chain).

Community
  • 1
  • 1
user2224280
  • 29
  • 1
  • 7

2 Answers2

0

I think you may have misunderstood the targetType parameter. According to the doco, it is the type of the target bound property.

This means that for your InverseBooleanConverter the target type will be a System.Visibility type.

For this sort of checking you should check the type of the incoming (bound) object:

if (value != null && value.GetType() != typeof(bool))
    throw new InvalidOperationException("The target must be a boolean");

But.... I would strongly suggest you don't throw exceptions from your converters - that can slow UI rendering down massively and they can be incredibly difficult to track down when you have a screenful of goodness (e.g. you have a grid with a couple of thousand rows in it, and one of the templated datagrid cells throws an exception - how are you going to identify it?). If you insist on throwing exceptions then at least surround it with an #if DEBUG define so that it isn't in your release code. Instead you should return DependencyProperty.UnsetValue if your converter cannot successfully convert a value. This ensures you don't get hard to track runtime exceptions, and it also ensures the binding subsystem can use things like the FallbackValue.

slugster
  • 49,403
  • 14
  • 95
  • 145
  • Thanks for suggesting that exceptions from converters may hog down the application, but it's very much helpful during the development as binding errors are not that verbose, so good to wrap in #if debug ! But coming to type check the target type check is another check than incoming value check. In your code you checking the incoming value and throwing the message about target. – user2224280 Aug 25 '15 at 05:04
  • @user2224280 Maybe you misunderstood my answer... how can you possibly do a type check on `targetType` when each converter operates in isolation, all you are doing is chaining their results. Mark has shown you a way, but believe me that breaks so many rules it isn't funny - just because it *can* be done doesn't mean it *should* be done. The moment you use that approach in a list type control your application will take an immediate performance hit. But it's over to you - if you want to use that approach then go for it, but it's not the way converters were designed to be used. – slugster Aug 25 '15 at 08:52
  • What I want is that individual converters are reusable so that they are doing enough checks when invoked individually or when combined together. I will see whether to keep this check in prod or not, as I am not adding those in list controls as of now.Thanks for suggestion ! – user2224280 Aug 25 '15 at 23:43
0

The original ValueConverterGroup code passes the final targetType into each stage, which is why your checks are failing. All you need to do is modify that behaviour to pass in the next converters targetType instead:

[ValueConversion(typeof(bool), typeof(Visibility))]
public class ValueConverterGroup : List<IValueConverter>, IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        for (int i = 0; i < this.Count(); i++)
        {
            var targ = (i == this.Count() - 1) 
                ? targetType 
                : (this[i + 1].GetType().GetCustomAttributes(typeof(ValueConversionAttribute), false).First() as ValueConversionAttribute).SourceType;
            value = this[i].Convert(value, targ, parameter, culture);
        }
        if (value.GetType() != (this.GetType().GetCustomAttributes(typeof(ValueConversionAttribute), false).First() as ValueConversionAttribute).TargetType)
            throw new InvalidOperationException("Last target must be of type " + targetType.Name);
        return value;
    }

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

    #endregion      
}
Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • Reflection is bit heavy thing, so I want to avoid that. But this works as what I wanted exactly in chaining. To me looks like perfect example of pick any two from quality, time and cost :(. Thanks anyway, wish there is something possible natively using xaml. – user2224280 Aug 25 '15 at 06:27
  • Well the list itself presumably won't change very often so you could always derive it from ObservableCollection instead and in the OnCollectionChanged override flag everything as dirty. Then in the Convert function only do the reflection and checks when dirty is true and cache the TargetTypes for subsequent calls. The fourth variable in your equation is complexity :) – Mark Feldman 10 mins ago – Mark Feldman Aug 25 '15 at 07:17