WPF TextBlock 根据搜索条件突出显示某些部分

发布于 2024-07-16 11:21:50 字数 518 浏览 7 评论 0原文

我有 TextBlock,它动态添加了内联(基本上是一堆斜体或粗体的 Run 对象)。

在我的应用程序中,我有搜索功能。

我希望能够突出显示正在搜索的 TextBlock 文本。

我所说的突出显示是指更改 TextBlock 文本颜色的某些部分(请记住,它可能会一次突出显示多个不同的 Run 对象)。

我尝试过这个示例 http://blogs.microsoft.co.il/blogs/tamir/archive/2008/05/12/search-and-highlight-any-text-on-wpf-rendered- page.aspx

但它看起来非常不稳定:(

有没有简单的方法来解决这个问题?

I have TextBlock that has Inlines dynamicly added to it (basically bunch of Run objects that are either italic or bold).

In my application I have search function.

I want to be able to highlight TextBlock's text that is in being searched for.

By highlighting I mean changing certain parts of TextBlock text's color (keeping in mind that it may highlight several different Run objects at a time).

I have tried this example http://blogs.microsoft.co.il/blogs/tamir/archive/2008/05/12/search-and-highlight-any-text-on-wpf-rendered-page.aspx

But it seams very unstable :(

Is there easy way to solve this problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

一向肩并 2024-07-23 11:21:50

这个问题类似于 如何在 WPF 项目控件中显示带有突出显示的查询词的搜索结果

为了回答这个问题,我提出了一种使用 IValueConverter 的方法。 该转换器获取文本片段,将其格式化为有效的 XAML 标记,并使用 XamlReader 将标记实例化为框架对象。

完整的解释相当长,所以我将其发布到我的博客: 突出显示 WPF TextBlock 中的查询术语

This question is similar to How to display search results in a WPF items control with highlighted query terms

In answer to that question, I came up with an approach that uses an IValueConverter. The converter takes a text snippet, formats it into valid XAML markup, and uses a XamlReader to instantiate the markup into framework objects.

The full explanation is rather long, so I've posted it to my blog: Highlighting Query Terms in a WPF TextBlock

娜些时光,永不杰束 2024-07-23 11:21:50

我采用了 dthrasers 答案,并消除了对 XML 解析器的需求。 他很好地解释了 他的博客,但是这不需要我添加任何额外的库,这就是我的做法。

第一步,创建一个转换器类:

class StringToXamlConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string input = value as string;
        if (input != null)
        {
            var textBlock = new TextBlock();
            textBlock.TextWrapping = TextWrapping.Wrap;
            string escapedXml = SecurityElement.Escape(input);

            while (escapedXml.IndexOf("|~S~|") != -1) {
            //up to |~S~| is normal
            textBlock.Inlines.Add(new Run(escapedXml.Substring(0, escapedXml.IndexOf("|~S~|"))));
            //between |~S~| and |~E~| is highlighted
            textBlock.Inlines.Add(new Run(escapedXml.Substring(escapedXml.IndexOf("|~S~|") + 5,
                                      escapedXml.IndexOf("|~E~|") - (escapedXml.IndexOf("|~S~|") + 5))) 
                                      { FontWeight = FontWeights.Bold, Background= Brushes.Yellow });
            //the rest of the string (after the |~E~|)
            escapedXml = escapedXml.Substring(escapedXml.IndexOf("|~E~|") + 5);
            }

            if (escapedXml.Length > 0)
            {
                textBlock.Inlines.Add(new Run(escapedXml));                      
            }
            return textBlock;
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException("This converter cannot be used in two-way binding.");
    }

}

第二步:
使用 ContentBlock 代替 TextBlock。 将字符串(您将用于 textBlock)传递到内容块,如下所示:

<ContentControl Margin="7,0,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Center"
                Content="{Binding Description, Converter={StaticResource CONVERTERS_StringToXaml}, Mode=OneTime}">
</ContentControl>

第三步:
确保您传递的文本在要突出显示的文本部分之前包含 |~S~| ,在之后包含 |~E~| 。 例如,在此字符串 "my text |~S~|is|~E~| good" 中,is 将以黄色突出显示。

笔记:

您可以更改运行中的样式来确定文本的突出显示内容和方式

确保将 Converter 类添加到命名空间和资源中。 这可能还需要重建才能工作。

I took dthrasers answer and took out the need for an XML parser. He does a great job explaining each of the pieces in his blog, However this didn't require me to add any extra libraries, here's how I did it.

Step one, make a converter class:

class StringToXamlConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string input = value as string;
        if (input != null)
        {
            var textBlock = new TextBlock();
            textBlock.TextWrapping = TextWrapping.Wrap;
            string escapedXml = SecurityElement.Escape(input);

            while (escapedXml.IndexOf("|~S~|") != -1) {
            //up to |~S~| is normal
            textBlock.Inlines.Add(new Run(escapedXml.Substring(0, escapedXml.IndexOf("|~S~|"))));
            //between |~S~| and |~E~| is highlighted
            textBlock.Inlines.Add(new Run(escapedXml.Substring(escapedXml.IndexOf("|~S~|") + 5,
                                      escapedXml.IndexOf("|~E~|") - (escapedXml.IndexOf("|~S~|") + 5))) 
                                      { FontWeight = FontWeights.Bold, Background= Brushes.Yellow });
            //the rest of the string (after the |~E~|)
            escapedXml = escapedXml.Substring(escapedXml.IndexOf("|~E~|") + 5);
            }

            if (escapedXml.Length > 0)
            {
                textBlock.Inlines.Add(new Run(escapedXml));                      
            }
            return textBlock;
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException("This converter cannot be used in two-way binding.");
    }

}

Step two:
Instead of a TextBlock use a ContentBlock. Pass in the string (you would of used for your textBlock) to the content block, like so:

<ContentControl Margin="7,0,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Center"
                Content="{Binding Description, Converter={StaticResource CONVERTERS_StringToXaml}, Mode=OneTime}">
</ContentControl>

Step three:
Make sure the text you pass includes |~S~| before and |~E~| after the text part you want to be highlighted. For example in this string "my text |~S~|is|~E~| good" the is will be highlighted in yellow.

Notes:

You can change the style in the run to determine what and how your text is highlighted

Make sure you add your Converter class to your namespace and resources. This might also require a rebuild to get working.

相权↑美人 2024-07-23 11:21:50

适用于 .NET Framework 4.7-4.8。 可能需要调整才能在较新的 .NET 版本(例如 .NET 6)中工作,请参阅评论。

