Xamarin Forms doesn’t have a Label with a Bindable FormattedString. For example, if you want a bindable bold word in the middle of a sentence in a Label, it’s very hard to design it with common control. For that, I create my own component for that.
LabelRenderer.cs
using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Runtime.CompilerServices; using Xamarin.Forms; namespace PSC.Controls { public class Label : Xamarin.Forms.Label { protected override void OnBindingContextChanged() { base.OnBindingContextChanged(); UpdateFormattedTextBindingContext(); } protected override void OnPropertyChanged( [CallerMemberName] string propertyName = null) { base.OnPropertyChanged(propertyName); if (propertyName == FormattedTextProperty.PropertyName) UpdateFormattedTextBindingContext(); } private void UpdateFormattedTextBindingContext() { var formattedText = this.FormattedText as FormattedString; if (formattedText == null) return; foreach (var span in formattedText.BindableSpans) span.BindingContext = this.BindingContext; } } [ContentProperty("BindableSpans")] public class FormattedString : Xamarin.Forms.FormattedString { private ObservableCollection<Span> _bindableSpans = new ObservableCollection<Span>(); public IList<Span> BindableSpans { get { return this._bindableSpans; } } public FormattedString() { this._bindableSpans.CollectionChanged += OnCollectionChanged; } private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (var bindableSpan in e.OldItems.Cast<Span>()) base.Spans.Remove(bindableSpan); } if (e.NewItems != null) { foreach (var bindableSpan in e.NewItems.Cast<Span>()) base.Spans.Add(bindableSpan); } } /// <param name="text">To be added.</param> /// <summary> /// Cast a string to a FromattedString that contains /// a single span with no attribute set. /// </summary> /// <returns>To be added.</returns> public static implicit operator FormattedString(string text) { return new FormattedString { BindableSpans = { new Span { Text = text ?? "" } } }; } /// <param name="formatted">To be added.</param> /// <summary> /// Cast the FormattedString to a string, /// stripping all the attributes. /// </summary> /// <returns>To be added.</returns> public static explicit operator string(FormattedString formatted) { return formatted.ToString(); } } [ContentProperty("Text")] public sealed class Span : BindableObject { Xamarin.Forms.Span _innerSpan; public Span() : this(new Xamarin.Forms.Span()) { } public Span(Xamarin.Forms.Span span) { _innerSpan = span; // important for triggering property inheritance from parent Label this.BackgroundColor = this._innerSpan.BackgroundColor; this.FontSize = this._innerSpan.FontSize; this.FontAttributes = this._innerSpan.FontAttributes; this.FontFamily = this._innerSpan.FontFamily; this.ForegroundColor = this._innerSpan.ForegroundColor; this.Text = this._innerSpan.Text ?? ""; } public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(Span), Color.Default); public Color BackgroundColor { get { return (Color)GetValue(BackgroundColorProperty); } set { SetValue(BackgroundColorProperty, value); } } public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create(nameof(FontAttributes), typeof(FontAttributes), typeof(Span), FontAttributes.None); public FontAttributes FontAttributes { get { return (FontAttributes)GetValue(FontAttributesProperty); } set { SetValue(FontAttributesProperty, value); } } public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create(nameof(FontFamily), typeof(string), typeof(Span), string.Empty); public string FontFamily { get { return (string)GetValue(FontFamilyProperty); } set { SetValue(FontFamilyProperty, value); } } public static readonly BindableProperty FontSizeProperty = BindableProperty.Create(nameof(FontSize), typeof(double), typeof(Span), -1.0, BindingMode.OneWay, null, null, null, null, bindable => Device.GetNamedSize( NamedSize.Default, typeof(Label))); [TypeConverter(typeof(FontSizeConverter))] public double FontSize { get { return (double)GetValue(FontSizeProperty); } set { SetValue(FontSizeProperty, value); } } public static readonly BindableProperty ForegroundColorProperty = BindableProperty.Create(nameof(ForegroundColor), typeof(Color), typeof(Span), Color.Default); public Color ForegroundColor { get { return (Color)GetValue(ForegroundColorProperty); } set { SetValue(ForegroundColorProperty, value); } } public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(Span), string.Empty); public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } public Xamarin.Forms.Span InnerSpan { get { return _innerSpan; } } protected override void OnPropertyChanged( [CallerMemberName] string propertyName = null) { base.OnPropertyChanged(propertyName); _innerSpan.BackgroundColor = this.BackgroundColor; _innerSpan.FontSize = this.FontSize; _innerSpan.FontAttributes = this.FontAttributes; _innerSpan.FontFamily = this.FontFamily; _innerSpan.ForegroundColor = this.ForegroundColor; _innerSpan.Text = this.Text ?? ""; } protected override void OnBindingContextChanged() { base.OnBindingContextChanged(); } public static implicit operator Xamarin.Forms.Span(Span bindableSpan) { return bindableSpan.InnerSpan; } public static implicit operator Span( Xamarin.Forms.Span span) { return new Span(span); } } }
In your XAML you use this control like that:
<ctl:Label FontSize="Small" TextColor="#333" HorizontalOptions="FillAndExpand"> <Label.FormattedText> <ctl:FormattedString> <ctl:Span Text="{Binding YourVar1}" FontAttributes="Bold"/> <ctl:Span Text="{Binding YourVar2, StringFormat=' {0}'}"/> </ctl:FormattedString> </Label.FormattedText> </ctl:Label>
Download the code from GitHub.
Happy coding!