| File: FormattedString.cs | Web Access |
| Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls) |
#nullable disable using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; namespace Microsoft.Maui.Controls { /// <include file="../../docs/Microsoft.Maui.Controls/FormattedString.xml" path="Type[@FullName='Microsoft.Maui.Controls.FormattedString']/Docs/*" /> [ContentProperty("Spans")] [TypeConverter(typeof(FormattedStringConverter))] public class FormattedString : Element { readonly SpanCollection _spans = new SpanCollection(); internal event NotifyCollectionChangedEventHandler SpansCollectionChanged; /// <include file="../../docs/Microsoft.Maui.Controls/FormattedString.xml" path="//Member[@MemberName='.ctor']/Docs/*" /> public FormattedString() => _spans.CollectionChanged += OnCollectionChanged; protected override void OnBindingContextChanged() { base.OnBindingContextChanged(); for (int i = 0; i < Spans.Count; i++) SetInheritedBindingContext(Spans[i], BindingContext); } /// <include file="../../docs/Microsoft.Maui.Controls/FormattedString.xml" path="//Member[@MemberName='Spans']/Docs/*" /> public IList<Span> Spans => _spans; public static explicit operator string(FormattedString formatted) => formatted.ToString(); public static implicit operator FormattedString(string text) => new FormattedString { Spans = { new Span { Text = text } } }; /// <include file="../../docs/Microsoft.Maui.Controls/FormattedString.xml" path="//Member[@MemberName='ToString']/Docs/*" /> public override string ToString() => string.Concat(Spans.Select(span => span.Text)); void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (object item in e.OldItems) { var bo = item as Span; if (bo != null) { bo.Parent?.RemoveLogicalChild(bo); bo.PropertyChanging -= OnItemPropertyChanging; bo.PropertyChanged -= OnItemPropertyChanged; } } } if (e.NewItems != null) { foreach (object item in e.NewItems) { var bo = item as Span; if (bo != null) { this.AddLogicalChild(bo); bo.PropertyChanging += OnItemPropertyChanging; bo.PropertyChanged += OnItemPropertyChanged; } } } OnPropertyChanged(nameof(Spans)); SpansCollectionChanged?.Invoke(sender, e); } void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) => OnPropertyChanged(nameof(Spans)); void OnItemPropertyChanging(object sender, PropertyChangingEventArgs e) => OnPropertyChanging(nameof(Spans)); class SpanCollection : ObservableCollection<Span> { protected override void InsertItem(int index, Span item) => base.InsertItem(index, item ?? throw new ArgumentNullException(nameof(item))); protected override void SetItem(int index, Span item) => base.SetItem(index, item ?? throw new ArgumentNullException(nameof(item))); protected override void ClearItems() { var removed = new List<Span>(this); base.ClearItems(); base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } } private sealed class FormattedStringConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string); public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => destinationType == typeof(string); public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { if (value is string strValue) { return (FormattedString)strValue; } throw new NotSupportedException(); } public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { if (value is FormattedString formattedStr) { return (string)formattedStr; } throw new NotSupportedException(); } } } }