1

I've created an IMarkupExtension for an ImageSource which gets a specified symbol from a specified font and displays it in a specified color with a specified height. Most of the times the icon name is static and I write into the XAML directly. But sometimes there are lists of things that have a property which determines which icon should be used. For this case it is necessary that the icon name is bindable.

Here is (more or less) the current state of my FontImageExtension:

[ContentProperty(nameof(IconName))]
public class FontImageExtension : IMarkupExtension<ImageSource>
{
    private readonly IconFontService iconFontService;

    [TypeConverter(typeof(FontSizeConverter))]
    public double Size { get; set; } = 30d;

    public string IconName { get; set; }

    public Color Color { get; set; }

    public string FontFamily { get; set; }

    public FontImageExtension()
    {
        iconFontService = SomeKindOfContainer.Resolve<IconFontService>();
    }

    public ImageSource ProvideValue(IServiceProvider serviceProvider)
    {
        if (string.IsNullOrEmpty(IconName))
            return null;

        IconFont iconFont = iconFontService.GetIconFont();

        if (iconFont == null)
            return null;

        string glyphCode = iconFont.GetGlyphCode(IconName);

        if (string.IsNullOrEmpty(glyphCode))
            return null;

        FontImageSource fontImageSource = new FontImageSource()
        {
            FontFamily = iconFont.GetPlatformLocation(),
            Glyph = glyphCode,
            Color = this.Color,
            Size = this.Size,
        };

        return fontImageSource;
    }

    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
    {
        return ProvideValue(serviceProvider);
    }
}

Most of the time I use it like this in XAML (which already works perfectly):

<Image Source="{m:FontImage SomeIcon, Color=Black, Size=48}"/>

But for dynamic UI (e.g. lists or something) I need it like this:

<CollectionView ItemsSource={Binding SomeCollection}">
    <CollectionView.ItemTemplate>
        <StackLayout>
            <Image Source="{m:FontImage IconName={Binding ItemIcon}, Color=Black, Size=48}"/>
            <Label Text="{Binding ItemText}"/>
        </StackLayout>
    </CollectionView.ItemTemplate>
</CollectionView>

How can I make this work?

Christoph Mett
  • 369
  • 3
  • 16

2 Answers2

1

It seems you could not use IMarkupExtension with bindable properties .As a 'Binding' can only be set on BindableProperty of a BindableObject.The problem is that MarkupExtension class does not derive from BindableObject, that's why it is not possible to set binding on it's properties.Though you let it implement BindableObject,it still could not work.

A workaround is using Value Converters.

For example:

class ImageSourceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var p = parameter.ToString().Split('|');
        string colorName = p[0];
        ColorTypeConverter colorTypeConverter = new ColorTypeConverter();
        Color color = (Color)colorTypeConverter.ConvertFromInvariantString(colorName);
        double fontSize = double.Parse(p[1]);

        //didn't test this here.
        IconFontService iconFontService = SomeKindOfContainer.Resolve<IconFontService();
        IconFont iconFont = iconFontService.GetIconFont();
        if (iconFont == null)
            return null;

        string glyphCode = iconFont.GetGlyphCode((string)value);
        if (string.IsNullOrEmpty(glyphCode))
            return null;

        FontImageSource fontImageSource = new FontImageSource()
        {
            FontFamily = iconFont.GetPlatformLocation(),
            Glyph = glyphCode,
            Color = color,
            Size = fontSize,
        };
        return fontImageSource;
    }

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

use in your xaml:

<ContentPage.Resources>
    <ResourceDictionary>
        <local:ImageSourceConverter x:Key="imageConvert" />
    </ResourceDictionary>
</ContentPage.Resources>

<CollectionView ItemsSource={Binding SomeCollection}">
  <CollectionView.ItemTemplate>
    <StackLayout>
        <Image Source="{Binding Name,Converter={StaticResource imageConvert}, ConverterParameter=Color.Black|48}"/>
        <Label Text="{Binding ItemText}"/>
    </StackLayout>
  </CollectionView.ItemTemplate>
</CollectionView>

Also see failed attempt declaring BindableProperty: IMarkupExtension with bindable property does not work and a more ambitious approach to a somewhat different situation - might be relevant: MarkupExtension for binding.

Leo Zhu
  • 15,726
  • 1
  • 7
  • 23
1

I solved this problem by creating a converter (like @Leo Zhu suggested) but in addition to the IMarkupExtension. So my extension stays as is (with the addition of a constant value that gets used in the converter) and the code for the converter is as follows:

public class FontIconConverter : IValueConverter, IMarkupExtension
{
    private IServiceProvider serviceProvider;

    public Color Color { get; set; }

    [TypeConverter(typeof(FontSizeConverter))]
    public double Size { get; set; } = FontIconExtension.DefaultFontSize;

    public string FontFamily { get; set; }

    public FontIconConverter()
    {
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is string iconName))
            return null;

        var fontIcon = new FontIconExtension()
        {
            IconName = iconName,
            Color = Color,
            Size = Size,
            FontFamily = FontFamily,
        };

        return fontIcon.ProvideValue(serviceProvider);
    }

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

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

It then can be used like this:

<Image Source="{Binding IconNameProperty, Converter={c:FontIconConverter Color=Black, Size=48}}"/>

And for static values it stays like this:

<Image Source="{m:FontImage SomeIconsName, Color=Black, Size=48}"/>
Christoph Mett
  • 369
  • 3
  • 16
  • That's a good idea which let the FontIconConverter implement the IMarkupExtension directly. – Leo Zhu Sep 17 '20 at 09:10
  • If my answer is helpful to you,could you mark it as an accept answer,or you could mark yourself.It maybe help others. – Leo Zhu Sep 22 '20 at 09:26