Search code examples
c#wpftextblockbold

How do I bold portions of a bound TextBlock?


I have a TextBlock bound to a property. However, I'd like to bold certain words within the text. What is the easiest way to do this? FYI, I'm new to WPF.


Solution

  • Bolding individual words involves actually creating more inline elements, so you can't just bind a string to the TextBlock's Text and do this.

    What I've done for this in the past is created a subclass of TextBlock which has a custom property that I bind to. When this property is bound I clear the Inlines of the base TextBlock and then use an algorithm that parses the new string value creating either plain Runs, Bolds, Hyperlinks etc.

    Here's some sample code which I wrote for my experimental Twitter client which detects URLs, emails and @ pattern and creates hyperlinks for them. Regular text is inlined as normal runs:

    [ContentProperty("StatusText")]
    public sealed class StatusTextBlock : TextBlock
    {
        #region Fields
    
        public static readonly DependencyProperty StatusTextProperty = DependencyProperty.Register(
                                                                                        "StatusText", 
                                                                                              typeof(string),
                                                                                        typeof(StatusTextBlock),
                                                                                        new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.None, StatusTextBlock.StatusTextPropertyChangedCallback, null));
        private static readonly Regex UriMatchingRegex = new Regex(@"(?<url>[a-zA-Z]+:\/\/[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,5}(:[0-9]{1,5})?([a-zA-Z0-9_\-\.\~\%\+\?\=\&\;\|/]*)?)|(?<emailAddress>[^\s]+@[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,5})|(?<toTwitterScreenName>\@[a-zA-Z0-9\-_]+)", RegexOptions.Compiled);
    
        #endregion
    
        #region Constructors
    
        public StatusTextBlock()
        {
        }
    
        #endregion
    
        #region Type specific properties
    
        public string StatusText
        {
            get
            {
                return (string)this.GetValue(StatusTextBlock.StatusTextProperty);
            }
    
            set
            {
                this.SetValue(StatusTextBlock.StatusTextProperty, value);
            }
        }
    
        #endregion
    
        #region Helper methods
    
        internal static IEnumerable<Inline> GenerateInlinesFromRawEntryText(string entryText)
        {
            int startIndex = 0;
            Match match = StatusTextBlock.UriMatchingRegex.Match(entryText);
    
            while(match.Success)
            {
                if(startIndex != match.Index)
                {
                    yield return new Run(StatusTextBlock.DecodeStatusEntryText(entryText.Substring(startIndex, match.Index - startIndex)));
                }
    
                Hyperlink hyperLink = new Hyperlink(new Run(match.Value));
    
                string uri = match.Value;
    
                if(match.Groups["emailAddress"].Success)
                {
                    uri = "mailto:" + uri;
                }
                else if(match.Groups["toTwitterScreenName"].Success)
                {
                    uri = "http://twitter.com/" + uri.Substring(1);
                }
    
                hyperLink.NavigateUri = new Uri(uri);
    
                yield return hyperLink;
    
                startIndex = match.Index + match.Length;
    
                match = match.NextMatch();
            }
    
            if(startIndex != entryText.Length)
            {
                yield return new Run(StatusTextBlock.DecodeStatusEntryText(entryText.Substring(startIndex)));
            }
        }
    
        internal static string DecodeStatusEntryText(string text)
        {
            return text.Replace("&gt;", ">").Replace("&lt;", "<");
        }
    
        private static void StatusTextPropertyChangedCallback(DependencyObject target, DependencyPropertyChangedEventArgs eventArgs)
        {
            StatusTextBlock targetStatusEntryTextBlock = (StatusTextBlock)target;
    
            targetStatusEntryTextBlock.Inlines.Clear();
    
            string newValue = eventArgs.NewValue as string;
    
            if(newValue != null)
            {
                targetStatusEntryTextBlock.Inlines.AddRange(StatusTextBlock.GenerateInlinesFromRawEntryText(newValue));
            }
        }
    
        #endregion
    }