テキストエディタの多くは左側に行番号が表示されます。さてこのようなものを作るにはどうすればいいのでしょうか?
まずユーザーコントロールを作成します。
そして
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public partial class UserControlRich : UserControl { public UserControl1() { InitializeComponent(); richTextBox1.TextChanged += RichTextBox1_TextChanged; richTextBox1.VScroll += RichTextBox1_VScroll; } private void RichTextBox1_VScroll(object sender, EventArgs e) { DrawLineNumber(); } private void RichTextBox1_TextChanged(object sender, EventArgs e) { DrawLineNumber(); } void DrawLineNumber() { int lineNum = 0; int height = richTextBox1.Size.Height; Graphics g = this.CreateGraphics(); g.Clear(Color.White); int charIndex = richTextBox1.GetCharIndexFromPosition(new Point(0, 0)); lineNum = richTextBox1.GetLineFromCharIndex(charIndex); while(true) { charIndex = richTextBox1.GetFirstCharIndexFromLine(lineNum); if(charIndex == -1) break; Point pt = richTextBox1.GetPositionFromCharIndex(charIndex); Font f = new Font("MS 明朝", 10, GraphicsUnit.Pixel); g.DrawString((lineNum + 1).ToString(), f, Brushes.Blue, new PointF(0, pt.Y)); lineNum++; if(height < pt.Y) break; } g.Dispose(); } } |
これでリッチテキストに入力されている文字列が変更されたときとスクロールされたときに表示されている部分の行が何行目であるか計算されて表示されます。
DrawLineNumber()について
1 |
int charIndex = richTextBox1.GetCharIndexFromPosition(new Point(0, 0)); |
まず最初に座標(0,0)に一番近い位置にある文字は先頭から何番目に存在するか調べます。
つぎにその文字の行番号を調べます。これをやるのがGetLineFromCharIndexメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int lineNum = 0; lineNum = richTextBox1.GetLineFromCharIndex(charIndex); while(true) { charIndex = richTextBox1.GetFirstCharIndexFromLine(lineNum); if(charIndex == -1) break; Point pt = richTextBox1.GetPositionFromCharIndex(charIndex); lineNum++; if(height < pt.Y) break; } |
ループのなかで最初に求めた行番号をひとつずつ増やしながら、その文字が先頭から何番目にあるかを調べています。GetFirstCharIndexFromLineメソッドが-1を返したときはそんな行は存在しないということなのでループから脱出、GetFirstCharIndexFromLineメソッドが返した値を引数にしてGetPositionFromCharIndexメソッドと呼び出し、Y座標がRichTextBoxの高さを超えていた場合は表示されない部分なので、この場合もループから脱出しています。
あとは得られた行番号を表示するだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Graphics g = this.CreateGraphics(); g.Clear(Color.White); while(true) { // lineNumは調べたい行番号 // 行番号と座標が取得できたら・・・ // その座標に行番号を描画 Font f = new Font("MS 明朝", 10, GraphicsUnit.Pixel); g.DrawString((lineNum + 1).ToString(), f, Brushes.Blue, new PointF(0, pt.Y)); lineNum++; // これ以上やる必要がないならループから抜ける } g.Dispose(); |
これを使えば現在選択されている部分が何行目の何文字目なのかも表示できます。
UserControlRichクラスを継承してUserControlRichExを作ります。
UserControlRichクラスのプロパティを変更して内部にあるrichTextBoxにアクセスできるようにしておきます。
カーソルの位置が変更されたらOnSelectionChangedが呼び出されるので、そこでカーソルの行と行頭からの文字数を計算します。そしてイベントを起こして計算結果を伝えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class UserControlRichEx : UserControlRich { public UserControlRichEx() { this.richTextBox1.SelectionChanged += (sender, e) => OnSelectionChanged(e); } public delegate void SelectionChangedHandler(object sender, SelectionChangedArg e); public event SelectionChangedHandler SelectionChanged; private void OnSelectionChanged(EventArgs e) { int start = richTextBox1.SelectionStart; int lineNum = richTextBox1.GetLineFromCharIndex(start); int lineHeadIndex = richTextBox1.GetFirstCharIndexFromLine(lineNum); int index = start - lineHeadIndex; SelectionChangedArg arg = new SelectionChangedArg(); arg.lineNum = lineNum + 1; // 先頭が0ではなく1になるようにする arg.index = index + 1; // 同上 SelectionChanged?.Invoke(this, arg); } } |
1 2 3 4 5 |
public class SelectionChangedArg { public int lineNum = 0; public int index = 0; } |
あとは以下のようにすれば、テキストボックスにカーソル位置を表示させることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); userControlRichEx1.SelectionChanged += UserControlRichEx1_SelectionChanged; } private void UserControlRichEx1_SelectionChanged(object sender, SelectionChangedArg e) { textBox1.Text = String.Format("{0}行{1}文字目", e.lineNum, e.index); } } |
ltは宣言されてませんけど、、
ご指摘ありがとうございます。
原因はこれです。
https://lets-csharp.com/clash-crayon-syntax-highlighter/
プラグインを更新したら文字化けしてしまったようです。他にも多数あるので直しているのですが・・・