2009-04-15 33 views
11

Tôi có TextBlock có Inlines động được thêm vào nó (về cơ bản là các đối tượng Run có in nghiêng hoặc in đậm).WPF TextBlock làm nổi bật một số phần nhất định dựa trên điều kiện tìm kiếm

Trong ứng dụng của tôi, tôi có chức năng tìm kiếm.

Tôi muốn có thể làm nổi bật văn bản của TextBlock đang được tìm kiếm.

Bằng cách làm nổi bật, tôi có nghĩa là thay đổi một số phần nhất định của màu văn bản TextBlock (lưu ý rằng nó có thể làm nổi bật một số đối tượng Run khác nhau tại một thời điểm).

Tôi đã thử ví dụ này http://blogs.microsoft.co.il/blogs/tamir/archive/2008/05/12/search-and-highlight-any-text-on-wpf-rendered-page.aspx

Nhưng nó seams rất không ổn định :(

Có cách dễ dàng để giải quyết vấn đề này?

Trả lời

14

Câu hỏi này tương tự như How to display search results in a WPF items control with highlighted query terms

Để trả lời cho câu hỏi đó, tôi đã đưa ra cách tiếp cận sử dụng IValueConverter. Trình biến đổi lấy một đoạn văn bản, định dạng nó thành đánh dấu XAML hợp lệ và sử dụng một XamlReader để nhanh chóng đánh dấu các đối tượng khung công tác.

Lời giải thích đầy đủ là khá dài, vì vậy tôi đã đăng nó vào blog của tôi: Highlighting Query Terms in a WPF TextBlock

0

Đã kết thúc viết code sau

Hiện nay có vài lỗi, nhưng giải quyết được vấn đề

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

Thật vậy, bạn có thể đăng toàn bộ mã hoặc liên kết không? –

2

Tôi đã có một vấn đề tương tự - cố gắng để thực hiện một tìm kiếm văn bản trên một tải của các diễn giả rằng về cơ bản đại diện cho một báo cáo. Báo cáo ban đầu được viết thành một chuỗi và chúng tôi đã tận dụng xây dựng của ctrl-F FlowDocumentViewer được xây dựng trong - nó không phải là rất tốt và có một số tùy chọn khủng khiếp hơn nhưng là đủ.

Nếu bạn chỉ muốn một cái gì đó như thế bạn có thể làm như sau:

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

Chúng tôi quyết định để đi cho một viết lại như báo cáo được giữ đồng bộ với phần còn lại của chương trình và về cơ bản tất cả các chỉnh sửa thay đổi nó , phải tạo lại toàn bộ báo cáo mọi lúc có nghĩa là điều này khá chậm. Chúng tôi muốn cải thiện điều này bằng cách chuyển sang mô hình cập nhật-bit-bạn-cần-để nhưng cần phải có mô hình xem (chứ không phải chỉ là một chuỗi) để có thể thực hiện điều đó một cách lành mạnh! Tuy nhiên, chúng tôi muốn giữ lại chức năng tìm kiếm trước khi trao đổi báo cáo và chuyển sang một báo cáo tốt hơn và làm nổi bật vị trí tìm kiếm 'hiện tại' trong một màu và các lần truy cập tìm kiếm khác trong một màu khác.

Đây là phiên bản đơn giản của giải pháp của tôi; một lớp có nguồn gốc từ TextBlock có thêm thuộc tính dependency của Type HighlightingInformation. Tôi đã không bao gồm không gian tên và sử dụng khi chúng nhạy cảm.

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

Loại lớp này có thể bị ràng buộc sử dụng phương thức cập nhật khi văn bản và danh sách nổi bật được thay đổi để cập nhật danh sách Chạy.Những điểm nổi bật bản thân giống như thế này:

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

Để tạo ra bộ sưu tập đúng điểm nổi bật là một vấn đề riêng biệt, mà tôi về cơ bản đã giải quyết bằng cách xử lý các bộ sưu tập của các diễn giả như là một cái cây mà bạn đệ quy tìm kiếm nội dung - các nút lá là những người có nội dung và các nút khác chỉ có con. Nếu bạn tìm kiếm chiều sâu đầu tiên bạn nhận được thứ tự bạn mong đợi. Sau đó, bạn có thể viết một trình bao bọc xung quanh danh sách các kết quả để theo dõi vị trí. Im sẽ không đăng tất cả các mã cho điều này - phản ứng của tôi ở đây nó là tài liệu làm thế nào bạn có thể làm cho wpf làm nổi bật nhiều màu trong phong cách MVP.

Tôi chưa sử dụng INotifyPropertyChanged hoặc CollectionChanged ở đây vì chúng tôi không cần thay đổi để có nhiều diễn viên (ví dụ: một người trình bày có nhiều chế độ xem). Ban đầu tôi đã cố gắng làm điều đó bằng cách thêm một sự kiện thay đổi thông báo cho văn bản và một cho một danh sách (mà bạn cũng phải tự đăng ký sự kiện INotifyCollectionChanged trên). Tôi đã có mối quan tâm về rò rỉ bộ nhớ từ các đăng ký sự kiện tuy nhiên và thực tế là các bản cập nhật cho văn bản và các điểm nổi bật đã không đến cùng một lúc làm cho nó có vấn đề.

Một nhược điểm của phương pháp này là mọi người không nên liên kết với thuộc tính Văn bản của điều khiển này. Trong phiên bản thực tế, tôi đã thêm một số kiểm tra + ngoại lệ ném để ngăn chặn mọi người làm điều này, nhưng nó từ nó cho ví dụ rõ ràng vì lợi ích của!

5

Tôi lấy dthrasers answer và rút ra sự cần thiết cho một trình phân tích cú pháp XML. Ông làm một công việc tuyệt vời giải thích từng mảnh trong his blog, Tuy nhiên điều này không yêu cầu tôi thêm bất kỳ thư viện phụ, đây là cách tôi đã làm nó.

Bước một, tạo ra một lớp chuyển đổi:

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

    } 

