WPF生成TextBlock内联元素

14

我有一个GridView,在其中一个GridViewColumn中,我想生成这样的文本:

textBlock.Text = string.Format("{0} is doing {1} .......", a, b);

但是ab(在视图中的项目属性)不应该仅以纯文本形式表示,而应该例如以超链接的形式呈现。
(另外:格式文本应根据项目类型而定)

我如何以那种方式生成TextBlock的文本?(用于本地化)

这个问题更多是:我应该自己写还是框架提供了一种简单的方法?

3个回答

23

这是一个老问题,但我觉得被接受的答案过于复杂了。你根本不需要解析格式化文本!只需将其放入Span元素中即可。

public class Attached
{
    public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
        "FormattedText", 
        typeof(string), 
        typeof(Attached), 
        new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure, FormattedTextPropertyChanged));

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

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

    private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var textBlock = d as TextBlock;
        if (textBlock == null)
        {
            return;
        }

        var formattedText = (string)e.NewValue ?? string.Empty;
        formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);

        textBlock.Inlines.Clear();
        using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
        {
            var result = (Span)XamlReader.Load(xmlReader);
            textBlock.Inlines.Add(result);
        }
    }
}
现在您可以在代码中使用FormattedText附加属性:

现在你可以在你的代码中使用FormattedText附加属性:

string inlineExpression = "<Run Style=\"Theme.GrayText\">Once I saw a little bird, go hop, hop, hop.</Run>";
Attached.SetFormattedText(myTextBlock1, inlineExpression);

更重要的是,直接来自XAML:

<TextBlock ns:Attached.FormattedText="{Binding Content}" />

其中ns是您定义Attached类的命名空间。


好主意。然而,我认为在你的解决方案中没有使用样式的机会:XamlReader会抛出异常,因为样式是在其他地方定义的。此外,您必须预处理/解析文本,将“run”转换为“Run”等等。无论如何,我认为这是许多情况下的好解决方案。 - Evgeni
@Evgeni:只要通过DynamicResource引用样式,它就可以正常工作。更重要的是,你不需要解析任何东西(我将“run”改为了“Run”,这是一个打字错误),这就是它的美妙之处,与笨重的被接受的答案相比。 - gwiazdorrr
第一个答案的美妙之处在于它早就给出了,很可能可以解决问题)) 我认为最重要的是,两种解决方案最终都提供了一个单独的附加属性。毫无疑问,你的解决方案更加优雅。 - Evgeni
3
这是一个很好的解决方案(+1),但我不得不对 RegisterAttached 进行一些更正才能在 XAML 中进行绑定设置:(1)typof(TextBlock) -> typeof(Attached),以及(2)在 new FrameworkPropertyMetadata 中添加 FormattedPropertyChanged 作为第三个参数。此外,在 XAML 中使用:<TextBlock ns:Formatter="{Binding Content}" />。 @gwiazdorrr,如果有人看不到我的评论,修改您的答案会很好。 (我本可以自己编辑,但我不确定我的更改是否会干扰您的代码/非 XAML 解决方案。) - devuxer
1
谢谢您注意到了这个问题!事实上,我使用自定义依赖属性工厂,而这只是一个快速而简单的翻译回到原始的WPF。 - gwiazdorrr

9

最近我也遇到了同样的问题。所以我决定为TextBlock实现一个附加属性,它接受一个string类型的值,然后动态地填充Inlines集合。您可以将属性值简单地设置为以下内容:

string inlineExpression = "Once i saw a little <bold>bird</bold>, <span>go <bold>hop, hop, hop</bold></span>.";
InlineExpression.SetInlineExpression(myTextBlock1, inlineExpression);

样式也被支持:
string inlineExpression = "<run style="Theme.GrayText">Once i saw a little bird, go hop, hop, hop.</run>";
InlineExpression.SetInlineExpression(myTextBlock1, inlineExpression);

