RichTextBox for WPF
構文の色指定
RichTextBox for WPF の使い方 > 構文の色指定

C1TextPointer の理解」では、Selection プロパティを使用して現在の選択範囲に対応する C1TextRange オブジェクトを取得する方法について説明し、そのオブジェクトを使用してカスタム書式を検証し、ドキュメントの各部に適用する方法についても説明します。

ただし、範囲を選択することなく書式を検証したり範囲に適用したい場合もあります。Selection プロパティを使用してこれを実行するには、現在の選択範囲を保存し、すべての書式を適用してから、元の選択範囲を復元する必要があります。また、選択範囲を変更すると、新しい選択範囲を表示するために、ドキュメントがスクロールする可能性があります。

このような状況に対処するために、C1RichTextBoxGetTextRange メソッドを公開しています。GetTextRange メソッドは、現在の選択範囲に影響を与えずに C1TextRange を返します。

たとえば、GetTextRange メソッドを使用して、C1RichTextBox に HTML 構文の色指定を追加することもできます。最初の手順は、ドキュメントに対する変更を検出することです。この変更により、実際に構文の色指定を行うメソッドがトリガされます。

コードのコピー
' タイマーによって構文の色指定を更新します
Private _updating As Boolean
Private _syntax As Storyboard
' ドキュメントが変更されるたびにタイマーをオンにします
Private Sub tb_TextChanged(sender As Object, e As C1TextChangedEventArgs)
   If Not _updating Then
       ' ストーリーボードが null の場合は、それを作成します
       If _syntax Is Nothing Then
          _syntax = New Storyboard()
           AddHandler _syntax.Completed, AddressOf _syntax_Completed
           _syntax.Duration = New Duration(TimeSpan.FromMilliseconds(1000))
       End If
       ' ストーリーボードを再開します
       _syntax.[Stop]()
     _syntax.Seek(TimeSpan.Zero)
       _syntax.Begin()
   End If
End Sub
' タイマーの時間が経過し、構文の色指定を更新します
Private Sub _syntax_Completed(sender As Object, e As EventArgs)
   _updating = True
   UpdateSyntaxColoring(_rtb)
   _updating = False
End Sub
コードのコピー
// タイマーによって構文の色指定を更新します
bool _updating;
Storyboard _syntax;
// ドキュメントが変更されるたびにタイマーをオンにします
void tb_TextChanged(object sender, C1TextChangedEventArgs e)
{
  if (!_updating)
  {
   // ストーリーボードが null の場合は、それを作成します
   if (_syntax == null)
    {
      _syntax = new Storyboard();
      _syntax.Completed += _syntax_Completed;
      _syntax.Duration = new Duration(TimeSpan.FromMilliseconds(1000));
    }
   // ストーリーボードを再開します
    _syntax.Stop();
    _syntax.Seek(TimeSpan.Zero);
    _syntax.Begin();
  }
}
// タイマーの時間が経過し、構文の色指定を更新します
void _syntax_Completed(object sender, EventArgs e)
{
  _updating = true;
  UpdateSyntaxColoring(_rtb);
  _updating = false;
}

このコードは、ユーザーがドキュメントを変更するたびにオンになるタイマーを作成します。タイマーがオンになっているときにユーザーがドキュメントを変更すると、タイマーはリセットされます。これで、ユーザーがすばやく入力している間にコードが頻繁に構文の色指定を更新することがなくなります。

タイマーの時間が経過すると、構文の色指定の更新中に行われた変更によってタイマーがオンにならないようにするフラグを設定し、UpdateSyntaxColoring メソッドを呼び出します。

コードのコピー
' 構文を色指定します
Private Sub UpdateSyntaxColoring(rtb As C1RichTextBox)
  ' HTML の解析に使用される正規表現を初期化します
   Dim pattern As String = "</?(?<tagName>[a-zA-Z0-9_:\-]+)" & "(\s+(?<attName>[a-zA-Z0-9_:\-]+)(?<attValue>(=""[^""]+"")?))*\s*/?>"
  ' ドキュメントの色指定に使用されるブラシを初期化します
   Dim brDarkBlue As Brush = New SolidColorBrush(Color.FromArgb(255, 0, 0, 180))
   Dim brDarkRed As Brush = New SolidColorBrush(Color.FromArgb(255, 180, 0, 0))
   Dim brLightRed As Brush = New SolidColorBrush(Colors.Red)
  ' 以前の色指定を削除します
   Dim input = rtb.Text
   Dim range = rtb.GetTextRange(0, input.Length)
   range.Foreground = rtb.Foreground
  ' 一致を強調表示します
   For Each m As Match In Regex.Matches(input, pattern)
      ' タグ全体を選択して濃い青色にします
       range = rtb.GetTextRange(m.Index, m.Length)
       range.Foreground = brDarkBlue
      ' タグ名を選択して濃い赤色にします
       Dim tagName = m.Groups("tagName")
       range = rtb.GetTextRange(tagName.Index, tagName.Length)
       range.Foreground = brDarkRed
