6

We are currently in the process of converting a project from version 3.5 to version 4.5 of .NET.

We set a text box IsEnabled flagged using a multi binding with a multi binding converter. Each of the bindings has their own converter.

All worked well in .NET 3.5 but in .NET 4.5 the target type that is passed to the child converter is of type object instead of bool.

Is this a known issue? has MS refactored the multi binding to not pass the target type to child converters.

I created a simplified project that demonstrates the issue. I created the project in VS2008 and then converted it to VS2012 and .NET 4.5.

Window XAML:

<Window x:Class="TestMultiBinding.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestMultiBinding"        
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <local:NotConverter x:Key="NotConverter"/>
        <local:MultiBoolConverter x:Key="MultiBoolConverter"/>
    </Window.Resources>
    <StackPanel>

        <TextBox>
            <TextBox.IsEnabled>
                <MultiBinding Converter="{StaticResource MultiBoolConverter}">
                    <Binding Path="ConditionOne" />
                    <Binding Path="ConditionTwo" Converter="{StaticResource NotConverter}"/>
                </MultiBinding>
            </TextBox.IsEnabled>
        </TextBox>


    </StackPanel>
</Window>

c#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Globalization;

namespace TestMultiBinding
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            DataContext = new ViewModel();
        }
    }

    public class ViewModel
    {
        public bool ConditionOne { get { return true; } }
        public bool ConditionTwo { get { return false; } }
    }

    /// <summary>
    /// Converts a boolean to its inverse (useful for radio buttons).
    /// </summary>
    [ValueConversion(typeof(bool), typeof(bool))]
    public class NotConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (targetType != typeof(bool) && targetType != typeof(bool?)) { throw new ArgumentException("Can only convert booleans.", "targetType"); }

            //return !(bool)value;
            return !true.Equals(value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Convert(value, targetType, parameter, culture);
        }
    }

    /// <summary>
    /// Converts multiple boolean values to one. Uses AND by default. Possible extension: Pass the desired operation as parameter
    /// </summary>
    [ValueConversion(typeof(bool), typeof(bool))]
    public class MultiBoolConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            try
            {
                // todo: support other operations like OR, XOR
                return values.Cast<bool>().Aggregate(true, (res, cur) => res && cur);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.TraceError("MultiBoolConverter({0}): {1}", parameter, ex.Message);
                return DependencyProperty.UnsetValue;
            }
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            System.Diagnostics.Trace.TraceError("MultiBoolConverter: does not support TwoWay or OneWayToSource bindings.");
            return null;
        }
    }

}
ghnz
  • 190
  • 6
  • Hi, are you trying to say that Convert method looks differently in 3.5 than 4.5? I cannot see any differences. Are you talking about ValueConversationAttribute? When implementing the IValueConverter interface, it is a good practice to decorate the implementation with a ValueConversionAttribute attribute to indicate to development tools the data types involved in the conversion. That is just an attribute used by Visual Studio and nothing more. – dev hedgehog Sep 30 '13 at 07:35
  • I am saying the targertType passed to the convert function has changed in somewhere between 3.5 and 4.5. This is only when the convert is called in a binding that is part of a multi-binding as above. The code sample I have attached works when using 3.5 but throws the exception in 4.5 – ghnz Sep 30 '13 at 19:00
  • What type you get then? – dev hedgehog Sep 30 '13 at 20:47
  • @ghnz Did you find a work around that didn't sacrifice your error checking of the target type? – denver Jul 07 '15 at 20:06

1 Answers1

1

Is there a reason you are testing that the targetType is bool?

I'm surprised it worked in 3.5, as the NonConverter is converting from bool to object (as the MultiBinding takes an array of object as it's inout).


I did some digging using reflector, and the underlying logic did change.

This is from the internal void TransferValue(object newValue, bool isASubPropertyChange) method of BindingExpression

In 3.5:

internal void TransferValue(object newValue, bool isASubPropertyChange)
{
  DependencyObject targetElement = this.TargetElement;
  if (targetElement == null || this.Worker == null)
    return;
  Type propertyType = this.TargetProperty.PropertyType;

In 4.5, all calls to propertyType are replaced by the below definition of effectiveTargetType:

internal void TransferValue(object newValue, bool isASubPropertyChange)
{
  DependencyObject targetElement = this.TargetElement;
  if (targetElement == null || this.Worker == null)
    return;
  Type effectiveTargetType = this.GetEffectiveTargetType();
...
}

internal Type GetEffectiveTargetType()
{
  Type type = this.TargetProperty.PropertyType;
  for (BindingExpressionBase bindingExpressionBase = this.ParentBindingExpressionBase; bindingExpressionBase != null; bindingExpressionBase = bindingExpressionBase.ParentBindingExpressionBase)
  {
    if (bindingExpressionBase is MultiBindingExpression)
    {
      type = typeof (object);
      break;
    }
  }
  return type;
}

I'm not sure what TargetProperty is set to in this case, but you can see why it's now being set to object for MultiBindings.

And, FYI, it appears this change occurred in .NET 4.0.

Rob H
  • 1,840
  • 16
  • 25