DarioSantarelli.Blog("UgiDotNet");

<sharing mode=”On” users=”*” />
posts - 176, comments - 144, trackbacks - 3

My Links

News


This is my personal blog. These postings are provided "AS IS" with no warranties, and confer no rights.

logo linkedIn logo Twitter logo FaceBook logo RSS logo Email

Logo DotNetMarche
Logo XPUG Marche



Tag Cloud

Archives

Post Categories

My English Blog

martedì 24 agosto 2010

[WPF] Evidenziare testo in una RichTextBox

Una funzionalità fondamentale per text-editor/reader è l’evidenziazione programmatica di testo, magari utilizzando stili di formattazione diversi. In questo post vorrei mostrare una possibile implementazione di tale funzionalità utilizzando il controllo RichTextBox di WPF. Immaginando un approccio top-down, potremmo partire da un metodo HilightText(…), il cui compito sia proprio quello di evidenziare del testo all’interno di una RichTextBox.

public void HilightText(string pattern)
{             
  List<TextRange> ranges = GetMatchingTextRanges(pattern);
 
foreach (TextRange textRange in ranges) HilightTextRange(textRange, Brushes.Black, Brushes.Yellow);            
}

L’idea è quella di applicare un background giallo ed un foreground nero a tutti gli intervalli di testo (TextRange) che soddisfano un dato pattern-matching.

private void HilightTextRange(TextRange textRange, Brush foregroundBrush, Brush backgroundBrush)
{
 
if (foregroundBrush != null) textRange.ApplyPropertyValue(TextElement.ForegroundProperty, foregroundBrush);
 
if (backgroundBrush != null) textRange.ApplyPropertyValue(TextElement.BackgroundProperty, backgroundBrush);
}

Ora ci troviamo di fronte all’esigenza di determinare gli intervalli di testo coinvolti nel matching. Definiamo dunque un metodo GetMatchingTextRanges(…), il cui compito è di navigare l’intero documento della RichTextBox (un FlowDocument) analizzando esclusivamente gli elementi (inlines) di tipo Run, ovvero quelli che contengono testo.

private List<TextRange> GetMatchingTextRanges(string pattern)
{

   List<TextRange> ranges = new List<TextRange>();

 

   TextPointer textNavigator = richTextBox.Document.ContentStart;

   while (textNavigator.CompareTo(richTextBox.Document.ContentEnd) < 0)

   {

      TextPointerContext context = textNavigator.GetPointerContext(LogicalDirection.Backward);

      if (context == TextPointerContext.ElementStart && textNavigator.Parent is Run)

      {

        List<TextRange> runRanges = GetMatchingTextRanges((Run)textNavigator.Parent, pattern);

        if (runRanges.Count > 0) ranges.AddRange(runRanges);

      }

      textNavigator = textNavigator.GetNextContextPosition(LogicalDirection.Forward);

   }

 

   return ranges;

}

A questo punto, per ogni Run trovato, è possibile controllare se esistono porzioni di testo in esso contenuti che soddisfano il pattern di ricerca. In caso positivo, possiamo aggiungerle alla lista dei TextRange da evidenziare.

private List<TextRange> GetMatchingTextRanges(Run run, string pattern)

{            

   List<TextRange> ranges = new List<TextRange>();

   MatchCollection matches = Regex.Matches(run.Text, pattern);            

 

   foreach (Match match in matches)

   {

     TextPointer startPosition = GetContentTextPointer(run.ContentStart, match.Index - 1);

     TextPointer endPosition = GetContentTextPointer(run.ContentStart, match.Index + match.Length - 1);

     ranges.Add(new TextRange(startPosition, endPosition));

   }
   return ranges;

}

Come si stabiliscono gli estremi dei TextRange da evidenziare all’interno di ciascun Run? Anzitutto, tramite la classe Regex riusciamo a capire per ciascun match quali sono gli indici dei caratteri iniziali all’interno del testo del Run. Poi, ricordando che per calcolare l’offset corretto all’interno di ciascun Run non dobbiamo considerare i caratteri (un Run può contenere altri tipi di simboli) bensì i TextPointer che sono adiacenti al testo di interesse, definiamo il seguente metodo:

 

private TextPointer GetContentTextPointer(TextPointer contentStart, int offset)
{
  TextPointer result = contentStart;

  int i = 0;

  while (i < offset && result != null)

  {

    if (result.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text ||

        result.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.None) i++;

    if
(result.GetPositionAtOffset(1, LogicalDirection.Forward) == null) return result;

 

    result = result.GetPositionAtOffset(1, LogicalDirection.Forward);
  }
  return result;
}

Finalmente il gioco è fatto. Di seguito riportiamo un esempio di output ottenuto invocando HilightText(“labor”);



Technorati Tags: ,

posted @ martedì 24 agosto 2010 22.01 | Feedback (1) | Filed Under [ WPF ]

Powered by: