ホーム >プログラム >Delphi 6 ローテクTips

RichEditにオートインデントを実装

プログラムのソースコードを書くのにほぼ必須とも思えるオートインデントをリッチエディットに実装してみようというページ。


OnKeyDownイベント

もっとちゃんとしたやり方があるのかも知れませんが、リッチエディットのOnKeyDownイベントでリターンキーを検知してインデントを調べ、OnChangeイベントで行頭にインデント分の文字列を挿入するという方法を取ります。

イベントをまたいで実現させているのでフォームのprivate部にインデント用の文字列変数を宣言します。

type
  TForm1 = class(TForm)
    RichEdit1: TRichEdit;
    procedure RichEdit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure RichEdit1Change(Sender: TObject);
  private
    { Private 宣言 }
    FsIndent : String;
  public
    { Public 宣言 }
  end;

まずはOnKeyDownイベントでリターンキーを検知します。

procedure TForm1.RichEdit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  function _GetIndent(sSrc: String; iCount: Integer): String;
  const
    lcs_INDENTCHAR = ' '#9; //半角スペースとタブ
  var
    i : Integer;
    li_Count : Integer;
  begin
    Result := '';
    if (iCount < 0) then
    begin
      li_Count := Length(sSrc);
    end else
    begin
      li_Count := iCount;
    end;
    for i := 1 to li_Count do
    begin
      if (AnsiPos(sSrc[i], lcs_INDENTCHAR) = 0) then
      begin
        Result := Copy(sSrc, 1, i -1);
        Exit;
      end;
    end;
    Result := Copy(sSrc, 1, li_Count);
  end;
var
  li_CurrentLine   : Integer;
  li_CurrentColumn : Integer;
begin
  if (Key = VK_RETURN) then
  begin
    //カーソルのある行を取得。
    li_CurrentLine := SendMessage(RichEdit1.Handle, EM_LINEFROMCHAR, WPARAM(-1), 0);
    //カーソルのあるカラムを取得。
    li_CurrentColumn := RichEdit1.SelStart - SendMessage(RichEdit1.Handle, EM_LINEINDEX, -1, 0);

    //直前の行のインデントを知りたいのでli_CurrentLine-1としそうだけれどもそれは間違い。
    //この時点ではまだ改行はされていないので調べたいインデントはカーソルのある行になる。
    FsIndent := _GetIndent(RichEdit1.Lines[li_CurrentLine], li_CurrentColumn);
  end;
end;

OnKeyDownイベントでリターンキーを検知したらカーソルのある行をEM_LINEFROMCHARメッセージを使って取得します。
EM_LINEFROMCHARはWParamに-1を指定することでカーソルのある行が返ります。
カーソルのあるカラムはリッチエディットのSelStartプロパティとEM_LINEINDEXメッセージを使って計算して求めます。
SelStartプロパティはテキストの先頭からの位置になります。
EM_LINEINDEXメッセージはWParamに-1を指定することでカーソルのある行の先頭文字の、テキストの先頭からの位置が返ります。
SelStartからEM_LINEINDEXメッセージの戻り値を引くことでカーソルのあるカラムが求められます。

注意する点はOnKeyDownイベントが起きた時点ではまだ改行されていないのでインデントを調べるのはカーソルのある行であるということです。
つまり改行前にその行のインデント量を取得しておくわけです。

  const
    lcs_INDENTCHAR = ' '#9; //半角スペースとタブ

インデントは半角スペースとタブにしています。
この部分を変えることで(例えば全角スペースも加えるなど)インデントの対象になる文字を増減できます。

OnChangeイベント

改行後のイベントはOnChangeイベントで処理します。
OnKeyUpイベントではキーを押しっ放しの連続入力に対応できません。

procedure TForm1.RichEdit1Change(Sender: TObject);
var
  li_CurrentLine : Integer;
begin
  if (FsIndent <> '') then
  begin
    //カーソルのある行を取得。
    li_CurrentLine := SendMessage(RichEdit1.Handle, EM_LINEFROMCHAR, WPARAM(-1), 0);
    RichEdit1.Lines[li_CurrentLine] := FsIndent + RichEdit1.Lines[li_CurrentLine];
    //カーソルが後ろに飛んでしまうので戻す。
    RichEdit1.SelStart := SendMessage(RichEdit1.Handle, EM_LINEINDEX, -1, 0) + Length(FsIndent);
    FsIndent := '';
  end;
end;

FsIndentが空文字でなければ処理に入ります。
OnKeyDownイベントの時と同じようにカーソルのある行を取得していますが、この時点では改行後のカーソル位置になっています。
あとはリッチエディットのLinesプロパティを使って行頭にインデント分の文字列を挿入するだけです。
これだけでOKかと思いきやインデント文字列を挿入したせいなのかカーソルが後ろに飛んでしまうので戻します。
戻すのはEM_LINEINDEXメッセージを使って行頭文字の位置を取得し、その値にインデント文字列の長さを足した値をSelStartプロパティの値に代入するだけです。
最後にFsIndentを初期化して終わりです。


2011-11-19: