Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IValueConverter with MarkupExtension

Recently I read about an IValueConverter which also inherits from MarkupExtension. It was something like:

internal class BoolToVisibilityConverter : MarkupExtension, IValueConverter
{
    private static BoolToVisibilityConverter converter;
    public BoolToVisibilityConverter()
    {
    }
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool)
        {
            if ((bool)value)
            {
                return Visibility.Visible;
            }
        }
        return Visibility.Collapsed;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Visibility)
        {
            Visibility visibility = (Visibility)value;
            if (visibility == Visibility.Collapsed)
            {
                return false;
            }
        }
        return true;
    }
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return converter ?? (converter = new BoolToVisibilityConverter());
    }
}

The usage than looks like:

<Button Content="Delete" Visibility="{Binding CanDelete, UpdateSourceTrigger=PropertyChanged, Converter={local:BoolToVisibilityConverter}"/>

I was used to use converters from a Resource like:

<loc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
...
<Button Content="Delete" Visibility="{Binding CanDelete, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BoolToVisibilityConverter}"/>

My first question now is: What is the better way? What advantages does it have if I'm using the MarkupExtension-Version (Beside the usage is easier to type)?

I also saw a very similar implementation which looks like:

internal class BoolToVisibilityConverter : MarkupExtension, IValueConverter
{
    public BoolToVisibilityConverter()
    {
    }
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool)
        {
            if ((bool)value)
            {
                return Visibility.Visible;
            }
        }
        return Visibility.Collapsed;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Visibility)
        {
            Visibility visibility = (Visibility)value;
            if (visibility == Visibility.Collapsed)
            {
                return false;
            }
        }
        return true;
    }
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;        
    }
}

If I understand it right, the first solution only creates one instance of this converter. The second one creates for every XAML a new instance of this converter, right?

like image 311
Tomtom Avatar asked Feb 13 '15 13:02

Tomtom


2 Answers

One massive advantage of using MarkupExtension which I have never seen being used online is the fact it can allow you pass values to the converter which could be used as argument or return values, for example:

public class CustomNullToVisibilityConverter : MarkupExtension, IValueConverter
{
    public object NullValue { get; set; }
    public object NotNullValue { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return NullValue;

        return NotNullValue;
    }

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

Usage:

...
Visibility="{Binding Property, 
            Converter={cnv:CustomNullToVisibilityConverter 
                       NotNullValue=Visible, NullValue=Collapsed}}" />
...

Be sure to reference the namespace of the converter in the .xaml.


Edit:

One thing I forgot to mention is that yes you are correct in the fact that this method would create a new instance of the converter each time it's used which is one downside.

However there nothing to stop you adding a converter with MarkupExtension to a resource dictionary - that way it will only be instanced once. Like so:

<cnv:CustomNullToVisibilityConverter x:Key="NullToVisibilityConverter"
        NotNullValue=Visible, NullValue=Collapsed />
...
Visibility="{Binding Property, Converter={StaticResource NullToVisibilityConverter}" />
...
like image 75
Alfie Avatar answered Nov 14 '22 03:11

Alfie


The only (slight) advantage that the markup extension is providing in this case is more concise XAML syntax.

Instead of this:

<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
...
{Binding SomeBooleanProperty, Converter={StaticResource BooleanToVisibilityConverter}}

you can have this:

{Binding SomeBooleanProperty, Converter={my:BoolToVisibilityConverter}}

In my opinion it's not really worth it. If you were that bothered about saving keystrokes you could just shorten the key used to reference the converter:

<BooleanToVisibilityConverter x:Key="btvc" />
...
{Binding SomeBooleanProperty, Converter={StaticResource my:btvc}}

As the ProvideValue method of the markup extension is an instance method, it can only be called once an instance of the class has been created. As the class is both a markup extension and a converter, both variants of the code will create a converter each time. The only difference is that the first variant will always return the same converter: it won't however, stop another converter from being created.

like image 10
Steven Rands Avatar answered Nov 14 '22 04:11

Steven Rands