Search code examples
c#wpfdata-bindingdependency-propertiesivalueconverter

Binding for a property of a Converter


I have a problem in Binding the value of the parameter of a class. I defined the below converter to change the values of the visual components using a unit converter class.

[ValueConversion(typeof(double), typeof(double))]
    public class VisualUnitConverter : DependencyObject, IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null) return null;
            if (UnitCombo == UnitCombination.uc_None) return null;
            if (!Enum.TryParse(parameter.ToString(), out PhysicalQuantities physicalQuantity))
                { throw new ArgumentException("Unsupported physical quantity", nameof(parameter));}
            
            return ConverterClass.ConvertValueFromDefault((double)value, physicalQuantity, UnitCombo)
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        public static readonly DependencyProperty UnitComboProperty =
            DependencyProperty.Register("UnitCombo", typeof(UnitCombination), typeof(VisualUnitConverter));

        public UnitCombination UnitCombo
        {
            get { return (UnitCombination)GetValue(UnitComboProperty); }
            set { SetValue(UnitComboProperty, value); }
        }
    }

the definition of the enum types and ConverterClass:

public class ConverterClass
    {
        public static double ConvertValueFromDefault(double value, PhysicalQuantities physical, UnitCombination unitCombination)
        {
            double res = value;
            // do some calculations on res based on input parameters
            return res;
        }

        public enum PhysicalQuantities
        {
            pqNone,
            pqLength,
            pqForce,
            pqTemperature
        }
        public enum UnitCombination
        {
            uc_None,
            uc_N_mm_C,
            uc_KN_mm_C
        }
    }

I used the VisualUnitConverterin my window like this:

<Window
 ...>
<Window.Resources>
<units:VisualUnitConverter x:Key="ValueConverter" UnitCombo="{Binding UnitCombination, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</Window.Resources>
<Grid>
<TextBlock Text="{Binding TextValue, Converter={StaticResources ValueConverter}, ConverterParameter={x:Static units:PhysicalQuantites.pqLength}}" />
</Grid>
</Window>

I Set the DataContext of the Window in the Code Behind and the 'TextValue' and 'UnitCombination' are properties of the DataContext class. Also PropertyChange applied for these properties and works fine. After all, when the Convert method tries to convert the value, the UnitCombo property is always set to uc_None . If I don't use the Binding for this property in the XAML code, the value applies correctly. Can anyone help me find the propblem?

Solution for correct DataBinding and Definition of classes.


Solution

  • As pointed in comment the converter doesn't have data context. Even if you would inherit it from FrameworkElement, which has DatatContext property, it will not be set, because of converter is not a part of visible or logical tree.

    There is a trick to make it works. If you inherit your converter from Freezable, then it should work as expected. Implementation of Freezable makes somehow the binding work.

    public class VisualUnitConverter : Freezable, IValueConverter
    {
        protected override Freezable CreateInstanceCore()
        {
            return new VisualUnitConverter();
        }
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
                return null;
            if (UnitCombo == UnitCombination.uc_None)
                return null;
            if (!Enum.TryParse(parameter.ToString(), out PhysicalQuantities physicalQuantity))
            { throw new ArgumentException("Unsupported physical quantity", nameof(parameter)); }
    
            return ConverterClass.ConvertValueFromDefault((double)value, physicalQuantity, UnitCombo)
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    
        public static readonly DependencyProperty UnitComboProperty =
            DependencyProperty.Register("UnitCombo", typeof(UnitCombination), typeof(VisualUnitConverter));
    
        public UnitCombination UnitCombo
        {
            get { return (UnitCombination)GetValue(UnitComboProperty); }
            set { SetValue(UnitComboProperty, value); }
        }
    }
    

    As alternative you can put ViewModel to the resources and use it in the binding as StaticResource:

    <Window.Resources>
        <vmNameSpace:YourVMClass x:Key="vm"/>
        <units:VisualUnitConverter x:Key="ValueConverter" UnitCombo="{Binding UnitCombination, Source={StaticResource vm}}"/>
        </Window.Resources>
    <Window.DataContext>
        <StaticResource ResourceKey="vm"/>
    </Window.DataContext>