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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5565885/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 22:46:02  来源:igfitidea点击:

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

wpfxamlbindingresourcestextblock

提问by Jodrell

I have a TextBlock in my WPF window.

我的 WPF 窗口中有一个 TextBlock。

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

When it is rendered it looks like this,

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

Some formattedtext.

一些格式化的文本。

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

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

I got as far as:

我得到了:

Making an application resource string,

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

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

and the following xaml (Some code omitted for brevity)

和以下 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>

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

Try1 使用标签进行渲染,并且不影响格式。

Some <Bold>formatted<Bold> text.

一些 <Bold> 格式的 <Bold> 文本。

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

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

Is this seemingly simple task possible and if so how?

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

回答by Vincent

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 switchstatement).

这是我修改后的递归格式文本代码。它处理粗体、斜体、下划线和换行符,但可以轻松扩展以支持更多(修改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));
    }
}


Edit:(proposed by Spook)

编辑:(由 Spook 提出)

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

较短的版本,但要求文本是 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;
                }
            }
        }
    }
}

And an example of usage:

以及一个使用示例:

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

回答by Wallstreet Programmer

How about using attached behavior? Below code only handles bold tags. Each word which should be bold needs to be wrapped in bold tags. You probably want to make the class accept other formats as well. Also spaces needs to be handled better, the class strips out consecutive spaces and add one extra to the end. So consider below class as demo code only which will need further work to be useful but it should get you started.

如何使用附加行为?下面的代码只处理粗体标签。每个应该是粗体的单词都需要用粗体标签包裹。您可能还想让类接受其他格式。空格也需要更好地处理,类去掉连续的空格并在末尾添加一个额外的空格。因此,仅将下面的课程视为演示代码,这需要进一步的工作才能有用,但它应该可以帮助您入门。

XAML:

XAML:

<Window x:Class="FormatTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:FormatTest="clr-namespace:FormatTest"
    Title="Window1" Height="300" Width="300">

    <TextBlock FormatTest:FormattedTextBehavior.FormattedText="{Binding Path=Text}" />

</Window>

Code behind:

后面的代码:

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

namespace FormatTest
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            DataContext = this;
        }

        public string Text { get { return "Some <Bold>formatted</Bold> text."; } }
    }

    public static class FormattedTextBehavior
    {
        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(FormattedTextBehavior),
                                                new UIPropertyMetadata("", FormattedTextChanged));

        private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            TextBlock textBlock = sender as TextBlock;
            string value = e.NewValue as string;
            string[] tokens = value.Split(' ');
            foreach (string token in tokens)
            {
                if (token.StartsWith("<Bold>") && token.EndsWith("</Bold>"))
                {
                    textBlock.Inlines.Add(new Bold(new Run(token.Replace("<Bold>", "").Replace("</Bold>", "") + " ")));
                }
                else
                {
                    textBlock.Inlines.Add(new Run(token + " "));
                }
            }
        }
    }
}

回答by Jodrell

EDIT:

编辑:

This line,

这条线,

<props:Resources x:Key="Resources"/>

<props:Resources x:Key="Resources"/>

is a bad approach to accesing the Project.Properties.Resources namespace. It causes awkward glitches when recompiling.

访问 Project.Properties.Resources 命名空间是一种糟糕的方法。重新编译时会导致尴尬的故障。

Much better to use x:Staticto do somthing like this,

x:Static用来做这样的事情要好得多,

Text="{x:Static props:Resources.SomeText}"

Text="{x:Static props:Resources.SomeText}"

in your binding. Thx to Ben

在您的绑定中。感谢



Okay, this is how I did it. It's not perfect but it works.

好的,这就是我所做的。它并不完美,但它有效。

Remember, there is a project resource called FormattedText.

请记住,有一个名为 FormattedText 的项目资源。

cs:

CS:

// TextBlock with a bindable InlineCollection property.

// Type is List(Inline) not InlineCollection becuase
// InlineCollection makes the IDE xaml parser complain
// presumably this is caused by an inherited attribute.

public class BindableTextBlock : TextBlock
{
    public static readonly DependencyProperty InlineCollectionProperty =
        DependencyProperty.Register(
            "InlineCollection",
            typeof(List<Inline>),
            typeof(BindableTextBlock),
            new UIPropertyMetadata(OnInlineCollectionChanged));