您可以在XAML中以标准方式使用此附加属性。
以下是公开此属性的类的代码:
public class InlineExpression
{
    public static readonly DependencyProperty InlineExpressionProperty = DependencyProperty.RegisterAttached(
        "InlineExpression", typeof(string), typeof(TextBlock), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure));

    public static void SetInlineExpression(TextBlock textBlock, string value)
    {
        textBlock.SetValue(InlineExpressionProperty, value);

        textBlock.Inlines.Clear();

        if (string.IsNullOrEmpty(value))
            return;

        var descriptions = GetInlineDescriptions(value);
        if (descriptions.Length == 0)
            return;

        var inlines = GetInlines(textBlock, descriptions);
        if (inlines.Length == 0)
            return;

        textBlock.Inlines.AddRange(inlines);
    }

    public static string GetInlineExpression(TextBlock textBlock)
    {
        return (string)textBlock.GetValue(InlineExpressionProperty);
    }

    enum InlineType
    {
        Run,
        LineBreak,
        Span,
        Bold,
        Italic,
        Hyperlink,
        Underline
    }

    class InlineDescription
    {
        public InlineType Type { get; set; }
        public string Text { get; set; }
        public InlineDescription[] Inlines { get; set; }
        public string StyleName { get; set; }
    }

    private static Inline[] GetInlines(FrameworkElement element, IEnumerable<InlineDescription> inlineDescriptions)
    {
        var inlines = new List<Inline>();
        foreach (var description in inlineDescriptions)
        {
            var inline = GetInline(element, description);
            if (inline != null)
                inlines.Add(inline);
        }

        return inlines.ToArray();
    }

    private static Inline GetInline(FrameworkElement element, InlineDescription description)
    {
        Style style = null;
        if (!string.IsNullOrEmpty(description.StyleName))
        {
            style = element.FindResource(description.StyleName) as Style;
            if (style == null)
                throw new InvalidOperationException("The style '" + description.StyleName + "' cannot be found");
        }

        Inline inline = null;
        switch (description.Type)
        {
            case InlineType.Run:
                var run = new Run(description.Text);
                inline = run;
                break;
            case InlineType.LineBreak:
                var lineBreak = new LineBreak();
                inline = lineBreak;
                break;
            case InlineType.Span:
                var span = new Span();
                inline = span;
                break;
            case InlineType.Bold:
                var bold = new Bold();
                inline = bold;
                break;
            case InlineType.Italic:
                var italic = new Italic();
                inline = italic;
                break;
            case InlineType.Hyperlink:
                var hyperlink = new Hyperlink();
                inline = hyperlink;
                break;
            case InlineType.Underline:
                var underline = new Underline();
                inline = underline;
                break;
        }

        if (inline != null)
        {
            var span = inline as Span;
            if (span != null)
            {
                var childInlines = new List<Inline>();
                foreach (var inlineDescription in description.Inlines)
                {
                    var childInline = GetInline(element, inlineDescription);
                    if (childInline == null)
                        continue;

                    childInlines.Add(childInline);
                }

                span.Inlines.AddRange(childInlines);
            }

            if (style != null)
                inline.Style = style;
        }

        return inline;
    }

    private static InlineDescription[] GetInlineDescriptions(string inlineExpression)
    {
        if (inlineExpression == null)
            return new InlineDescription[0];

        inlineExpression = inlineExpression.Trim();
        if (inlineExpression.Length == 0)
            return new InlineDescription[0];

        inlineExpression = inlineExpression.Insert(0, @"<root>");
        inlineExpression = inlineExpression.Insert(inlineExpression.Length, @"</root>");

        var xmlTextReader = new XmlTextReader(new StringReader(inlineExpression));
        var xmlDocument = new XmlDocument();
        xmlDocument.Load(xmlTextReader);

        var rootElement = xmlDocument.DocumentElement;
        if (rootElement == null)
            return new InlineDescription[0];

        var inlineDescriptions = new List<InlineDescription>();

        foreach (XmlNode childNode in rootElement.ChildNodes)
        {
            var description = GetInlineDescription(childNode);
            if (description == null)
                continue;

            inlineDescriptions.Add(description);
        }

        return inlineDescriptions.ToArray();
    }

    private static InlineDescription GetInlineDescription(XmlNode node)
    {
        var element = node as XmlElement;
        if (element != null)
            return GetInlineDescription(element);
        var text = node as XmlText;
        if (text != null)
            return GetInlineDescription(text);
        return null;
    }

    private static InlineDescription GetInlineDescription(XmlElement element)
    {
        InlineType type;
        var elementName = element.Name.ToLower();
        switch (elementName)
        {
            case "run":
                type = InlineType.Run;
                break;
            case "linebreak":
                type = InlineType.LineBreak;
                break;
            case "span":
                type = InlineType.Span;
                break;
            case "bold":
                type = InlineType.Bold;
                break;
            case "italic":
                type = InlineType.Italic;
                break;
            case "hyperlink":
                type = InlineType.Hyperlink;
                break;
            case "underline":
                type = InlineType.Underline;
                break;
            default:
                return null;
        }

        string styleName = null;
        var attribute = element.GetAttributeNode("style");
        if (attribute != null)
            styleName = attribute.Value;

        string text = null;
        var childDescriptions = new List<InlineDescription>();

        if (type == InlineType.Run || type == InlineType.LineBreak)
        {
            text = element.InnerText;
        }
        else
        {
            foreach (XmlNode childNode in element.ChildNodes)
            {
                var childDescription = GetInlineDescription(childNode);
                if (childDescription == null)
                    continue;

                childDescriptions.Add(childDescription);
            }
        }

        var inlineDescription = new InlineDescription
                                        {
                                            Type = type,
                                            StyleName = styleName,
                                            Text = text,
                                            Inlines = childDescriptions.ToArray()
                                        };

        return inlineDescription;
    }

    private static InlineDescription GetInlineDescription(XmlText text)
    {
        var value = text.Value;
        if (string.IsNullOrEmpty(value))
            return null;

        var inlineDescription = new InlineDescription
                                        {
                                            Type = InlineType.Run,
                                            Text = value
                                        };
        return inlineDescription;
    }
}

4
在XAML中,您可以这样做:
<GridViewColumn>
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <TextBlock>
                <Hyperlink NavigateUri="{Binding AUri}">
                    <Run Text="{Binding A}"/>
                </Hyperlink>
                <Run Text=" is doing "/>
                <Hyperlink NavigateUri="{Binding BUri}">
                    <Run Text="{Binding B}"/>
                </Hyperlink>
            </TextBlock>
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>

在代码后端也可以做同样的事情,但我不建议这样做,因为它涉及使用FrameworkElementFactories

1
是的,那将是最糟糕的情况。但是该文本比“正在执行”的长度更长,我需要进行许多小部分的翻译 =/ - ordag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接