与其他更容易重用的解决方案的差异

  • -> 附加行为而不是自定义控件
  • MVVM 友好 -> 没有背后的代码
  • 可以双向工作! -> 更改要突出显示的术语或文本,都会更新文本块中的突出显示。 我检查的其他解决方案存在问题,即更改文本不会重新应用突出显示。 仅更改突出显示的术语/搜索文本有效。

如何使用

  • 重要提示:不要再使用 TextBlock 的常规 Text="blabla" 属性。 而是将您的文本绑定到 HighlightTermBehavior.Text="blabla"
  • 将附加属性添加到您的 TextBlock 中
<TextBlock local:HighlightTermBehavior.TermToBeHighlighted="{Binding MyTerm}"
           local:HighlightTermBehavior.Text="{Binding MyText}" />

,或者硬编码

<TextBlock local:HighlightTermBehavior.TermToBeHighlighted="highlight this"
           local:HighlightTermBehavior.Text="bla highlight this bla" />

添加此类

  • 要更改突出显示的类型,只需更改这些方法:
    AddPartToTextBlock() 用于非突出显示文本
    AddHighlightedPartToTextBlock() 用于突出显示的文本。
  • 目前突出显示的是 FontWeights.ExtraBold,非突出显示的文本是 FontWeights.Light
  • 如果没有 IDE,可能很难阅读,抱歉。
public static class HighlightTermBehavior
{
    public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
        "Text",
        typeof(string),
        typeof(HighlightTermBehavior),
        new FrameworkPropertyMetadata("", OnTextChanged));

    public static string GetText(FrameworkElement frameworkElement)               => (string) frameworkElement.GetValue(TextProperty);
    public static void   SetText(FrameworkElement frameworkElement, string value) => frameworkElement.SetValue(TextProperty, value);


    public static readonly DependencyProperty TermToBeHighlightedProperty = DependencyProperty.RegisterAttached(
        "TermToBeHighlighted",
        typeof(string),
        typeof(HighlightTermBehavior),
        new FrameworkPropertyMetadata("", OnTextChanged));

    public static string GetTermToBeHighlighted(FrameworkElement frameworkElement)
    {
        return (string) frameworkElement.GetValue(TermToBeHighlightedProperty);
    }

    public static void SetTermToBeHighlighted(FrameworkElement frameworkElement, string value)
    {
        frameworkElement.SetValue(TermToBeHighlightedProperty, value);
    }


    private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBlock textBlock)
            SetTextBlockTextAndHighlightTerm(textBlock, GetText(textBlock), GetTermToBeHighlighted(textBlock));
    }

    private static void SetTextBlockTextAndHighlightTerm(TextBlock textBlock, string text, string termToBeHighlighted)
    {
        textBlock.Text = string.Empty;

        if (TextIsEmpty(text))
            return;

        if (TextIsNotContainingTermToBeHighlighted(text, termToBeHighlighted))
        {
            AddPartToTextBlock(textBlock, text);
            return;
        }

        var textParts = SplitTextIntoTermAndNotTermParts(text, termToBeHighlighted);

        foreach (var textPart in textParts)
            AddPartToTextBlockAndHighlightIfNecessary(textBlock, termToBeHighlighted, textPart);
    }

    private static bool TextIsEmpty(string text)
    {
        return text.Length == 0;
    }

    private static bool TextIsNotContainingTermToBeHighlighted(string text, string termToBeHighlighted)
    {
        return text.Contains(termToBeHighlighted, StringComparison.Ordinal) == false;
    }

    private static void AddPartToTextBlockAndHighlightIfNecessary(TextBlock textBlock, string termToBeHighlighted, string textPart)
    {
        if (textPart == termToBeHighlighted)
            AddHighlightedPartToTextBlock(textBlock, textPart);
        else
            AddPartToTextBlock(textBlock, textPart);
    }

    private static void AddPartToTextBlock(TextBlock textBlock, string part)
    {
        textBlock.Inlines.Add(new Run {Text = part, FontWeight = FontWeights.Light});
    }

    private static void AddHighlightedPartToTextBlock(TextBlock textBlock, string part)
    {
        textBlock.Inlines.Add(new Run {Text = part, FontWeight = FontWeights.ExtraBold});
    }


    public static List<string> SplitTextIntoTermAndNotTermParts(string text, string term)
    {
        if (text.IsNullOrEmpty())
            return new List<string>() {string.Empty};

        return Regex.Split(text, $@"({Regex.Escape(term)})")
                    .Where(p => p != string.Empty)
                    .ToList();
    }
}

works in .NET Framework 4.7-4.8. May need adjustments to work in newer .NET versions like .NET 6, see comments.

Differences to other solutions

  • easier to reuse -> attached behavior instead of custom control
  • MVVM friendly -> no code behind
  • works BOTH ways! -> Changing the term to be highlighted OR the text, both updates the highlight in the textblock. The other solutions i checked had the problem, that changing the text does not reapply the highlighting. Only changing the highlighted term/search text worked.

How to use

  • IMPORTANT: do NOT use the regular Text="blabla" property of the TextBlock anymore. Instead bind your text to HighlightTermBehavior.Text="blabla".
  • Add the attached properties to your TextBlock like that
<TextBlock local:HighlightTermBehavior.TermToBeHighlighted="{Binding MyTerm}"
           local:HighlightTermBehavior.Text="{Binding MyText}" />

or hardcoded

<TextBlock local:HighlightTermBehavior.TermToBeHighlighted="highlight this"
           local:HighlightTermBehavior.Text="bla highlight this bla" />

Add this class

  • To change the kind of highlighting, just change these Methods:
    AddPartToTextBlock() for non highlighted text
    AddHighlightedPartToTextBlock() for the highlighted text.
  • At the moment highlighted is FontWeights.ExtraBold and non highlighted text is FontWeights.Light.
  • probably hard to read without an IDE, sorry.
