2009-04-15 138 views
11

我有TextBlock,它具有动态添加到它的Inline(基本上一堆斜体或粗体的Run对象)。WPF TextBlock根据搜索条件突出显示某些部分

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

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

通过突出显示我的意思是更改TextBlock文本颜色的某些部分(请记住,它可能一次突出显示几个不同的Run对象)。

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

但它的接缝非常不稳定:(

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

回答

0

最后写下面的代码

在此刻有一些错误,但是解决问题

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

确实,您可以发布完整的代码或链接? – 2010-12-18 22:45:40

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

类型这个类可以绑定到使用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风格进行多色突出显示。

我没有在这里使用INotifyPropertyChanged或CollectionChanged,因为我们不需要这些更改是多播(例如一个演示者有多个视图)。最初,我尝试通过为文本添加事件更改通知并为列表添加一个通知(您还必须手动订阅INotifyCollectionChanged事件)。然而,我对事件描述中的内存泄漏表示担忧,而且文本和亮点的更新并不是同时发生,这使得它成为问题。

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

5

我拿了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."); 
     } 

    } 

第二步: 相反TextBlock的使用内文。通过在字符串中(你的使用你的文本块)的内容块,像这样:

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

第三步: 确保你通过测试标记化与|~S~||~E~|。让突出显示开始吧!

注:
你可以改变风格在运行,以确定什么和如何你的文本被高亮
确保你把你的转换器类,以您的命名空间和资源。这可能也需要重建才能正常工作。

0

如果你正在处理ContainerContentChanging您ListViewBase,你可以采取以下方法:TextBlock highlighting for WinRT/ContainerContentChanging

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

2

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

这是一个相当直接的控制,以创建,你可以在这里找到相应的文章:

WPF TextBlock With Search String Matching

而且完整的代码如下解决方案:

HighlightSearchMatchTextBlock (GitHub)

1

以下是我通过构建现有的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}"/> 
1

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

所以假设你要上色下面的一段代码,也突出了它的一部分:

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 = { "<","&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(); 

    } 
} 

步骤2。画布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步:在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; 
     } 

    } 
}