jbyrd
jbyrd

Reputation: 5585

Xamarin.Forms UWP - Display HTML in a Label

In Xamarin.Forms, how do I display HTML in a label? In other words, if I have new Label { Text = "some text and <b>some bold text</b>" }, I want it to display as "some text and some bold text".

I know I need to use a custom renderer, and I've implemented in iOS. I just don't know how to implement it in UWP.

Upvotes: 0

Views: 660

Answers (2)

Nico Zhu
Nico Zhu

Reputation: 32775

For your requirement, you could realize this feature via custom LabelRenderer, and in native project you could make HtmlTextBehavior for TextBlock. The following is Behavior processing logical.

public class HtmlTextBehavior : Behavior<TextBlock>
{
    private const string ElementA = "A";
    private const string ElementB = "B";
    private const string ElementBr = "BR";
    private const string ElementEm = "EM";
    private const string ElementI = "I";
    private const string ElementP = "P";
    private const string ElementStrong = "STRONG";
    private const string ElementU = "U";
    private const string ElementUl = "UL";
    private const string ElementLi = "LI";
    private const string ElementDiv = "DIV";
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.Loaded += OnAssociatedObjectLoaded;
        AssociatedObject.LayoutUpdated += OnAssociatedObjectLayoutUpdated;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
        AssociatedObject.LayoutUpdated -= OnAssociatedObjectLayoutUpdated;
    }

    private void OnAssociatedObjectLayoutUpdated(object sender, object o)
    {
        UpdateText();
    }

    private void OnAssociatedObjectLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
        UpdateText();
        AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
    }

    private void UpdateText()
    {
        if (AssociatedObject == null) return;
        if (string.IsNullOrEmpty(AssociatedObject.Text)) return;

        string text = AssociatedObject.Text;

        // Just incase we are not given text with elements.
        string modifiedText = string.Format("<div>{0}</div>", text);

        // reset the text because we will add to it.
        AssociatedObject.Inlines.Clear();
        try
        {
            var element = XElement.Parse(modifiedText);
            ParseText(element, AssociatedObject.Inlines);
        }
        catch (Exception)
        {
            // if anything goes wrong just show the html
            AssociatedObject.Text = text;
        }
        AssociatedObject.LayoutUpdated -= OnAssociatedObjectLayoutUpdated;
        AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
    }
    private static void ParseText(XElement element, InlineCollection inlines)
    {
        if (element == null) return;

        InlineCollection currentInlines = inlines;
        var elementName = element.Name.ToString().ToUpper();
        switch (elementName)
        {
            case ElementA:
                var link = new Hyperlink();
                var href = element.Attribute("href");
                if (href != null)
                {
                    try
                    {
                        link.NavigateUri = new Uri(href.Value);
                    }
                    catch (System.FormatException) { /* href is not valid */ }
                }
                inlines.Add(link);
                currentInlines = link.Inlines;
                break;
            case ElementB:
            case ElementStrong:
                var bold = new Bold();
                inlines.Add(bold);
                currentInlines = bold.Inlines;
                break;
            case ElementI:
            case ElementEm:
                var italic = new Italic();
                inlines.Add(italic);
                currentInlines = italic.Inlines;
                break;
            case ElementU:
                var underline = new Underline();
                inlines.Add(underline);
                currentInlines = underline.Inlines;
                break;
            case ElementBr:
                inlines.Add(new LineBreak());
                break;
            case ElementP:
                // Add two line breaks, one for the current text and the second for the gap.
                if (AddLineBreakIfNeeded(inlines))
                {
                    inlines.Add(new LineBreak());
                }

                Span paragraphSpan = new Span();
                inlines.Add(paragraphSpan);
                currentInlines = paragraphSpan.Inlines;
                break;
            case ElementLi:
                inlines.Add(new LineBreak());
                inlines.Add(new Run { Text = " • " });
                break;
            case ElementUl:
            case ElementDiv:
                AddLineBreakIfNeeded(inlines);
                Span divSpan = new Span();
                inlines.Add(divSpan);
                currentInlines = divSpan.Inlines;
                break;
        }
        foreach (var node in element.Nodes())
        {
            XText textElement = node as XText;
            if (textElement != null)
            {
                currentInlines.Add(new Run { Text = textElement.Value });
            }
            else
            {
                ParseText(node as XElement, currentInlines);
            }
        }
        // Add newlines for paragraph tags
        if (elementName == "ElementP")
        {
            currentInlines.Add(new LineBreak());
        }
    }
    private static bool AddLineBreakIfNeeded(InlineCollection inlines)
    {
        if (inlines.Count > 0)
        {
            var lastInline = inlines[inlines.Count - 1];
            while ((lastInline is Span))
            {
                var span = (Span)lastInline;
                if (span.Inlines.Count > 0)
                {
                    lastInline = span.Inlines[span.Inlines.Count - 1];
                }
            }
            if (!(lastInline is LineBreak))
            {
                inlines.Add(new LineBreak());
                return true;
            }
        }
        return false;
    }
}

And then you could add this behavior to Native Control in cusotm renderer.

protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
    base.OnElementChanged(e);
    HtmlTextBehavior behavior = new HtmlTextBehavior();      
    Interaction.GetBehaviors(Control).Add(behavior);
}

I have uploaded code sample. Please check.

Upvotes: 2

Depechie
Depechie

Reputation: 6142

Maybe take a look at this https://blog.pieeatingninjas.be/2017/11/05/creating-a-hyperlinklabel-in-xamarin-forms/... It could be a good starting point to expand upon on what is still missing for your needs?

Currently it only supports hyperlinks, but the way to handle the tag would be the same.

Upvotes: 0

Related Questions