public static class HighlightTermBehavior
{
    public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
        "Text",
        typeof(string),
        typeof(HighlightTermBehavior),
        new FrameworkPropertyMetadata("", OnTextChanged));

    public static string GetText(FrameworkElement frameworkElement)               => (string) frameworkElement.GetValue(TextProperty);
    public static void   SetText(FrameworkElement frameworkElement, string value) => frameworkElement.SetValue(TextProperty, value);


    public static readonly DependencyProperty TermToBeHighlightedProperty = DependencyProperty.RegisterAttached(
        "TermToBeHighlighted",
        typeof(string),
        typeof(HighlightTermBehavior),
        new FrameworkPropertyMetadata("", OnTextChanged));

    public static string GetTermToBeHighlighted(FrameworkElement frameworkElement)
    {
        return (string) frameworkElement.GetValue(TermToBeHighlightedProperty);
    }

    public static void SetTermToBeHighlighted(FrameworkElement frameworkElement, string value)
    {
        frameworkElement.SetValue(TermToBeHighlightedProperty, value);
    }


    private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBlock textBlock)
            SetTextBlockTextAndHighlightTerm(textBlock, GetText(textBlock), GetTermToBeHighlighted(textBlock));
    }

    private static void SetTextBlockTextAndHighlightTerm(TextBlock textBlock, string text, string termToBeHighlighted)
    {
        textBlock.Text = string.Empty;

        if (TextIsEmpty(text))
            return;

        if (TextIsNotContainingTermToBeHighlighted(text, termToBeHighlighted))
        {
            AddPartToTextBlock(textBlock, text);
            return;
        }

        var textParts = SplitTextIntoTermAndNotTermParts(text, termToBeHighlighted);

        foreach (var textPart in textParts)
            AddPartToTextBlockAndHighlightIfNecessary(textBlock, termToBeHighlighted, textPart);
    }

    private static bool TextIsEmpty(string text)
    {
        return text.Length == 0;
    }

    private static bool TextIsNotContainingTermToBeHighlighted(string text, string termToBeHighlighted)
    {
        return text.Contains(termToBeHighlighted, StringComparison.Ordinal) == false;
    }

    private static void AddPartToTextBlockAndHighlightIfNecessary(TextBlock textBlock, string termToBeHighlighted, string textPart)
    {
        if (textPart == termToBeHighlighted)
            AddHighlightedPartToTextBlock(textBlock, textPart);
        else
            AddPartToTextBlock(textBlock, textPart);
    }

    private static void AddPartToTextBlock(TextBlock textBlock, string part)
    {
        textBlock.Inlines.Add(new Run {Text = part, FontWeight = FontWeights.Light});
    }

    private static void AddHighlightedPartToTextBlock(TextBlock textBlock, string part)
    {
        textBlock.Inlines.Add(new Run {Text = part, FontWeight = FontWeights.ExtraBold});
    }


    public static List<string> SplitTextIntoTermAndNotTermParts(string text, string term)
    {
        if (text.IsNullOrEmpty())
            return new List<string>() {string.Empty};

        return Regex.Split(text, $@"({Regex.Escape(term)})")
                    .Where(p => p != string.Empty)
                    .ToList();
    }
}
渔村楼浪 2024-07-23 11:21:50

奇怪的是,我最近写了一篇文章来解决同样的问题。 它是一个自定义控件,具有与 TextBlock 相同的属性(因此您可以在任何需要的地方将其替换为 TextBlock),并且它有一个额外的属性,您可以将其绑定到名为 HighLightText,只要在主 Text 属性中找到 HighLightText 的值(不区分大小写),它就会突出显示。

这是一个创建起来相当简单的控件,您可以在这里找到完整的代码作为解决方案:

SearchMatchTextblock(GitHub )

By strange coincidence, I have recently written an article that solves the very same problem. It is a custom control that has the same properties as a TextBlock (so you can swap is out for a TextBlock wherever you need it), and it has an extra Property that you can bind to called HighLightText, and wherever the value of HighLightText is found in the main Text property (case insensitive), it is highlighted.

It was a fairly straight-forward control to create, and you can find the full code as a solution here:

SearchMatchTextblock(GitHub)

递刀给你 2024-07-23 11:21:50

以下是我通过构建现有的 TextBlock 并添加一个名为 SearchText 的新依赖属性得出的结果:

public class SearchHightlightTextBlock : TextBlock
{
    public SearchHightlightTextBlock() : base() { }

    public String SearchText { get { return (String)GetValue(SearchTextProperty); }
                               set { SetValue(SearchTextProperty, value); } }      

    private static void OnDataChanged(DependencyObject source,
                                      DependencyPropertyChangedEventArgs e)
    {
        TextBlock tb = (TextBlock)source;

        if (tb.Text.Length == 0)
            return;

        string textUpper = tb.Text.ToUpper();
        String toFind = ((String) e.NewValue).ToUpper();
        int firstIndex = textUpper.IndexOf(toFind);
        String firstStr = tb.Text.Substring(0, firstIndex);
        String foundStr = tb.Text.Substring(firstIndex, toFind.Length);
        String endStr = tb.Text.Substring(firstIndex + toFind.Length, 
                                         tb.Text.Length - (firstIndex + toFind.Length));

        tb.Inlines.Clear();
        var run = new Run();
        run.Text = firstStr;
        tb.Inlines.Add(run);
        run = new Run();
        run.Background = Brushes.Yellow;
        run.Text = foundStr;
        tb.Inlines.Add(run);
        run = new Run();
        run.Text = endStr;

        tb.Inlines.Add(run);
    }

    public static readonly DependencyProperty SearchTextProperty =
        DependencyProperty.Register("SearchText", 
                                    typeof(String), 
                                    typeof(SearchHightlightTextBlock), 
                                    new FrameworkPropertyMetadata(null, OnDataChanged));
}

在您看来,这是:

<view:SearchHightlightTextBlock SearchText="{Binding TextPropertyContainingTextToSearch}" 
                                Text="{Binding YourTextProperty}"/>

Here is what I came up with by building off of the exisiting TextBlock and adding a new dependency property named SearchText:

public class SearchHightlightTextBlock : TextBlock
{
    public SearchHightlightTextBlock() : base() { }

    public String SearchText { get { return (String)GetValue(SearchTextProperty); }
                               set { SetValue(SearchTextProperty, value); } }      

    private static void OnDataChanged(DependencyObject source,
                                      DependencyPropertyChangedEventArgs e)
    {
        TextBlock tb = (TextBlock)source;

        if (tb.Text.Length == 0)
            return;

        string textUpper = tb.Text.ToUpper();
        String toFind = ((String) e.NewValue).ToUpper();
        int firstIndex = textUpper.IndexOf(toFind);
        String firstStr = tb.Text.Substring(0, firstIndex);
        String foundStr = tb.Text.Substring(firstIndex, toFind.Length);
        String endStr = tb.Text.Substring(firstIndex + toFind.Length, 
                                         tb.Text.Length - (firstIndex + toFind.Length));

        tb.Inlines.Clear();
        var run = new Run();
        run.Text = firstStr;
        tb.Inlines.Add(run);
        run = new Run();
        run.Background = Brushes.Yellow;
        run.Text = foundStr;
        tb.Inlines.Add(run);
        run = new Run();
        run.Text = endStr;

        tb.Inlines.Add(run);
    }

