2009-04-15 20 views
11

J'ai TextBlock qui contient des Inlines ajoutés dynamiquement (essentiellement des objets Run italiques ou gras).WPF TextBlock met en surbrillance certaines parties en fonction de la condition de recherche

Dans mon application, j'ai la fonction de recherche.

Je souhaite être en mesure de mettre en évidence le texte de TextBlock en cours de recherche. En mettant en surbrillance je veux dire changer certaines parties de la couleur du texte de TextBlock (en gardant à l'esprit qu'il peut mettre en évidence plusieurs objets Run différents à la fois).

J'ai essayé cet exemple http://blogs.microsoft.co.il/blogs/tamir/archive/2008/05/12/search-and-highlight-any-text-on-wpf-rendered-page.aspx

Mais coutures très instable :(

Y at-il moyen facile de résoudre ce problème?

Répondre

14

Cette question est similaire à How to display search results in a WPF items control with highlighted query terms

En réponse à cette question, je suis venu avec une approche qui utilise un IValueConverter. Le convertisseur prend un extrait de texte, le met en forme dans un balisage XAML valide et utilise un XamlReader pour instancier le balisage dans les objets de structure.

L'explication complète est assez longue, donc je l'ai posté sur mon blog: Highlighting Query Terms in a WPF TextBlock

0

écriture fini par code suivant

Au moment, a quelques bugs, mais résout le problème

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

En effet, pouvez-vous poster le code complet ou un lien? –

2

J'ai eu un problème similaire - en essayant de mettre en œuvre une recherche de texte sur une charge de présentateurs qui représentent essentiellement un rapport. Le rapport a été écrit à l'origine dans une chaîne et nous utilisions FlowDocumentViewer intégré dans ctrl-F - ce n'est pas très bon et a quelques options wierd mais était suffisant.

Si vous voulez juste quelque chose comme ça, vous pouvez faire ce qui suit:

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

Nous avons décidé d'aller pour une ré-écriture que le rapport est maintenu en phase avec le reste du programme et essentiellement tous les modifier le change , devoir recréer le rapport entier à chaque fois signifie que c'est assez lent. Nous voulions améliorer cela en passant à un modèle de mise à jour-les-bits-que-vous-deviez, mais il fallait avoir un modèle de vue (plutôt qu'une simple chaîne de caractères) pour être capable de le faire de façon sensée! Nous voulions cependant préserver la fonctionnalité de recherche avant d'échanger le rapport et d'aller encore mieux et de mettre en évidence la position de recherche «actuelle» dans une couleur et les autres occurrences de recherche dans une autre.

Voici une version simplifiée de ma solution; une classe dérivée de TextBlock qui ajoute une propriété de dépendance de type HighlightingInformation. Je n'ai pas inclus l'espace de noms et les utilisations car ils sont sensibles.

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

Le type de cette classe peut être lié à utilise la méthode de mise à jour quand il est le texte et la liste des faits saillants sont modifiés pour mettre à jour la liste des courses.Les temps forts se regardent quelque chose comme ceci:

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

Pour produire la collection correcte des faits saillants est un problème séparé, que je fondamentalement résolu en traitant la collecte des présentateurs comme un arbre que vous effectuez une recherche récursive pour le contenu - nœuds feuilles sont ceux qui ont du contenu et d'autres nœuds ont juste des enfants. Si vous recherchez la profondeur, vous obtenez l'ordre auquel vous vous attendez. Vous pouvez ensuite écrire un wrapper autour de la liste des résultats pour garder une trace de la position. Je ne vais pas poster tout le code pour cela - ma réponse ici est de documenter comment vous pouvez faire wpf faire la mise en évidence multicolore dans le style MVP.

Je n'ai pas utilisé INotifyPropertyChanged ou CollectionChanged ici car nous n'avions pas besoin que les modifications soient multi-cast (par exemple, un présentateur a plusieurs vues). Initialement j'ai essayé de faire cela en ajoutant un événement a changé la notification pour le texte et un pour une liste (que vous devez également souscrire manuellement à l'événement INotifyCollectionChanged dessus). J'ai eu des inquiétudes au sujet des fuites de mémoire des souscriptions d'événement cependant et le fait que les mises à jour pour le texte et les faits saillants ne soient pas venus en même temps l'ont rendu problématique.

Le seul inconvénient de cette approche est que les gens ne devraient pas se lier à la propriété Text de ce contrôle. Dans la version réelle, j'ai ajouté quelques vérifications + jet d'exception pour empêcher les gens de le faire mais je l'ai omis de l'exemple pour des raisons de clarté!

5

J'ai pris dthrasers answer et ai supprimé le besoin d'un analyseur XML. Il fait un excellent travail en expliquant chacune des pièces dans his blog, mais cela ne m'a pas obligé à ajouter des bibliothèques supplémentaires, voici comment je l'ai fait.

Première étape, faire une classe de conversion:

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

    } 

