如何将 TextBlock 绑定到包含格式化文本的资源? [英] How to bind a TextBlock to a resource containing formatted text?

查看:36
本文介绍了如何将 TextBlock 绑定到包含格式化文本的资源?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的 WPF 窗口中有一个 TextBlock.

I have a TextBlock in my WPF window.

 <TextBlock>
     Some <Bold>formatted</Bold> text.
 </TextBlock>

当它被渲染时,它看起来像这样,

When it is rendered it looks like this,

一些格式化文本.

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

My question is, can I bind this inline "content" to a resource in my application?

我做到了:

制作应用资源字符串,

myText="Some <Bold>formatted</Bold> text."

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

and the following xaml (Some code omitted for brevity)

 <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>

Try1 使用适当的标签进行渲染并且不影响格式.

Try1 renders with the tags in place and not effecting formatting.

一些格式化文本.

Some <Bold>formatted<Bold> text.

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

Try2 will not compile or render because the resource "myText" is not of type Inline but a string.

这个看似简单的任务是否可行?如果可行,如何实现?

Is this seemingly simple task possible and if so how?

推荐答案

这是我修改后的递归格式文本代码.它处理粗体、斜体、下划线和换行符,但可以轻松扩展以支持更多(修改 switch 语句).

Here is my modified code for recursively format text. It handles Bold, Italic, Underline and LineBreak but can easily be extended to support more (modify the switch statement).

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));
    }
}


(由 Spook 提出)


(proposed by Spook)

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

A shorter version, but requires the text to be XML-valid:

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;
                }
            }
        }
    }
}

以及用法示例:

<RootItem xmlns:u="clr-namespace:MyApp.Helpers">
    <TextBlock u:TextBlockHelper.FormattedText="{Binding SomeProperty}" />
</RootItem>

这篇关于如何将 TextBlock 绑定到包含格式化文本的资源?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