Bước hai: Thay vì một TextBlock sử dụng một ContentBlock. Vượt qua trong chuỗi (bạn sẽ sử dụng cho các TextBlock của bạn) vào khối nội dung, như vậy:

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

Bước ba: Hãy chắc chắn rằng các thử nghiệm bạn vượt qua trong được tokenized với |~S~||~E~|. Và để làm nổi bật bắt đầu!

Ghi chú:
Bạn có thể thay đổi phong cách trong thời gian để xác định những gì và làm thế nào văn bản của bạn được đánh dấu
Hãy chắc chắn rằng bạn thêm lớp Chuyển đổi của bạn để không gian tên và các nguồn lực của bạn. Điều này cũng có thể yêu cầu xây dựng lại để làm việc.

0

Nếu bạn đang xử lý ContainerContentChanging cho ListViewBase của bạn, bạn có thể có những phương pháp sau đây: TextBlock highlighting for WinRT/ContainerContentChanging

Xin lưu ý rằng mã này là dành cho Windows RT. Cú pháp WPF sẽ hơi khác một chút. Cũng lưu ý rằng nếu bạn đang sử dụng ràng buộc để điền thuộc tính TextBlock.Text, văn bản được tạo bởi cách tiếp cận của tôi sẽ bị ghi đè. Tôi sử dụng ContainerContentChanging để điền vào các trường mục tiêu vì hiệu suất và cải tiến được tăng cường triệt để trong việc sử dụng bộ nhớ, so với ràng buộc bình thường. Tôi chỉ sử dụng ràng buộc để quản lý dữ liệu nguồn chứ không phải chế độ xem dữ liệu.

2

Bằng sự trùng hợp kỳ lạ, gần đây tôi đã viết một bài viết giải quyết cùng một vấn đề.Nó là một điều khiển tùy chỉnh có các thuộc tính giống như một TextBlock (vì vậy bạn có thể hoán đổi cho một bất kỳ nơi nào bạn cần), và nó có thêm một thuộc tính mà bạn có thể liên kết với tên là HighLightText, và bất cứ nơi nào giá trị HighLightText là được tìm thấy trong thuộc tính chính Text (không phân biệt chữ hoa chữ thường), nó được tô sáng.

Đó là một điều khiển khá thẳng về phía trước để tạo ra, và bạn có thể tìm thấy các bài viết ở đây:

WPF TextBlock With Search String Matching

Và mã đầy đủ như một giải pháp ở đây:

HighlightSearchMatchTextBlock (GitHub)

1

Dưới đây là những gì tôi nghĩ ra bằng cách xây dựng khỏi số TextBlock hiện có và thêm thuộc tính phụ thuộc mới có tên 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)); 
} 

Và theo quan điểm của bạn, điều này:

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

Ở đây tôi giới thiệu một cách tiếp cận để làm nổi bật văn bản. Tôi đã có một trường hợp sử dụng, nơi tôi cần phải trang trí một loạt các C# Mã trong WPF, tuy nhiên tôi không muốn sử dụng textBlock.Inlines.Add loại cú pháp, thay vào đó tôi muốn tạo ra các XAML làm nổi bật trên bay và sau đó tự động thêm nó vào Canvas hoặc một số vùng chứa khác trong WPF.

Vì vậy, giả sử bạn muốn tô màu cho đoạn mã sau và cũng có thể làm nổi bật một phần của nó:

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

Giả mã trên được tìm thấy trong một tập tin gọi là Test.txt. Giả sử bạn muốn tô màu tất cả các từ khóa C# (công khai, tĩnh, void vv ..) và các loại đơn giản (int, string) trong Blue và Console.WriteLine làm nổi bật màu vàng.

Bước 0. Tạo một ứng dụng WPF mới và bao gồm một số mẫu mã tương tự như trên trong một tập tin gọi là Test.txt

Bước 1. Tạo một lớp Mã Highlighter:

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

    } 
} 

Bước 2. Thêm một thẻ Canvas XAML để MainWindow.xaml bạn

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

Bước 3. trong ứng dụng WPF của bạn thêm đoạn mã sau: (hãy chắc chắn rằng test.txt là ở đúng vị trí):

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

    } 
} 
Các vấn đề liên quan