Deuxième étape: Au lieu d'un TextBlock utiliser un ContentBlock. Passez dans la chaîne (vous de utilisée pour votre textBlock) au bloc de contenu, comme ceci:

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

Troisième étape: Assurez-vous que le test que vous passez tokenizé avec |~S~| et |~E~|. Et que la mise en évidence commence!

Remarques:
Vous pouvez changer le style dans la course pour déterminer quoi et comment votre texte est mis en surbrillance
Assurez-vous que vous ajoutez votre classe Converter à votre espace de noms et les ressources. Cela peut également nécessiter une reconstruction pour fonctionner.

0

Si vous gérez ContainerContentChanging pour votre ListViewBase, vous pouvez prendre l'approche suivante: TextBlock highlighting for WinRT/ContainerContentChanging

S'il vous plaît noter que ce code est pour Windows RT. La syntaxe WPF sera légèrement différente. Notez également que si vous utilisez la liaison pour remplir la propriété TextBlock.Text, le texte généré par mon approche sera remplacé. J'utilise ContainerContentChanging pour remplir les champs cibles en raison de performances radicalement améliorées et d'améliorations dans l'utilisation de la mémoire, par rapport à une liaison normale. J'utilise la liaison uniquement pour gérer les données source, pas la vue de données.

2

Par une étrange coïncidence, j'ai récemment écrit un article qui résout le même problème.C'est un contrôle personnalisé qui a les mêmes propriétés qu'un TextBlock (donc vous pouvez échanger pour un TextBlock où vous en avez besoin), et il a une propriété supplémentaire que vous pouvez lier à HighLightText, et où la valeur de HighLightText est trouvé dans la propriété principale Text (cas insensible), il est en surbrillance.

Il était un contrôle assez simple pour créer, et vous pouvez trouver l'article ici:

WPF TextBlock With Search String Matching

Et le code complet comme solution ici:

HighlightSearchMatchTextBlock (GitHub)

1

Voici ce que j'ai trouvé en construisant TextBlock existant et en ajoutant une nouvelle propriété de dépendance nommée 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)); 
} 

Et à votre avis, ceci:

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

Je présente ici une autre approche pour mettre en évidence le texte. J'ai eu un cas d'utilisation où je devais décorer un tas de code C# dans WPF, mais je ne voulais pas utiliser le type de syntaxe textBlock.Inlines.Add, je voulais plutôt générer le XAML en surbrillance à la volée, puis l'ajouter dynamiquement à un canevas ou un autre conteneur dans WPF.

Supposons donc que vous voulez coloriser la pièce suivante de code et aussi mettre en évidence une partie de celui-ci:

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

Supposons que le code ci-dessus se trouve dans un fichier appelé Test.txt. Supposons que vous souhaitiez colorier tous les mots-clés C# (public, static, void etc.) et les types simples (int, string) en bleu, et que Console.WriteLine surligne en jaune.

Étape 0. Créer une application WPF et inclure quelques exemples de code similaire ci-dessus dans un fichier appelé Test.txt

Étape 1. Créez une classe code surligneur:

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

    } 
} 

Étape 2. Ajouter un tag Canvas XAML à votre 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> 

Étape 3. dans votre application WPF ajoutez le code suivant: (assurez-vous que test.txt est au bon endroit):

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

    } 
}