Search code examples
c#wpfitemscontrolivalueconverter

Pass a property from an ItemsControl source class into a converter parameter


I have an ItemsControl like this

<ItemsControl Name="itemsControl">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ToggleButton Style="{StaticResource ToggleButtonInputControl}">
                <Grid>
                    <local:CustomTextBox
                        StringInTextBox="{Binding
                            Path=UIntProperty,
                            Mode=TwoWay,
                            UpdateSourceTrigger=PropertyChanged,
                            Converter={local:UIntConverter Param=123}}"/>
                </Grid>
            </ToggleButton>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The source of the ItemsControl is set in code and it is an ObservableCollection of type MyClass which has a property named UIntProperty. The converter UIntConverter extends MarkupExtension so I can use it like above and has a uint property called Param.

Objective

The objective is to edit UIntProperty using the CustomTextBox. If a no numeric value is typed, the value UIntProperty should not change and if the StringInTextBox is empty, the value should be set to 0 and display that value. More conditions are required for a float type, but the case of a uint should give the idea of the final objective.

I have tried

I can use the dependency property StringInTextBox with the CoerceValueCallback and PropertyChangedCallback, but according to my test, the bindings trigger first, so the UIntConverter performs first and that is where the UIntProperty is set. I could use an intermediary string property to bind to StringInTextBox and use the set get methods to validate, but I would have to implement that intermediary string to every property in MyClass (many properties). I could also make the UIntConverter return null to know when to keep the original value, but I would have to make the UIntProperty nullable across the code.

I figured I could use the UIntConverter and pass the original UintProperty so the converter can return that value when needed, but I don't know how to pass the UIntProperty to the Param, I thought UIntProperty would be in the same "scope" as for the binding but Param=UIntProperty in the code above doesn't work.

Question

So, the question is how can I pass UIntProperty to Param or as the converter parameter? I don't know if I can pass a value to Param when the UIntConverter is a StaticResource, if possible how to do that?

Thanks in advance.

UPDATE:

I solve the problem returning Binding.DoNothing from ConvertBack. Looks like the ConverterParameter is not meant to pass values that depend on each object, but to indicate more precisely what kind of conversion is going to be performed, like a type of filter.


Solution

    1. If the value "Param=123" is configurable at runtime, but the same for all Items, then it can be set in resources and received in the Converter Parameter using StaticResource.
    2. If "Param=123" is configured once when creating a UIntConverter instance, then you can add a property to it and set its value when creating the instance.
    3. If "Param=123" is individual for each Items, then you need to use MultiBinding and MultiConverter.

    Also, in cases of complex logic, you can consider creating an additional property for the CustomTextBox or Attached Property. But in this case, I see it as redundant. If you need some additional logic, it is better to include it in the StringInTextBox property.

    If you provide more clarification on the required functionality and application, then I would try to make an example implementation for you.

    Addition to the answer.
    An example of a Number to String converter that does not change the source property in case of an error converting from a string to the source type.

    In case of an error, the special value Binding.DoNothing is returned, which tells the binding system not to assign the value returned by the converter.

    using System;
    using System.Globalization;
    using System.Windows.Data;
    using System.Windows.Markup;
    
    namespace CommonCore.Converters
    {
        [ValueConversion(typeof(uint), typeof(string))]
        [ValueConversion(typeof(int), typeof(string))]
        [ValueConversion(typeof(byte), typeof(string))]
        [ValueConversion(typeof(sbyte), typeof(string))]
        [ValueConversion(typeof(short), typeof(string))]
        [ValueConversion(typeof(ushort), typeof(string))]
        [ValueConversion(typeof(long), typeof(string))]
        [ValueConversion(typeof(ulong), typeof(string))]
        [ValueConversion(typeof(double), typeof(string))]
        [ValueConversion(typeof(float), typeof(string))]
        [ValueConversion(typeof(decimal), typeof(string))]
        public class NumberToStringConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                return System.Convert.ToString(value, culture);
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                string text = (string)value;
                if (string.IsNullOrWhiteSpace(text))
                    return System.Convert.ChangeType(0, targetType, culture);
                try
                {
                    return System.Convert.ChangeType(value, targetType, culture);
                }
                catch
                {
                    return Binding.DoNothing;
                }
            }
    
            private NumberToStringConverter() { }
    
            public static NumberToStringConverter Instance { get; } = new();
    
        }
        [MarkupExtensionReturnType(typeof(NumberToStringConverter))]
        public class NumberToStringExtension : MarkupExtension
        {
            public override object ProvideValue(IServiceProvider serviceProvider)
            {
                return NumberToStringConverter.Instance;
            }
        }
    }