エディターに行番号を追加(LEditor)


■ 概略説明
大分以前から、やりたかった事ですがなかなか実行できませんでした。
今回のサンプルはCEditViewに対してですが、この実現はCEditのクラスメンバを見ていてひらめきました。
それは、SetMargins関数です。今まで見過ごしてきましたが、これはその名の通り、左右のマージンを設定するものです。試しに使ってみると、当然ですが左側に空白ができました。このとき、「ここに行番号を表示できるのでは」そう考えて、後は幾つかの問題はありましたが、一応クリアできました。以下のような感じです。

これは、上記のスペースの上に行番号表示用のウィンドウを貼り付るやり方です。わかりやすいように行番号部のウィンドウの色が変えてあります。
■ プログラムの説明(作成手順の概略)
1. CEditViewの子供として、行番号表示用のウィンドウを作成します。(CLineWnd)

2. 編集する関数は、OnPaintだけです。ポイントは
m_nStart = pParent->GetEditCtrl().GetFirstVisibleLine();
m_nLineCount = pParent->GetEditCtrl().GetLineCount();
この2行で、親(CEditView)から現在表示されている先頭の行番号と全体の行数を持ってきて、後は表示できるだけ行番号を表示しているだけです。

3. 次にCEditViewの編集ですが、まずメンバに上記のCLineWndのポインタメンバm_pWndを作成します。

4. OnCreate内で、子ウィンドウを作成します。
m_pWnd = new CLineWnd;
m_pWnd->Create( NULL, "", WS_CHILD | WS_VISIBLE, rc, this, 0);
このときに、CClientDCを使いdcを取り出し、現在のフォントと改行ピッチを取り出し、それをCLineWndに伝えておきます。

5. OnSizeを追加し、サイズの変更に伴いCLineWndのサイズを変更します。
子供が絶えずくっついた状態にします。

6. これだけで、行番号が表示されます。子ウィンドウなので自動的にCLineWndにペイント命令を発行します。
後は、エディタの変更時にCLineWndを更新するだけですが、ここからが厄介でした。

7. まずスクロールですが、スクロールバーに対するもの(WM_VSCROLL)と、エディターのスクロールに対するもの(EN_VSCROLL)の両方を追加します。
void CLEditorView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
void CLEditorView::OnVscroll()
処理は、どちらも同じで、CLineWndのRedrawWindow()呼ぶだけです。
ただし、OnVScrollは、
CEditView::OnVScroll(nSBCode, nPos, pScrollBar);
の後に処理を入れます。

8. そしてエディターの修正にあわせるため、EN_CHANGEの関数OnChangeを追加し、前項と同じ処理を追加します。
ただしこのままだと、一字修正するたびに行番号を書き直してしまうので、行番号が変化したときだけ行番号を書き直すようにします。

9. 最後に今回のキーポイントとして、PreCreateWindowでのCEditViewのウィンドウスタイルを変更します。
cs.style |= WS_CLIPCHILDREN;
cs.style |= (ES_MULTILINE|WS_VSCROLL); // マルチライン.
cs.style |= ES_NOHIDESEL;
cs.style |= WS_HSCROLL;
WS_CLIPCHILDRENですが、「親ウィンドウの内部で描画するときに、子ウィンドウが占める領域を除外します」というなんともありがたいフラグで、これにより行番号付エディターができました。因みにこの行を削除するとどうなるかがわかります。
■ 更新情報
こんやすさんから、コメントを追加した修正ファイルを頂きました。ありがとうございます。
また、以前は問題なかったのですが、Xp上でファイルを開いた直後行番号が更新されなくなったので、CLEditorViewにInitialUpdate()を追加しそこで行番号ウィンドウを更新する処理を追加しました。
■ファイルのダウンロード
(VC++6.0SDKプロジェクトファイル24KB) 2004.10.4更新