如何将TextBlock绑定到包含格式化文本的资源?

Jod*_*ell 24 wpf resources xaml binding textblock

我的WPF窗口中有一个TextBlock.

 <TextBlock>
     Some <Bold>formatted</Bold> text.
 </TextBlock>
Run Code Online (Sandbox Code Playgroud)

渲染时它看起来像这样,

一些格式化文本.

我的问题是,我可以将此内联"内容"绑定到我的应用程序中的资源吗?

我得到了:

制作应用程序资源字符串,

myText="Some <Bold>formatted</Bold> text."
Run Code Online (Sandbox Code Playgroud)

和以下xaml(为简洁起见,省略了一些代码)

 <Window xmlns:props="clr-namespace:MyApp.Properties">
     <Window.Resources>
         <props:Resources x:Key="Resources"/>
     </Window.Resources>
      <TextBlock x:Name="Try1" 
          Text="{Binding Source={StaticResource Resources} Path=myText}"/>
     <TextBlock x:Name="Try2">
          <Binding Source="{StaticResource Resources}" Path="myText" />
     </TextBlock>
 </Window>
Run Code Online (Sandbox Code Playgroud)

Try1呈现标签并且不影响格式.

一些<Bold>格式化的<Bold>文本.

Try2不会编译或渲染,因为资源"myText"不是Inline类型而是字符串.

这看似简单的任务是否可能,如果可行,怎么办?

Vin*_*ent 28

这是我修改后的代码,用于递归格式化文本.它处理Bold,Italic,Underline和LineBreak,但可以轻松扩展以支持更多(修改switch语句).

public static class MyBehavior
{
    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(MyBehavior),
        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 <Bold>
            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)
                {
                    case "<Bold>":
                        return new Bold(Traverse(content));
                    case "<Italic>":
                        return new Italic(Traverse(content));
                    case "<Underline>":
                        return new Underline(Traverse(content));
                    case "<LineBreak/>":
                        return new LineBreak();
                    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;
        }
    }

    /// <summary>
    /// Examines the passed string and find the first token, where it begins and where it ends.
    /// </summary>
    /// <param name="value">The string to examine.</param>
    /// <param name="token">The found token.</param>
    /// <param name="startIndex">Where the token begins.</param>
    /// <param name="endIndex">Where the end-token ends.</param>
    /// <returns>True if a token was found.</returns>
    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. <LineBreak/>
        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;
    }

    /// <summary>
    /// Splits the string into sections of tokens and regular text.
    /// </summary>
    /// <param name="value">The string to split.</param>
    /// <returns>An array with the sections.</returns>
    static string[] SplitIntoSections(string value)
    {
        List<string> sections = new List<string>();

        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));
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:(由Spook提出)

较短的版本,但要求文本具有XML有效性:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Xml;

// (...)

public static class TextBlockHelper
{
    #region FormattedText Attached dependency property

    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(TextBlockHelper),
        new UIPropertyMetadata("", FormattedTextChanged));

    private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        string value = e.NewValue as string;

        TextBlock textBlock = sender as TextBlock;

        if (textBlock != null)
        {
            textBlock.Inlines.Clear();
            textBlock.Inlines.Add(Process(value));
        }
    }

    #endregion

    static Inline Process(string value)
    {
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(value);

        Span span = new Span();
        InternalProcess(span, doc.ChildNodes[0]);

        return span;
    }

    private static void InternalProcess(Span span, XmlNode xmlNode)
    {
        foreach (XmlNode child in xmlNode)
        {
            if (child is XmlText)
            {
                span.Inlines.Add(new Run(child.InnerText));
            }
            else if (child is XmlElement)
            {
                Span spanItem = new Span();
                InternalProcess(spanItem, child);
                switch (child.Name.ToUpper())
                {
                    case "B":
                    case "BOLD":
                        Bold bold = new Bold(spanItem);
                        span.Inlines.Add(bold);
                        break;
                    case "I":
                    case "ITALIC":
                        Italic italic = new Italic(spanItem);
                        span.Inlines.Add(italic);
                        break;
                    case "U":
                    case "UNDERLINE":
                        Underline underline = new Underline(spanItem);
                        span.Inlines.Add(underline);
                        break;
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以及用法示例:

<RootItem xmlns:u="clr-namespace:MyApp.Helpers">
    <TextBlock u:TextBlockHelper.FormattedText="{Binding SomeProperty}" />
</RootItem>
Run Code Online (Sandbox Code Playgroud)

  • 嘿文森特,您的回答很棒。在我的代码中,我添加了相同的textBlock.Inlines.Clear();。在@Spook具有的FormattedTextChanged方法中。这样可以防止工具提示每次鼠标悬停时不断添加文本。 (2认同)

don*_*eme 5

我已经为Vincents解决方案添加了超链接和图像支持:

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 <Bold>
            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 "<B>":
                    case "<BOLD>":
                        /* <b>Bold text</b> */
                        return new Bold(Traverse(content));
                    case "<I>":
                    case "<ITALIC>":
                        /* <i>Italic text</i> */
                        return new Italic(Traverse(content));
                    case "<U>":
                    case "<UNDERLINE>":
                        /* <u>Underlined text</u> */
                        return new Underline(Traverse(content));
                    case "<BR>":
                    case "<BR/>":
                    case "<LINEBREAK/>":
                        /* Line 1<br/>line 2 */
                        return new LineBreak();
                    case "<A>":
                    case "<LINK>":
                        /* <a>{http://www.google.de}Google</a> */
                        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 "<IMG>":
                    case "<IMAGE>":
                        /* <image>pack://application:,,,/ProjectName;component/directory1/directory2/image.png</image> */
                        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());
    }

    /// <summary>
    /// Examines the passed string and find the first token, where it begins and where it ends.
    /// </summary>
    /// <param name="value">The string to examine.</param>
    /// <param name="token">The found token.</param>
    /// <param name="startIndex">Where the token begins.</param>
    /// <param name="endIndex">Where the end-token ends.</param>
    /// <returns>True if a token was found.</returns>
    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. <LineBreak/>
        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;
    }

    /// <summary>
    /// Splits the string into sections of tokens and regular text.
    /// </summary>
    /// <param name="value">The string to split.</param>
    /// <returns>An array with the sections.</returns>
    static string[] SplitIntoSections(string value)
    {
        List<string> sections = new List<string>();

        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));
    }
}
Run Code Online (Sandbox Code Playgroud)

感谢Vincent提供了很棒的模板,它就像一个魅力!