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

问题描述 投票:14回答:8

我有TextBlock,动态添加了Inlines(基本上是一堆斜体或粗体的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

但它似乎很不稳定:(

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

wpf highlighting textblock
8个回答
17
投票

这个问题类似于How to display search results in a WPF items control with highlighted query terms

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

完整的解释相当长,所以我把它发布到我的博客:Highlighting Query Terms in a WPF TextBlock


10
投票

我拿了dthrasers answer并且需要一个XML解析器。他很好地解释了his blog中的每一个部分,但是这并没有要求我添加任何额外的库,这就是我如何做到的。

第一步,制作转换器类:

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~|标记的。让突出显示开始!

笔记: 您可以更改运行中的样式以确定文本突出显示的内容和方式 确保将Converter类添加到命名空间和资源中。这可能还需要重建才能正常工作。


4
投票

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

这是一个相当简单的创建控件,你可以在这里找到这篇文章:

WPF TextBlock With Search String Matching

完整的代码作为解决方案:

SearchMatchTextblock(GitHub)


2
投票

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

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

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

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

这是我的解决方案的简化版本;从TextBlock派生的类,它添加Type 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);
    }
}

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

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风格进行多色突出显示。

我没有在这里使用INotifyPropertyChanged或CollectionChanged,因为我们不需要对多播进行更改(例如,一个演示者有多个视图)。最初我尝试通过为Text添加事件更改通知和为列表添加一个通知(您还必须手动订阅INotifyCollectionChanged事件)。我担心事件子描述中的内存泄漏,以及文本和突出显示的更新未同时发生的事实使其成为问题。

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


1
投票

这是我通过构建现有的qazxsw poi并添加一个名为qazxsw poi的新依赖属性来实现的:

TextBlock

在您看来,这个:

SearchText

1
投票

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

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

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

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

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

步骤1.创建Code Highlighter类:

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

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

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

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

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 = { "<","&lt;" , //Replace less than sign
                            ">","&gt;" }; //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", "&#160;&#160;&#160;&#160;"); //Replace tabs
                line = line.Replace(" ", "&#160;"); //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();

    }
}

0
投票

结束编写以下代码

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

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

0
投票

如果您正在处理ListViewBase的ContainerContentChanging,则可以采用以下方法: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; } } }

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

© www.soinside.com 2019 - 2024. All rights reserved.