    public static readonly DependencyProperty SearchTextProperty =
        DependencyProperty.Register("SearchText", 
                                    typeof(String), 
                                    typeof(SearchHightlightTextBlock), 
                                    new FrameworkPropertyMetadata(null, OnDataChanged));
}

And in your view, this:

<view:SearchHightlightTextBlock SearchText="{Binding TextPropertyContainingTextToSearch}" 
                                Text="{Binding YourTextProperty}"/>
奈何桥上唱咆哮 2024-07-23 11:21:50

在这里我提出另一种突出显示文本的方法。 我有一个用例,我需要在 WPF 中装饰一堆 C# 代码,但是我不想使用 textBlock.Inlines.Add 类型的语法,而是想动态生成突出显示的 XAML,然后动态添加它到 Canvas 或 WPF 中的其他容器。

因此,假设您想要对以下代码段进行着色并突出显示其中的一部分:

public static void TestLoop(int count)
{ 
   for(int i=0;i<count;i++)
     Console.WriteLine(i);
}

假设在名为 Test.txt 的文件中找到上述代码。
假设您想要将所有 C# 关键字(public、static、void 等)和简单类型(int、string)着色为蓝色,并以黄色突出显示 Console.WriteLine。

步骤 0. 创建一个新的 WPF 应用程序,并在名为 Test.txt 的文件中包含一些与上面类似的示例代码 步骤

1. 创建代码荧光笔类:

using System.IO;
using System.Text;

public enum HighLightType
{
    Type = 0,
    Keyword = 1,
    CustomTerm = 2
}

public class CodeHighlighter
{
    public static string[] KeyWords = { "public", "static", "void", "return", "while", "for", "if" };
    public static string[] Types = { "string", "int", "double", "long" };

    private string FormatCodeInXaml(string code, bool withLineBreak)
    {
        string[] mapAr = { "<","<" , //Replace less than sign
                            ">",">" }; //Replace greater than sign
        StringBuilder sb = new StringBuilder();

        using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
        {
            while (!sr.EndOfStream)
            {
                string line = sr.ReadLine();

                line = line.Replace("\t", "    "); //Replace tabs
                line = line.Replace(" ", " "); //Replace spaces

                for (int i = 0; i < mapAr.Length; i += 2)
                    line = line.Replace(mapAr[i], mapAr[i + 1]);

                if (withLineBreak)
                    sb.AppendLine(line + "<LineBreak/>"); //Replace line breaks
                else
                    sb.AppendLine(line);
            }

        }
        return sb.ToString();
    }


    private string BuildForegroundTag(string highlightText, string color)
    {
        return "<Span Foreground=\"" + color + "\">" + highlightText + "</Span>";
    }

    private string BuildBackgroundTag(string highlightText, string color)
    {
        return "<Span Background=\"" + color + "\">" + highlightText + "</Span>";
    }

    private string HighlightTerm(HighLightType type, string term, string line)
    {
        if (term == string.Empty)
            return line;

        string keywordColor = "Blue";
        string typeColor = "Blue";
        string statementColor = "Yellow";

        if (type == HighLightType.Type)
            return line.Replace(term, BuildForegroundTag(term, typeColor));
        if (type == HighLightType.Keyword)
            return line.Replace(term, BuildForegroundTag(term, keywordColor));
        if (type == HighLightType.CustomTerm)
            return line.Replace(term, BuildBackgroundTag(term, statementColor));

        return line;
    }

    public string ApplyHighlights(string code, string customTerm)
    {
        code = FormatCodeInXaml(code, true);
        customTerm = FormatCodeInXaml(customTerm, false).Trim();

        StringBuilder sb = new StringBuilder();
        using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
        {
            while (!sr.EndOfStream)
            {
                string line = sr.ReadLine();

                line = HighlightTerm(HighLightType.CustomTerm, customTerm, line);

                foreach (string keyWord in KeyWords)
                    line = HighlightTerm(HighLightType.Keyword, keyWord, line);

                foreach (string type in Types)
                    line = HighlightTerm(HighLightType.Type, type, line);

                sb.AppendLine(line);
            }
        }

        return sb.ToString();

    }
}

步骤 2. 将 Canvas XAML 标记添加到 MainWindow.xaml

<Window x:Class="TestCodeVisualizer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestCodeVisualizer"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Canvas Name="canvas" />
</Window>

步骤 3. 在您的 MainWindow.xaml 中WPF应用程序添加以下代码:(确保test.txt位于正确的位置):

using System.Text;
using System.IO;
using System.Windows;
using System.Windows.Markup;

namespace TestCodeVisualizer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            string testText = File.ReadAllText("Test.txt");
            FrameworkElement fe = GenerateHighlightedTextBlock(testText, "Console.WriteLine");
            this.canvas.Children.Add(fe);
        }


        private FrameworkElement GenerateHighlightedTextBlock(string code, string term)
        {
            CodeHighlighter ch = new CodeHighlighter();
            string uc = "<UserControl xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>[CONTENT]</UserControl>";

            string content = "<TextBlock>" + ch.ApplyHighlights(code, term) + "</TextBlock>";
            uc = uc.Replace("[CONTENT]", content);

            FrameworkElement fe = XamlReader.Load(new System.IO.MemoryStream(Encoding.UTF8.GetBytes(uc))) as FrameworkElement;
            return fe;
        }

    }
}

Here I present another Approach for highlighting text. I had a use case where I needed to decorate a bunch of C# Code in WPF, however I did not want to use textBlock.Inlines.Add type of syntax, instead I wanted to generate the highlighting XAML on the fly and then dynamically add it to a Canvas or some other container in WPF.

So suppose you want to colorize the following piece of code and also highlight a part of it:

public static void TestLoop(int count)
{ 
   for(int i=0;i<count;i++)
     Console.WriteLine(i);
}

Suppose the above code is found in a file called Test.txt .
Suppose you want to colorize all the C# keywords (public, static, void etc..) and simple types(int, string) in Blue, and Console.WriteLine highlight in yellow.

Step 0. Create a new WPF Application and include some sample code similar to above in a file called Test.txt

Step 1. Create a Code Highlighter class:

using System.IO;
using System.Text;

public enum HighLightType
{
    Type = 0,
    Keyword = 1,
    CustomTerm = 2
}

public class CodeHighlighter
{
    public static string[] KeyWords = { "public", "static", "void", "return", "while", "for", "if" };
    public static string[] Types = { "string", "int", "double", "long" };

    private string FormatCodeInXaml(string code, bool withLineBreak)
    {
        string[] mapAr = { "<","<" , //Replace less than sign
                            ">",">" }; //Replace greater than sign
        StringBuilder sb = new StringBuilder();

        using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
        {
            while (!sr.EndOfStream)
            {
                string line = sr.ReadLine();

                line = line.Replace("\t", "    "); //Replace tabs
                line = line.Replace(" ", " "); //Replace spaces

                for (int i = 0; i < mapAr.Length; i += 2)
                    line = line.Replace(mapAr[i], mapAr[i + 1]);

                if (withLineBreak)
                    sb.AppendLine(line + "<LineBreak/>"); //Replace line breaks
                else
                    sb.AppendLine(line);
            }

        }
        return sb.ToString();
    }


    private string BuildForegroundTag(string highlightText, string color)
    {
        return "<Span Foreground=\"" + color + "\">" + highlightText + "</Span>";
    }

    private string BuildBackgroundTag(string highlightText, string color)
    {
        return "<Span Background=\"" + color + "\">" + highlightText + "</Span>";
    }

    private string HighlightTerm(HighLightType type, string term, string line)
    {
        if (term == string.Empty)
            return line;

        string keywordColor = "Blue";
        string typeColor = "Blue";
        string statementColor = "Yellow";

        if (type == HighLightType.Type)
            return line.Replace(term, BuildForegroundTag(term, typeColor));
        if (type == HighLightType.Keyword)
            return line.Replace(term, BuildForegroundTag(term, keywordColor));
        if (type == HighLightType.CustomTerm)
            return line.Replace(term, BuildBackgroundTag(term, statementColor));

        return line;
    }

    public string ApplyHighlights(string code, string customTerm)
    {
        code = FormatCodeInXaml(code, true);
        customTerm = FormatCodeInXaml(customTerm, false).Trim();

        StringBuilder sb = new StringBuilder();
        using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
        {
            while (!sr.EndOfStream)
            {
                string line = sr.ReadLine();

                line = HighlightTerm(HighLightType.CustomTerm, customTerm, line);

                foreach (string keyWord in KeyWords)
                    line = HighlightTerm(HighLightType.Keyword, keyWord, line);

                foreach (string type in Types)
                    line = HighlightTerm(HighLightType.Type, type, line);

                sb.AppendLine(line);
            }
        }

        return sb.ToString();

    }
}

Step 2. Add a Canvas XAML tag to your MainWindow.xaml

<Window x:Class="TestCodeVisualizer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestCodeVisualizer"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Canvas Name="canvas" />
</Window>

Step 3. In Your WPF Application add the following code: (make sure that test.txt is in the correct location) :

using System.Text;
using System.IO;
using System.Windows;
using System.Windows.Markup;

namespace TestCodeVisualizer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            string testText = File.ReadAllText("Test.txt");
            FrameworkElement fe = GenerateHighlightedTextBlock(testText, "Console.WriteLine");
            this.canvas.Children.Add(fe);
        }


        private FrameworkElement GenerateHighlightedTextBlock(string code, string term)
        {
            CodeHighlighter ch = new CodeHighlighter();
            string uc = "<UserControl xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>[CONTENT]</UserControl>";

            string content = "<TextBlock>" + ch.ApplyHighlights(code, term) + "</TextBlock>";
            uc = uc.Replace("[CONTENT]", content);

            FrameworkElement fe = XamlReader.Load(new System.IO.MemoryStream(Encoding.UTF8.GetBytes(uc))) as FrameworkElement;
            return fe;
        }

    }
}
浪漫人生路 2024-07-23 11:21:50

我遇到了类似的问题 - 试图对大量代表报告的演示者实施文本搜索。 该报告最初被写入字符串中,我们利用 FlowDocumentViewer 的内置 ctrl-F - 它不是很好,并且有一些奇怪的选项,但已经足够了。

如果您只是想要类似的东西,您可以执行以下操作:

        <FlowDocumentScrollViewer>
            <FlowDocument>
                <Paragraph FontFamily="Lucida Console" FontSize="12">
                    <Run Text="{Binding Content, Mode=OneWay}"/>
                </Paragraph>
            </FlowDocument>
        </FlowDocumentScrollViewer>

我们决定进行重写,因为报告与程序的其余部分保持同步,并且基本上每次编辑都会更改它,每次都必须重新创建整个报告意味着这是相当慢的。 我们希望通过转向更新所需的位模型来改进这一点,但需要具有视图模型(而不仅仅是字符串)才能以合理的方式做到这一点! 然而,我们希望在更换报告之前保留搜索功能,并做得更好,并以一种颜色突出显示“当前”搜索位置,并以另一种颜色突出显示其他搜索命中。

这是我的解决方案的简化版本; 一个派生自 TextBlock 的类,添加了 HighlightingInformation 类型的依赖属性。 我没有包含名称空间和用途,因为它们很敏感。

public class HighlightingTextBlock : TextBlock
{
    public static readonly DependencyProperty HighlightingProperty =
        DependencyProperty.Register("Highlighting", typeof (HighlightingInformation), typeof (HighlightingTextBlock));

    public HighlightingInformation Highlighting
    {
        get { return (HighlightingInformation)GetValue(HighlightingProperty); }
        set { SetValue(HighlightingProperty, value); }
    }

    public HighlightingTextBlock()
    {
        AddValueChangedCallBackTo(HighlightingProperty, UpdateText);
    }

    private void AddValueChangedCallBackTo(DependencyProperty property, Action updateAction)
    {
        var descriptor = DescriptorFor(property);
        descriptor.AddValueChanged(this, (src, args) => updateAction());
    }

    private DependencyPropertyDescriptor DescriptorFor(DependencyProperty property)
    {
        return DependencyPropertyDescriptor.FromProperty(property, GetType());
    }

    private void UpdateText()
    {
        var highlighting = Highlighting;
        if (highlighting == null)
            return;
        highlighting.SetUpdateMethod(UpdateText);

        var runs = highlighting.Runs;
        Inlines.Clear();
        Inlines.AddRange(runs);
    }
}

当更改文本和突出显示列表以更新运行列表时,此类可以绑定到的类型使用 update 方法。 亮点本身看起来像这样:

public class Highlight
{
    private readonly int _length;
    private readonly Brush _colour;

    public int Start { get; private set; }

    public Highlight(int start, int length,Brush colour)
    {
        Start = start;
        _length = length;
        _colour = colour;
    }

    private string TextFrom(string currentText)
    {
        return currentText.Substring(Start, _length);
    }

    public Run RunFrom(string currentText)
    {
        return new Run(TextFrom(currentText)){Background = _colour};
    }
}

生成正确的亮点集合是一个单独的问题,我基本上通过将演示者集合视为递归搜索内容的树来解决这个问题 - 叶节点是那些具有内容和其他节点的节点只要有孩子。 如果您深度优先搜索,您会得到您期望的顺序。 然后,您基本上可以在结果列表周围编写一个包装器来跟踪位置。 我不会发布所有代码 - 我在这里的回应是记录如何让 wpf 以 MVP 风格进行多色突出显示。

我在这里没有使用 INotifyPropertyChangedCollectionChanged ,因为我们不需要多播更改(例如,一个演示者有多个视图)。 最初,我尝试通过添加一个文本事件更改通知和一个列表事件更改通知(您还必须手动订阅 INotifyCollectionChanged 事件)来实现这一点。 然而,我担心事件订阅会导致内存泄漏,而且文本和突出显示的更新不同时出现这一事实导致了问题。

这种方法的一个缺点是人们不应该绑定到该控件的 Text 属性。 在真实版本中,我添加了一些检查+异常抛出来阻止人们这样做,但为了清楚起见,我从示例中省略了它!

I had a similar problem - trying to implement a text search over a load of presenters that basically represent a report. The report was originally written into a string and we were leveraging FlowDocumentViewer's built in ctrl-F - it's not very good and has some wierd options but was sufficient.

If you just want something like that you can do the following:

        <FlowDocumentScrollViewer>
            <FlowDocument>
                <Paragraph FontFamily="Lucida Console" FontSize="12">
                    <Run Text="{Binding Content, Mode=OneWay}"/>
                </Paragraph>
            </FlowDocument>
        </FlowDocumentScrollViewer>

We decided to go for a rewrite as the report is kept in sync with the rest of the program and basically every edit changes it, having to recreate the entire report everytime means that this is quite slow. We wanted to improve this by moving to a update-the-bits-you-need-to model but needed to have view model (rather than just a string) to be able to do that in a sane way! We wanted to preserve the searching functionality before swapping out the report however and go one better and have highlighting of the 'current' search position in one colour and other search hits in another.

Here's a simplified version of my solution; a class that derives from TextBlock that adds a dependency property of Type HighlightingInformation. I've not included the namespace and usings as they are sensitive.

public class HighlightingTextBlock : TextBlock
{
    public static readonly DependencyProperty HighlightingProperty =
        DependencyProperty.Register("Highlighting", typeof (HighlightingInformation), typeof (HighlightingTextBlock));

    public HighlightingInformation Highlighting
    {
        get { return (HighlightingInformation)GetValue(HighlightingProperty); }
        set { SetValue(HighlightingProperty, value); }
    }

    public HighlightingTextBlock()
    {
        AddValueChangedCallBackTo(HighlightingProperty, UpdateText);
    }

    private void AddValueChangedCallBackTo(DependencyProperty property, Action updateAction)
    {
        var descriptor = DescriptorFor(property);
        descriptor.AddValueChanged(this, (src, args) => updateAction());
    }

    private DependencyPropertyDescriptor DescriptorFor(DependencyProperty property)
    {
        return DependencyPropertyDescriptor.FromProperty(property, GetType());
    }

    private void UpdateText()
    {
        var highlighting = Highlighting;
        if (highlighting == null)
            return;
        highlighting.SetUpdateMethod(UpdateText);

        var runs = highlighting.Runs;
        Inlines.Clear();
        Inlines.AddRange(runs);
    }
}

The type this class can be bound to uses the update method when it's text and list of highlights are changed to update the list of Runs. The highlights themselves look something like this:

public class Highlight
{
    private readonly int _length;
    private readonly Brush _colour;

    public int Start { get; private set; }

    public Highlight(int start, int length,Brush colour)
    {
        Start = start;
        _length = length;
        _colour = colour;
    }

    private string TextFrom(string currentText)
    {
        return currentText.Substring(Start, _length);
    }

    public Run RunFrom(string currentText)
    {
        return new Run(TextFrom(currentText)){Background = _colour};
    }
}

To produce the correct collection of highlights is a seperate problem, which I basically solved by treating the collection of presenters as a Tree that you recursively search for content - leaf nodes are those that have content and other nodes just have children. If you search depth-first you get the order you'd expect. You can then basically write a wrapper around the list of results to keep track of the position. Im not going to post all the code for this - my response here it is to document how you can make wpf do multi-coloured highlighting in MVP style.

I haven't used INotifyPropertyChanged or CollectionChanged here as we didn't need the changes to be multi-cast (eg one presenter has multiple views). Initially I tried to do that by adding an event changed notification for Text and one for a list (which you also have to manually subscribe to the INotifyCollectionChanged event on). I had concerns about memory leaks from the event subcriptions however and the fact that the updates for the text and the highlights didn't come at the same time made it problematic.

The one drawback of this approach is that people shouldn't bind to the Text property of this control. In the real version I have added some checking + exception throwing to stop people from doing this but ommitted it from the example for clarity's sake!

弥枳 2024-07-23 11:21:50

最终编写了以下代码

目前几乎没有错误,但解决了问题

if (Main.IsFullTextSearch)
{
    for (int i = 0; i < runs.Count; i++)
    {
        if (runs[i] is Run)
        {
            Run originalRun = (Run)runs[i];

            if (Main.SearchCondition != null && originalRun.Text.ToLower()
                .Contains(Main.SearchCondition.ToLower()))
            {
                int pos = originalRun.Text.ToLower()
                          .IndexOf(Main.SearchCondition.ToLower());

                if (pos > 0)
                {
                    Run preRun = CloneRun(originalRun);
                    Run postRun = CloneRun(originalRun);

                    preRun.Text = originalRun.Text.Substring(0, pos);
                    postRun.Text = originalRun.Text
                        .Substring(pos + Main.SearchCondition.Length);

                    runs.Insert(i - 1 < 0 ? 0 : i - 1, preRun);
                    runs.Insert(i + 1, new Run(" "));
                    runs.Insert(i + 2, postRun);

                    originalRun.Text = originalRun.Text
                        .Substring(pos, Main.SearchCondition.Length);

                    SolidColorBrush brush = new SolidColorBrush(Colors.Yellow);
                    originalRun.Background = brush;

                    i += 3;
                }
            }
        }
    }
}

Ended up writing following code

At moment has few bugs, but solves the problem

if (Main.IsFullTextSearch)
{
    for (int i = 0; i < runs.Count; i++)
    {
        if (runs[i] is Run)
        {
            Run originalRun = (Run)runs[i];

            if (Main.SearchCondition != null && originalRun.Text.ToLower()
                .Contains(Main.SearchCondition.ToLower()))
            {
                int pos = originalRun.Text.ToLower()
                          .IndexOf(Main.SearchCondition.ToLower());

                if (pos > 0)
                {
                    Run preRun = CloneRun(originalRun);
                    Run postRun = CloneRun(originalRun);

                    preRun.Text = originalRun.Text.Substring(0, pos);
                    postRun.Text = originalRun.Text
                        .Substring(pos + Main.SearchCondition.Length);

                    runs.Insert(i - 1 < 0 ? 0 : i - 1, preRun);
                    runs.Insert(i + 1, new Run(" "));
                    runs.Insert(i + 2, postRun);

                    originalRun.Text = originalRun.Text
                        .Substring(pos, Main.SearchCondition.Length);

                    SolidColorBrush brush = new SolidColorBrush(Colors.Yellow);
                    originalRun.Background = brush;

                    i += 3;
                }
            }
        }
    }
}
狼亦尘 2024-07-23 11:21:50

如果您正在为 ListViewBase 处理 ContainerContentChanging,则可以采用以下方法:WinRT/ContainerContentChanging 的 TextBlock 突出显示

请注意,这代码适用于 Windows RT。 WPF 语法会略有不同。 另请注意,如果您使用绑定来填充 TextBlock.Text 属性,则我的方法生成的文本将被覆盖。 我使用 ContainerContentChanging 来填充目标字段,因为与普通绑定相比,性能显着提高,内存使用也得到改善。 我仅使用绑定来管理源数据,而不是数据视图。

If you are handling ContainerContentChanging for your ListViewBase, you can take the following approach: TextBlock highlighting for WinRT/ContainerContentChanging

Please note that this code is for Windows RT. The WPF syntax will be slightly different. Also note that if you are using binding to populate the TextBlock.Text property, the text generated by my approach will be overwritten. I use ContainerContentChanging to populate target fields because of radically-increased performance and improvements in memory usage, vs. normal binding. I use binding only to manage the source data, not the data view.

风月客 2024-07-23 11:21:50

以下突出显示搜索方法采用您的 TextBlock搜索词,然后返回包含该词或包含该词的单词的块(以紫色突出显示)。

    private TextBlock HighlightSearch(TextBlock textBlock, string searchTerm)
    {
        string[] words = textBlock.Text.Split(' ');

        textBlock.Text = string.Empty; 
        
        foreach (string word in words)
        {
            if (!string.IsNullOrEmpty(searchTerm) &&
                word.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0)
            {
                textBlock.Inlines.Add(new Run($"{word} ") { Foreground = Brushes.Purple, FontWeight = FontWeights.DemiBold });
            }
            else
            {
                textBlock.Inlines.Add($"{word} ");
            }
        }

        return textBlock; 
    }

`

The following highlight search method takes your TextBlock and search term then returns your block with this term or words which contain this term highlighted purple.

    private TextBlock HighlightSearch(TextBlock textBlock, string searchTerm)
    {
        string[] words = textBlock.Text.Split(' ');

        textBlock.Text = string.Empty; 
        
        foreach (string word in words)
        {
            if (!string.IsNullOrEmpty(searchTerm) &&
                word.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0)
            {
                textBlock.Inlines.Add(new Run(
quot;{word} ") { Foreground = Brushes.Purple, FontWeight = FontWeights.DemiBold });
            }
            else
            {
                textBlock.Inlines.Add(
quot;{word} ");
            }
        }

        return textBlock; 
    }

`

等待我真够勒 2024-07-23 11:21:50

我的要求是突出显示必须完全可以设置样式,而不仅仅是一些预定义的选项:

public partial class HighlightTextBlock : UserControl
{
    public HighlightTextBlock()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty TextBlockStyleProperty = DependencyProperty.Register(
        nameof(TextBlockStyle), typeof(Style), typeof(HighlightTextBlock), new PropertyMetadata(default(Style)));
    public Style TextBlockStyle
    {
        get { return (Style)GetValue(TextBlockStyleProperty); }
        set { SetValue(TextBlockStyleProperty, value); }
    }

    public static readonly DependencyProperty HighlightTextElementStyleProperty = DependencyProperty.Register(
        nameof(HighlightTextElementStyle), typeof(Style), typeof(HighlightTextBlock), new PropertyMetadata(default(Style)));
    public Style HighlightTextElementStyle
    {
        get { return (Style)GetValue(HighlightTextElementStyleProperty); }
        set { SetValue(HighlightTextElementStyleProperty, value); }
    }

    public static readonly DependencyProperty NormalTextElementStyleProperty = DependencyProperty.Register(
        nameof(NormalTextElementStyle), typeof(Style), typeof(HighlightTextBlock), new PropertyMetadata(default(Style)));
    public Style NormalTextElementStyle
    {
        get { return (Style)GetValue(NormalTextElementStyleProperty); }
        set { SetValue(NormalTextElementStyleProperty, value); }
    }

    public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
        nameof(Text), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(default(string), PropertyChangedCallback));
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty HighlightProperty = DependencyProperty.Register(
        nameof(Highlight), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(default(string), PropertyChangedCallback));
    public string Highlight
    {
        get { return (string)GetValue(HighlightProperty); }
        set { SetValue(HighlightProperty, value); }
    }
    
    private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        d.EnsureType<HighlightTextBlock>().update();
    }
    
    private void update()
    {
        var highlightLength = this.Highlight?.Length ?? 0;

        if (highlightLength > 0)
        {
            var highlightOffset = this.Text?.IndexOf(this.Highlight, StringComparison.InvariantCultureIgnoreCase) ?? -1;

            if (highlightOffset > -1)
            {
                PrefixRun.Text = this.Text.Substring(0, highlightOffset);
                HighlightRun.Text = this.Text.Substring(highlightOffset, highlightLength);
                SuffixRun.Text = this.Text.Substring(highlightOffset + highlightLength);
                return;
            }
        }

        PrefixRun.Text = this.Text;
        HighlightRun.Text = null;
        SuffixRun.Text = null;
    }
}

