Search code examples
c#wpfxamlmarkup-extensionsimultivalueconverter

ObservableCollection<T> on xaml


I'm currently working on a CustomConverter on WPF. Which is like a Generic convertion.

Reading on this blog, found a way to simplify the xaml.

So the converter looks like this:

public CustomConverter : MarkupExtension, IMultiValueConverter {

    public ParametersCollection Parameters { get; set; }

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        if(Parameters == null)
            return Binding.DoNothing;
        //Convertion Logic
        //...
    }
    public override object ProvideValue(IServiceProvider serviceProvider) {
        return new CustomConverter();
    }
}
public class ParametersCollection : ObservableCollection<object> {
}

And in my Xaml file, have the following:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                 xmlns:primitives="clr-namespace:System;assembly=mscorlib"
                 xmlns:converter="clr-namespace:NS.Converters">
    <Label>
        <Label.Visibility>
            <MultiBinding>
                <converter:CustomConverter>
                    <converter:CustomConverter.Parameters>
                        <converter:ParametersCollection>
                            <primitives:String>Param1</primitives:String>
                            ...
                        </converter:ParametersCollection>
                    </converter:CustomConverter.Parameters>
                </converter:CustomConverter>
            </MultiBinding>
            <!--Bindings start here -->
        </Label.Visibility>
    </Label>
</UserControl>

So when debbuging the code, the Parameters property is empty (null), so the xaml is not populating the collection. So my question is, how to popultate Parameters by using only xaml, no C# code.


Solution

  • If you put a breakpoint on the first line of your Convert method, you'll find that Parameters is always null. That's because you never initialize it. The solution is to initialize that property in the constructor (or right in the declaration, if you're in C#6).

    Parameters doesn't need to be ObservableCollection<T>, because nobody will ever bind to it, and it doesn't need to be a non-generic subclass either. Lastly, ProvideValue() should provide the actual instance that has the Parameters you were given from the XAML. If you return a new instance with an empty Parameters collection, it won't have any Parameters. ProvideValue() is called after you're created and initialized. The purpose isn't to provide a new instance of your own class, but for markup extensions like StaticResource or DynamicResource that take their parameters and use those to create or retrieve an instance of something other than themselves.

    public class CustomConverter : MarkupExtension, IMultiValueConverter
    {
        public CustomConverter()
        {
            Parameters = new List<Object>();
        }
        public List<Object> Parameters { get; set; }
    
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (Parameters == null)
                return Binding.DoNothing;
    
            //  Just something that returns something, for testing
            //  Replace with your own convertion logic. 
            return String.Join(";", Parameters);
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            return null;
        }
    
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }