How to bind a TextBlock to a resource containing formatted text?

前端 未结 7 930
被撕碎了的回忆
被撕碎了的回忆 2020-11-28 08:57

I have a TextBlock in my WPF window.

 
     Some formatted text.
 

When it is r

7条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-11-28 09:09

    I've added hyperlink and image support to Vincents solution:

    public static class FormattedTextBlock
    {
        public static string GetFormattedText(DependencyObject obj)
        {
            return (string)obj.GetValue(FormattedTextProperty);
        }
    
        public static void SetFormattedText(DependencyObject obj, string value)
        {
            obj.SetValue(FormattedTextProperty, value);
        }
    
        public static readonly DependencyProperty FormattedTextProperty =
            DependencyProperty.RegisterAttached("FormattedText",
            typeof(string),
            typeof(FormattedTextBlock),
            new UIPropertyMetadata("", FormattedTextChanged));
    
        static Inline Traverse(string value)
        {
            // Get the sections/inlines
            string[] sections = SplitIntoSections(value);
    
            // Check for grouping
            if(sections.Length.Equals(1))
            {
                string section = sections[0];
                string token; // E.g 
                int tokenStart, tokenEnd; // Where the token/section starts and ends.
    
                // Check for token
                if(GetTokenInfo(section, out token, out tokenStart, out tokenEnd))
                {
                    // Get the content to further examination
                    string content = token.Length.Equals(tokenEnd - tokenStart) ?
                        null :
                        section.Substring(token.Length, section.Length - 1 - token.Length * 2);
    
                    switch(token.ToUpper())
                    {
                        case "":
                        case "":
                            /* Bold text */
                            return new Bold(Traverse(content));
                        case "":
                        case "":
                            /* Italic text */
                            return new Italic(Traverse(content));
                        case "":
                        case "":
                            /* Underlined text */
                            return new Underline(Traverse(content));
                        case "
    ": case "
    ": case "": /* Line 1
    line 2 */ return new LineBreak(); case "": case "": /* {http://www.google.de}Google */ var start = content.IndexOf("{"); var end = content.IndexOf("}"); var url = content.Substring(start + 1, end - 1); var text = content.Substring(end + 1); var link = new Hyperlink(); link.NavigateUri = new System.Uri(url); link.RequestNavigate += Hyperlink_RequestNavigate; link.Inlines.Add(text); return link; case "": case "": /* pack://application:,,,/ProjectName;component/directory1/directory2/image.png */ var image = new Image(); var bitmap = new BitmapImage(new Uri(content)); image.Source = bitmap; image.Width = bitmap.Width; image.Height = bitmap.Height; var container = new InlineUIContainer(); container.Child = image; return container; default: return new Run(section); } } else return new Run(section); } else // Group together { Span span = new Span(); foreach(string section in sections) span.Inlines.Add(Traverse(section)); return span; } } static void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) { Process.Start(e.Uri.ToString()); } /// /// Examines the passed string and find the first token, where it begins and where it ends. /// /// The string to examine. /// The found token. /// Where the token begins. /// Where the end-token ends. /// True if a token was found. static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex) { token = null; endIndex = -1; startIndex = value.IndexOf("<"); int startTokenEndIndex = value.IndexOf(">"); // No token here if(startIndex < 0) return false; // No token here if(startTokenEndIndex < 0) return false; token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1); // Check for closed token. E.g. if(token.EndsWith("/>")) { endIndex = startIndex + token.Length; return true; } string endToken = token.Insert(1, "/"); // Detect nesting; int nesting = 0; int temp_startTokenIndex = -1; int temp_endTokenIndex = -1; int pos = 0; do { temp_startTokenIndex = value.IndexOf(token, pos); temp_endTokenIndex = value.IndexOf(endToken, pos); if(temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex) { nesting++; pos = temp_startTokenIndex + token.Length; } else if(temp_endTokenIndex >= 0 && nesting > 0) { nesting--; pos = temp_endTokenIndex + endToken.Length; } else // Invalid tokenized string return false; } while(nesting > 0); endIndex = pos; return true; } /// /// Splits the string into sections of tokens and regular text. /// /// The string to split. /// An array with the sections. static string[] SplitIntoSections(string value) { List sections = new List(); while(!string.IsNullOrEmpty(value)) { string token; int tokenStartIndex, tokenEndIndex; // Check if this is a token section if(GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex)) { // Add pretext if the token isn't from the start if(tokenStartIndex > 0) sections.Add(value.Substring(0, tokenStartIndex)); sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex)); value = value.Substring(tokenEndIndex); // Trim away } else { // No tokens, just add the text sections.Add(value); value = null; } } return sections.ToArray(); } private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { string value = e.NewValue as string; TextBlock textBlock = sender as TextBlock; if(textBlock != null) textBlock.Inlines.Add(Traverse(value)); } }

    Thanks Vincent for the great template, it works like a charm!

提交回复
热议问题