    private static void OnInlineCollectionChanged(DependencyObject sender,
        DependencyPropertyChangedEventArgs e)
    {
        BinableTextBlock instance = sender as BindableTextBlock;

        if (instance != null)
        {
            List<Inline> newText = e.NewValue as List<Inline>;
            if (newText != null)
            {
                // Clear the underlying Inlines property
                instance.Inlines.Clear();
                // Add the passed List<Inline> to the real Inlines
                instance.Inlines.AddRange(newText.ToList());
            }
        }
    }

    public List<Inline> InlineCollection
    {
        get
        {
            return (List<Inline>)GetValue(InlineCollectionProperty);
        }
        set
        {
            SetValue(InlineCollectionProperty, value);
        }
    }
}

// Convertor between a string of xaml with implied run elements
// and a generic list of inlines

[ValueConversion(typeof(string), typeof(List<Inline>))]
public class StringInlineCollectionConvertor : IValueConverter
{
    public object Convert(object value, 
        Type targetType, 
        object parameter, 
        System.Globalization.CultureInfo culture)
    {
        string text = value as String;

        // a surrogate TextBlock to host an InlineCollection
        TextBlock results = new TextBlock();

        if (!String.IsNullOrEmpty(text))
        {
            //Arbritary literal acting as a replace token, 
            //must not exist in the empty xaml definition.
            const string Replace = "xxx";

            // add a dummy run element and replace it with the text
            results.Inlines.Add(new Run(Replace));
            string resultsXaml = XamlWriter.Save(results);
            string resultsXamlWithText = resultsXaml.Replace(Replace, text);

            // deserialise the xaml back into our TextBlock
            results = XamlReader.Parse(resultsXamlWithText) as TextBlock;
        }
        return results.Inlines.ToList<Inline>();
    }

    // Not clear when this will be called but included for completeness

    public object ConvertBack(
        object value, 
        Type targetType, 
        object parameter, 
        System.Globalization.CultureInfo culture)
    {
        String results = String.Empty;

        InlineCollection inlines = value as InlineCollection;
        if (inlines != null)
        {
            //read the xaml as xml and return the "content"
            var reader = 
                XElement.Parse(XamlWriter.Save(inlines)).CreateReader();
            reader.MoveToContent();
            results = reader.ReadInnerXml();
        }
        return results;
    }
}

xaml:

xml:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:props="clr-namespace:Project.Properties"
    xmlns:local="clr-namespace:Project">
    <Window.Resources>
        <props:Resources x:Key="Resources"/>
        <local:StringInlineCollectionConvertor x:Key="InlineConvert"/>
    </Window.Resources>
    <local:BindableTextBlock InlineCollection="
        {Binding Source={StaticResource Resources}, 
        Path=FormattedText, 
        Converter={StaticResource InlineConvert}}"/>
</Window>

I made 2 classes. A sub-classed TextBlock with a "bindable" InlineCollection and an IValueConverter to convert the collection from and to a String.

我做了2节课。具有“可绑定” InlineCollection 和 IValueConverter 的子类 TextBlock,用于将集合从字符串转换为字符串。

Using InlineCollection directly as the type of the property made VS2010 complain, although the code still ran fine. I changed to a generic list of Inlines. I assume that there is an inherited attribute telling VS that the InlineCollection has no constructor.

直接使用 InlineCollection 作为属性的类型使 VS2010 抱怨,尽管代码仍然运行良好。我改为内联的通用列表。我假设有一个继承属性告诉 VS InlineCollection 没有构造函数。

I tryed making the InlineCollection property the BindableTextBlock's ContentProperty but ran into issues and out of time. Please feel free to take the next step and tell me about it.

我尝试将 InlineCollection 属性设置为 BindableTextBlock 的 ContentProperty,但遇到了问题并且超时。请随时进行下一步并告诉我。

I apologise for any errata but this code had to be transcribed and sanitised.

对于任何勘误,我深表歉意,但必须转录和清理此代码。

If there is a better way of doing this, surely there must be, please tell me that too. Wouldn't it be nice if this functionality was built in or, have I missed something?

如果有更好的方法来做到这一点,肯定有,也请告诉我。如果这个功能是内置的,或者,我错过了什么,不是很好吗?

回答by devnullicus

I ended up needing to do this in my application and had to support many of the markup possible normally in TextBlock inlines, so I took Wallstreet Programmer's answer above (which works beautifully and is much less complicated than most other answers I found on this topic) and expanded on it. I figure someone else might find this useful.

我最终需要在我的应用程序中执行此操作,并且必须支持 TextBlock 内联中通常可能存在的许多标记,因此我采用了上面华尔街程序员的答案(它工作得很好,并且比我在此主题上找到的大多数其他答案要简单得多)并对其进行了扩展。我想其他人可能会觉得这很有用。

I haven't thoroughly tested this with ALL the tags yet, but every one I've tested has worked like a charm. I also suspect it's not the fastest code in the world, but my own testing with several thousand formatted messages in a ListView seemed surprisingly zippy. YMMV. Code is below:

我还没有对所有标签进行彻底的测试,但我测试过的每个标签都像魅力一样。我也怀疑它不是世界上最快的代码,但是我自己在 ListView 中用数千条格式化消息进行的测试似乎出奇地快。天啊。代码如下:

XAML:

XAML:

<Window x:Class="FormatTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:FormatTest="clr-namespace:FormatTest"
    Title="Window1" Height="300" Width="300">

    <TextBlock FormatTest:FormattedTextBehavior.FormattedText="{Binding Path=Text}" />

</Window>

C#

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace FormatTest
{

public static class FormattedTextBehavior
{
    public class TextPart
    {
        public String mType = String.Empty;
        public Inline mInline = null;
        public InlineCollection mChildren = null;

        public TextPart() {}
        public TextPart(String t, Inline inline, InlineCollection col)
        {
            mType = t;
            mInline = inline;
            mChildren = col;
        }
    }

    private static Regex mRegex = new Regex(@"<(?<Span>/?[^>]*)>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    private static Regex mSpanRegex = new Regex("(?<Key>[^\s=]+)=\"(?<Val>[^\s\"]*)\"", RegexOptions.Compiled | RegexOptions.IgnoreCase);

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

    private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        TextBlock textBlock = sender as TextBlock;
        FormatText(e.NewValue as string, new TextPart("TextBlock", null, textBlock.Inlines));
    }

    public static void FormatText(String s, TextPart root)
    {
        int len = s.Length;
        int lastIdx = 0;
        List<TextPart> parts = new List<TextPart>();
        parts.Add(root);
        Match m = mRegex.Match(s);
        while (m.Success)
        {
            String tag = m.Result("${Span}");
            if (tag.StartsWith("/"))
            {
                String prevStr = s.Substring(lastIdx, m.Index - lastIdx);
                TextPart part = parts.Last();
                if (!String.IsNullOrEmpty(prevStr))
                {
                    if (part.mChildren != null)
                    {
                        part.mChildren.Add(new Run(prevStr));
                    }
                    else if (part.mInline is Run)
                    {
                        (part.mInline as Run).Text = prevStr;
                    }
                }
                if (!tag.Substring(1).Equals(part.mType, StringComparison.InvariantCultureIgnoreCase))
                {
                    Logger.LogD("Mismatched End Tag '" + tag.Substring(1) + "' (expected </" + part.mType + ">) at position " + m.Index.ToString() + " in String '" + s + "'");
                }
                if (parts.Count > 1)
                {
                    parts.RemoveAt(parts.Count - 1);
                    TextPart parentPart = parts.Last();
                    if (parentPart.mChildren != null)
                    {
                        parentPart.mChildren.Add(part.mInline);
                    }
                }
            }
            else
            {
                TextPart prevPart = parts.Last();
                String prevStr = s.Substring(lastIdx, m.Index - lastIdx);
                if (!String.IsNullOrEmpty(prevStr))
                {
                    if (prevPart.mChildren != null)
                    {
                        prevPart.mChildren.Add(new Run(prevStr));
                    }
                    else if (prevPart.mInline is Run)
                    {
                        (prevPart.mInline as Run).Text = prevStr;
                    }
                }

                bool hasAttributes = false;
                TextPart part = new TextPart();
                if (tag.StartsWith("bold", StringComparison.InvariantCultureIgnoreCase))
                {
                    part.mType = "BOLD";
                    part.mInline = new Bold();
                    part.mChildren = (part.mInline as Bold).Inlines;
                }
                else if (tag.StartsWith("underline", StringComparison.InvariantCultureIgnoreCase))
                {
                    part.mType = "UNDERLINE";
                    part.mInline = new Underline();
                    part.mChildren = (part.mInline as Underline).Inlines;
                }
                else if (tag.StartsWith("italic", StringComparison.InvariantCultureIgnoreCase))
                {
                    part.mType = "ITALIC";
                    part.mInline = new Italic();
                    part.mChildren = (part.mInline as Italic).Inlines;
                }
                else if (tag.StartsWith("linebreak", StringComparison.InvariantCultureIgnoreCase))
                {
                    part.mType = "LINEBREAK";
                    part.mInline = new LineBreak();
                }
                else if (tag.StartsWith("span", StringComparison.InvariantCultureIgnoreCase))
                {
                    hasAttributes = true;
                    part.mType = "SPAN";
                    part.mInline = new Span();
                    part.mChildren = (part.mInline as Span).Inlines;
                }
                else if (tag.StartsWith("run", StringComparison.InvariantCultureIgnoreCase))
                {
                    hasAttributes = true;
                    part.mType = "RUN";
                    part.mInline = new Run();
                }
                else if (tag.StartsWith("hyperlink", StringComparison.InvariantCultureIgnoreCase))
                {
                    hasAttributes = true;
                    part.mType = "HYPERLINK";
                    part.mInline = new Hyperlink();
                    part.mChildren = (part.mInline as Hyperlink).Inlines;
                }

                if (hasAttributes && part.mInline != null)
                {
                    Match m2 = mSpanRegex.Match(tag);
                    while (m2.Success)
                    {
                        String key = m2.Result("${Key}");
                        String val = m2.Result("${Val}");
                        if (key.Equals("FontWeight", StringComparison.InvariantCultureIgnoreCase))
                        {
                            FontWeight fw = FontWeights.Normal;
                            try
                            {
                                fw = (FontWeight)new FontWeightConverter().ConvertFromString(val);
                            }
                            catch (Exception)
                            {
                                fw = FontWeights.Normal;
                            }
                            part.mInline.FontWeight = fw;
                        }
                        else if (key.Equals("FontSize", StringComparison.InvariantCultureIgnoreCase))
                        {
                            double fs = part.mInline.FontSize;
                            if (Double.TryParse(val, out fs))
                            {
                                part.mInline.FontSize = fs;
                            }
                        }
                        else if (key.Equals("FontStretch", StringComparison.InvariantCultureIgnoreCase))
                        {
                            FontStretch fs = FontStretches.Normal;
                            try
                            {
                                fs = (FontStretch)new FontStretchConverter().ConvertFromString(val);
                            }
                            catch (Exception)
                            {
                                fs = FontStretches.Normal;
                            }
                            part.mInline.FontStretch = fs;
                        }
                        else if (key.Equals("FontStyle", StringComparison.InvariantCultureIgnoreCase))
                        {
                            FontStyle fs = FontStyles.Normal;
                            try
                            {
                                fs = (FontStyle)new FontStyleConverter().ConvertFromString(val);
                            }
                            catch (Exception)
                            {
                                fs = FontStyles.Normal;
                            }
                            part.mInline.FontStyle = fs;
                        }
                        else if (key.Equals("FontFamily", StringComparison.InvariantCultureIgnoreCase))
                        {
                            if (!String.IsNullOrEmpty(val))
                            {
                                FontFamily ff = new FontFamily(val);
                                if (Fonts.SystemFontFamilies.Contains(ff))
                                {
                                    part.mInline.FontFamily = ff;
                                }
                            }
                        }
                        else if (key.Equals("Background", StringComparison.InvariantCultureIgnoreCase))
                        {
                            Brush b = part.mInline.Background;
                            try
                            {
                                b = (Brush)new BrushConverter().ConvertFromString(val);
                            }
                            catch (Exception)
                            {
                                b = part.mInline.Background;
                            }
                            part.mInline.Background = b;
                        }
                        else if (key.Equals("Foreground", StringComparison.InvariantCultureIgnoreCase))
                        {
                            Brush b = part.mInline.Foreground;
                            try
                            {
                                b = (Brush)new BrushConverter().ConvertFromString(val);
                            }
                            catch (Exception)
                            {
                                b = part.mInline.Foreground;
                            }
                            part.mInline.Foreground = b;
                        }
                        else if (key.Equals("ToolTip", StringComparison.InvariantCultureIgnoreCase))
                        {
                            part.mInline.ToolTip = val;
                        }
                        else if (key.Equals("Text", StringComparison.InvariantCultureIgnoreCase) && part.mInline is Run)
                        {
                            (part.mInline as Run).Text = val;
                        }
                        else if (key.Equals("NavigateUri", StringComparison.InvariantCultureIgnoreCase) && part.mInline is Hyperlink)
                        {
                            (part.mInline as Hyperlink).NavigateUri = new Uri(val);
                        }
                        m2 = m2.NextMatch();
                    }
                }

                if (part.mInline != null)
                {
                    if (tag.TrimEnd().EndsWith("/"))
                    {
                        if (prevPart.mChildren != null)
                        {
                            prevPart.mChildren.Add(part.mInline);
                        }
                    }
                    else
                    {
                        parts.Add(part);
                    }
                }
            }
            lastIdx = m.Index + m.Length;
            m = m.NextMatch();
        }
        if (lastIdx < (len - 1))
        {
            root.mChildren.Add(new Run(s.Substring(lastIdx)));
        }
    }
}

}