' 属性名を選択して明るい赤色にします
       Dim attGroup = m.Groups("attName")
       If attGroup IsNot Nothing Then
           Dim atts = attGroup.Captures
           For i As Integer = 0 To atts.Count - 1
               Dim att = atts(i)
               range = rtb.GetTextRange(att.Index, att.Length)
               range.Foreground = brLightRed
           Next
       End If
   Next
End Sub
コードのコピー
// 構文を色指定します
void UpdateSyntaxColoring(C1RichTextBox rtb)
{
// HTML の解析に使用される正規表現を初期化します
string pattern =
   @"</?(?<tagName>[a-zA-Z0-9_:\-]+)" +
   @"(\s+(?<attName>[a-zA-Z0-9_:\-]+)(?<attValue>(=""[^""]+"")?))*\s*/?>";
// ドキュメントの色指定に使用されるブラシを初期化します
Brush brDarkBlue = new SolidColorBrush(Color.FromArgb(255, 0, 0, 180));
Brush brDarkRed = new SolidColorBrush(Color.FromArgb(255, 180, 0, 0));
Brush brLightRed = new SolidColorBrush(Colors.Red);
// 以前の色指定を削除します
var input = rtb.Text;
var range = rtb.GetTextRange(0, input.Length);
range.Foreground = rtb.Foreground;
// 一致を強調表示します
foreach (Match m in Regex.Matches(input, pattern))
{
   // タグ全体を選択して濃い青色にします
   range = rtb.GetTextRange(m.Index, m.Length);
   range.Foreground = brDarkBlue;
   // タグ名を選択して濃い赤色にします
   var tagName = m.Groups["tagName"];
   range = rtb.GetTextRange(tagName.Index, tagName.Length);
   range.Foreground = brDarkRed;
   // 属性名を選択して明るい赤色にします
   var attGroup = m.Groups["attName"];
   if (attGroup != null)
   {
     var atts = attGroup.Captures;
     for (int i = 0; i < atts.Count; i++)
     {
       var att = atts[i];
       range = rtb.GetTextRange(att.Index, att.Length);
       range.Foreground = brLightRed;
     }
   }
  }
}

最初にコードは、HTML を解析するための正規表現パターンを定義します。これは、HTML を解析するための最も効率的な方法ではなく、表現も格別に読みやすく保守しやすいというわけではありません。サンプルコード以外で HTML の解析に正規表現を使用することはお勧めしません。サンプルコードでは、正規表現によってコードがコンパクトになり、理解しやすくなります。

次の手順では、残っている色指定を削除します。それには、ドキュメント全体をカバーする範囲を作成し、その Foreground プロパティを C1RichTextBox コントロールの Foreground に一致するように設定します。

次に、正規表現を使用してドキュメントを解析します。このコードは、各一致候補をスキャンし、C1TextRange オブジェクトを作成し、Foreground プロパティを目的の値に設定します。HTML タグには濃い青色、タグ名には濃い赤色、および属性名には明るい赤色を使用します。

必要なコードはこれだけです。次の画像は、今作成した構文の色指定を C1RichTextBox に適用し、その中に HTML ドキュメントを表示したところです。

任意の HTML テキストをコントロールに入力するか、貼り付けて、アプリケーションをテストしてください。入力を止めるとすぐに、入力したテキストに自動的に色が付けられます。

実際のアプリケーションでは、テキスト変更の種類を検出し、ドキュメントの小さな部分の色指定を更新することで、構文の色指定プロセスを最適化できます。さらにスタイルシート、コメントなどの要素を検出し、通常は正規表現の代わりに専用のパーサーを使用します。

ただし、基本的なメカニズムは同じです。ドキュメント内の範囲を検出し、C1TextRange オブジェクトを取得し、書式設定を適用します。