Reputation: 549
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?
Upvotes: 2
Views: 912
Reputation: 25871
You can actually make your FontImageExtension
markup extension implement (1) BindableObject
, (2) IMarkupExtension
, AND IMultiValueConverter
all at the same time:
[EDIT]
For completeness, I added a refactor so that the FontImage implementation is available to XAML as a markup extension and to C# as an extension method to BindableObject:
/// <summary>
/// This is a FontImage markup extension that has bindable IconName, IconSize, IconColor, and IconFontFamily properties.
///
/// Example usage:
/// <Image Source="{local:FontImage IconName={Binding ...}, IconSize={Binding ...}, IconColor={Binding ...}, IconFontFamily={Binding ...}}" />
/// </summary>
[ContentProperty(nameof(IconName))]
public class FontImageExtension : BindableObject, IMultiValueConverter, IMarkupExtension<BindingBase>
{
//private readonly IconFontService iconFontService;
public static readonly BindableProperty SizeProperty = BindableProperty.Create(nameof(Size), typeof(double), typeof(FontImageExtension));
[TypeConverter(typeof(FontSizeConverter))]
public double Size
{
get { return (double)GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}
public static readonly BindableProperty IconNameProperty = BindableProperty.Create(nameof(IconName), typeof(string), typeof(FontImageExtension));
public string IconName
{
get { return (string)GetValue(IconNameProperty); }
set { SetValue(IconNameProperty, value); }
}
public static readonly BindableProperty ColorProperty = BindableProperty.Create(nameof(Color), typeof(Color), typeof(FontImageExtension));
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create(nameof(FontFamily), typeof(string), typeof(FontImageExtension));
public string FontFamily
{
get { return (string)GetValue(IconNameProperty); }
set { SetValue(FontFamilyProperty, value); }
}
public FontImageExtension()
{
iconFontService = SomeKindOfContainer.Resolve<IconFontService>();
}
public object ProvideValue(IServiceProvider serviceProvider)
=> (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
=> MakeBinding(
new Binding(nameof(Size), BindingMode.OneWay, null, null, null, this),
new Binding(nameof(IconName), BindingMode.OneWay, null, null, null, this),
new Binding(nameof(Color), BindingMode.OneWay, null, null, null, this),
new Binding(nameof(FontFamily), BindingMode.OneWay, null, null, null, this));
public BindingBase MakeBinding(BindingBase SizeBinding, BindingBase IconNameBinding, BindingBase ColorBinding, BindingBase FontFamilyBinding)
=> new MultiBinding()
{
Bindings = new Collection<BindingBase>
{
SizeBinding,
IconNameBinding,
ColorBinding,
FontFamilyBinding
},
Converter = this
};
public object? Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double Size = (double)values[0];
string IconName = (string)values[1];
Color Color = (Color)values[2];
string FontFamily = (string)values[3];
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;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
/// <summary>
/// This is a static library containing FontImage extension methods.
/// </summary>
public static class FontImageMethodExtension
{
/// <summary>
/// This is a FontImage extension method that sets the target property to a FontImageSource with bindable IconName, IconSize, IconColor, and IconFontFamily properties.
///
/// Example C# Usage:
/// Image.SetFontImage(Image.SourceProperty, new Binding("IconSize", ...), new Binding("IconName", ...), new Binding("IconColor", ...), new Binding("IconFontFamily", ...));
/// </summary>
/// <param name="bindable"></param>
/// <param name="targetProperty"></param>
/// <param name="SizeBinding"></param>
/// <param name="IconNameBinding"></param>
/// <param name="ColorBinding"></param>
/// <param name="FontFamilyBinding"></param>
public static void SetFontImage(this BindableObject bindable, BindableProperty targetProperty, BindingBase SizeBinding, BindingBase IconNameBinding, BindingBase ColorBinding, BindingBase FontFamilyBinding)
=> bindable.SetBinding(
targetProperty,
new FontImageExtension() { }.MakeBinding(SizeBinding, IconNameBinding, ColorBinding, FontFamilyBinding)
);
}
Upvotes: 1
Reputation: 549
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}"/>
Upvotes: 1
Reputation: 14956
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.
Upvotes: 1