回答by Malu MN

The same I have implemented using Behavior. Code given below:

我已经使用 Behavior 实现了同样的效果。代码如下:

public class FormatTextBlock : Behavior<System.Windows.Controls.TextBlock>
{
    public static readonly DependencyProperty FormattedTextProperty = 
        DependencyProperty.Register(
            "FormattedText", 
            typeof(string),
            typeof(FormatTextBlock),
            new PropertyMetadata(string.Empty, OnFormattedTextChanged));

    public string FormattedText
    {
        get { return (string)AssociatedObject.GetValue(FormattedTextProperty); }
        set { AssociatedObject.SetValue(FormattedTextProperty, value); }
    }

    private static void OnFormattedTextChanged(DependencyObject textBlock, DependencyPropertyChangedEventArgs eventArgs)
    {
        System.Windows.Controls.TextBlock currentTxtBlock = (textBlock as FormatTextBlock).AssociatedObject;

        string text = eventArgs.NewValue as string;

        if (currentTxtBlock != null)
        {
            currentTxtBlock.Inlines.Clear();

            string[] strs = text.Split(new string[] { "<Bold>", "</Bold>" }, StringSplitOptions.None);

            for (int i = 0; i < strs.Length; i++)
            {
                currentTxtBlock.Inlines.Add(new Run { Text = strs[i], FontWeight = i % 2 == 1 ? FontWeights.Bold : FontWeights.Normal });
            }
        }
    }
}