Mind HighlightPropertyTextProperty 使用的 PropertyChangedCallback

XAML:

<UserControl x:Class="Example.HighlightTextBlock"
             x:Name="self"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
    <Grid>
        <TextBlock DataContext="{Binding ElementName=self}" Style="{Binding TextBlockStyle}">
            <!-- NOTE: TO avoid whitespaces when rendering Inlines, avoid them in markup (e.g. between Run tags)-->
            <TextBlock.Inlines><Run 
                                    x:Name="PrefixRun"     x:FieldModifier="private" Style="{Binding NormalTextElementStyle}"/><Run 
                                    x:Name="HighlightRun"  x:FieldModifier="private" Style="{Binding HighlightTextElementStyle}"/><Run 
                                    x:Name="SuffixRun"     x:FieldModifier="private" Style="{Binding NormalTextElementStyle}"/></TextBlock.Inlines>
        </TextBlock>
    </Grid>
</UserControl>

数据模板:

<DataTemplate x:Key="ExampleDataTemplate">
    <DataTemplate.Resources>
        <Style x:Key="HighlightTextElementStyle" TargetType="{x:Type Inline}">
            <Setter Property="Foreground" Value="DarkGray"/>
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="TextDecorations" Value="Underline"/>
        </Style>
        
        <Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="HorizontalAlignment" Value="Stretch"/>
            <Setter Property="TextAlignment" Value="Left"/>
        </Style>
    </DataTemplate.Resources>

    <controls1:HighlightTextBlock Text="{Binding ExampleText}"
                                  Highlight="{Binding ExampleHighlight}"
                                  TextBlockStyle="{StaticResource TextBlockStyle}"
                                  HighlightTextElementStyle="{StaticResource HighlightTextElementStyle}"/>
</DataTemplate>

The requirement I had was highlighting must be fully style-able beyond just a few pre-defined options:

public partial class HighlightTextBlock : UserControl
{
    public HighlightTextBlock()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty TextBlockStyleProperty = DependencyProperty.Register(
        nameof(TextBlockStyle), typeof(Style), typeof(HighlightTextBlock), new PropertyMetadata(default(Style)));
    public Style TextBlockStyle
    {
        get { return (Style)GetValue(TextBlockStyleProperty); }
        set { SetValue(TextBlockStyleProperty, value); }
    }

    public static readonly DependencyProperty HighlightTextElementStyleProperty = DependencyProperty.Register(
        nameof(HighlightTextElementStyle), typeof(Style), typeof(HighlightTextBlock), new PropertyMetadata(default(Style)));
    public Style HighlightTextElementStyle
    {
        get { return (Style)GetValue(HighlightTextElementStyleProperty); }
        set { SetValue(HighlightTextElementStyleProperty, value); }
    }

    public static readonly DependencyProperty NormalTextElementStyleProperty = DependencyProperty.Register(
        nameof(NormalTextElementStyle), typeof(Style), typeof(HighlightTextBlock), new PropertyMetadata(default(Style)));
    public Style NormalTextElementStyle
    {
        get { return (Style)GetValue(NormalTextElementStyleProperty); }
        set { SetValue(NormalTextElementStyleProperty, value); }
    }

    public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
        nameof(Text), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(default(string), PropertyChangedCallback));
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty HighlightProperty = DependencyProperty.Register(
        nameof(Highlight), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(default(string), PropertyChangedCallback));
    public string Highlight
    {
        get { return (string)GetValue(HighlightProperty); }
        set { SetValue(HighlightProperty, value); }
    }
    
    private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        d.EnsureType<HighlightTextBlock>().update();
    }
    
    private void update()
    {
        var highlightLength = this.Highlight?.Length ?? 0;

        if (highlightLength > 0)
        {
            var highlightOffset = this.Text?.IndexOf(this.Highlight, StringComparison.InvariantCultureIgnoreCase) ?? -1;

            if (highlightOffset > -1)
            {
                PrefixRun.Text = this.Text.Substring(0, highlightOffset);
                HighlightRun.Text = this.Text.Substring(highlightOffset, highlightLength);
                SuffixRun.Text = this.Text.Substring(highlightOffset + highlightLength);
                return;
            }
        }

        PrefixRun.Text = this.Text;
        HighlightRun.Text = null;
        SuffixRun.Text = null;
    }
}

Mind PropertyChangedCallback used by HighlightProperty and TextProperty.

XAML:

<UserControl x:Class="Example.HighlightTextBlock"
             x:Name="self"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
    <Grid>
        <TextBlock DataContext="{Binding ElementName=self}" Style="{Binding TextBlockStyle}">
            <!-- NOTE: TO avoid whitespaces when rendering Inlines, avoid them in markup (e.g. between Run tags)-->
            <TextBlock.Inlines><Run 
                                    x:Name="PrefixRun"     x:FieldModifier="private" Style="{Binding NormalTextElementStyle}"/><Run 
                                    x:Name="HighlightRun"  x:FieldModifier="private" Style="{Binding HighlightTextElementStyle}"/><Run 
                                    x:Name="SuffixRun"     x:FieldModifier="private" Style="{Binding NormalTextElementStyle}"/></TextBlock.Inlines>
        </TextBlock>
    </Grid>
</UserControl>

DataTemplate:

<DataTemplate x:Key="ExampleDataTemplate">
    <DataTemplate.Resources>
        <Style x:Key="HighlightTextElementStyle" TargetType="{x:Type Inline}">
            <Setter Property="Foreground" Value="DarkGray"/>
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="TextDecorations" Value="Underline"/>
        </Style>
        
        <Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="HorizontalAlignment" Value="Stretch"/>
            <Setter Property="TextAlignment" Value="Left"/>
        </Style>
    </DataTemplate.Resources>

    <controls1:HighlightTextBlock Text="{Binding ExampleText}"
                                  Highlight="{Binding ExampleHighlight}"
                                  TextBlockStyle="{StaticResource TextBlockStyle}"
                                  HighlightTextElementStyle="{StaticResource HighlightTextElementStyle}"/>
</DataTemplate>
匿名的好友 2024-07-23 11:21:50

我发现有一个组件 EasyHighlightTextBlock 可能非常适合解决您的问题。 它使用 突出显示文本。 像这样:
查看此处

希望对您有所帮助。

There is a component EasyHighlightTextBlock that I found may be quite good to solve your problem. It uses the <tags></tags> to highlight the text. Like this:
check here to see

I hope it can be helpful for you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文