XAML - import namespace

XAML - 导入命名空间

<UserControl x:Class="MyClass"
         xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
         xmlns:behav="clr-namespace:myAssembly.myNameSapce;assembly=myAssembly"
>

Then to use the behavior as:

然后将行为用作:

    <TextBlock TextWrapping="Wrap">
        <i:Interaction.Behaviors>
            <behav:FormatTextBlock FormattedText="{Binding Path=UIMessage}" />
        </i:Interaction.Behaviors>
    </TextBlock>

回答by dontbyteme

I've added hyperlink and image support to Vincentssolution:

我已经为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));
    }
}

Thanks Vincentfor the great template, it works like a charm!

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

回答by Rodrigo Rodrigues

This work for me:

这对我有用:

XAML:

XAML:

<phone:PhoneApplicationPage x:Class="MyAPP.Views.Class"
                        xmlns:utils="clr-namespace:MyAPP.Utils">

and your TextBlock XAML:

和你的 TextBlock XAML:

<TextBlock utils:TextBlockHelper.FormattedText="{Binding Text}" />

CODE:

代码:

public static class TextBlockHelper
{
    public static string GetFormattedText(DependencyObject textBlock)
    { 
        return (string)textBlock.GetValue(FormattedTextProperty); 
    }

    public static void SetFormattedText(DependencyObject textBlock, string value)
    { 
        textBlock.SetValue(FormattedTextProperty, value); 
    }

    public static readonly DependencyProperty FormattedTextProperty =
        DependencyProperty.RegisterAttached("FormattedText", typeof(string), typeof(TextBlock),
        new PropertyMetadata(string.Empty, (sender, e) =>
        {
            string text = e.NewValue as string;
            var textB1 = sender as TextBlock;
            if (textB1 != null)
            {
                textB1.Inlines.Clear();
                var str = text.Split(new string[] { "<b>", "</b>" }, StringSplitOptions.None);
                for (int i = 0; i < str.Length; i++)
                    textB1.Inlines.Add(new Run { Text = str[i], FontWeight = i % 2 == 1 ? FontWeights.Bold : FontWeights.Normal });

            }
        }));
}

USE in your string binding:

在您的字符串绑定中使用:

String Text = Text <b>Bold</b>;