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

コンポーネントメモ・TTreeView

TTreeViewの覚書。

右マウスボタンのOnMouseDownイベントが遅れる

右マウスボタンを押した直後に何らかの処理をしたい場合、WM_MOUSEACTIVATEメッセージを補足することで可能となります。
Formのprivate部に以下のように宣言します。

  private
    { Private 宣言 }
    F_bEvent: Boolean;
    procedure WMMouseActivate(var Msg: TWMMOUSEACTIVATE); message WM_MOUSEACTIVATE;
function gfnbKeyState(iKey: Integer): Boolean;
//キーが押されているかを返す。
begin
  //マウスの左右ボタンを入れ替えている場合に対応
  if (GetSystemMetrics(SM_SWAPBUTTON) <> 0) then
  begin
    if (iKey = VK_LBUTTON) then
    begin
      iKey := VK_RBUTTON;
    end else
    if (iKey = VK_RBUTTON) then
    begin
      iKey := VK_RBUTTON;
    end;
  end;

  Result := BOOL(Hi(GetAsyncKeyState(iKey)));
end;

procedure TForm1.WMMouseActivate(var Msg: TWMMOUSEACTIVATE);
//OnMouseDownイベントの代わり
var
  lpt_Pos: TPoint;
begin
  if (F_bEvent) then
    Exit;
  end;
  F_bEvent := True;

  GetCursorPos(lpt_Pos);
  lpt_Pos := TreeView1.ScreenToClient(lpt_Pos);
  if (PtInRect(TreeView1.ClientRect, lpt_Pos)) and (gfnbKeyState(VK_RBUTTON)) then
  begin
    //右マウスボタンを押した直後の処理
  end;
  Msg.Result := MA_ACTIVATE;

  F_bEvent := False;
end;

これで右マウスボタンでもボタンを押した直後に処理を行うことができます。

F_bEventによる処理のスキップはなくてもOKな場合もありますが、ないとフリーズしてしまう場合があるので入れておきます。

gfnbKeyStateは引数に仮想キーコードを与えることでキーやボタンが押されいるかどうかを返す関数です。
マウスイベント以外でもマウスボタンの状態を取得できるので結構便利に使えます。


右クリックした場合も左クリック同様ノードを選択状態にする例。

procedure TForm1.WMMouseActivate(var Msg: TWMMOUSEACTIVATE);
//OnMouseDownイベントの代わり
var
  lpt_Pos: TPoint;
  l_Node: TTreeNode;
begin
  if (F_bEvent) then
    Exit;
  end;
  F_bEvent := True;

  GetCursorPos(lpt_Pos);
  lpt_Pos := TreeView1.ScreenToClient(lpt_Pos);
  if (PtInRect(TreeView1.ClientRect, lpt_Pos)) and (gfnbKeyState(VK_RBUTTON)) then
  begin
    //右ボタンを押した直後の処理
    TreeView1.SetFocus;
    l_Node := TreeView1.GetNodeAt(lpt_Pos.X, lpt_Pos.Y);
    if (l_Node <> nil) then
    begin
      TreeView1.Select(l_Node);
    end;
  end;
  Msg.Result := MA_ACTIVATE;

  F_bEvent := False;
end;

SaveToFileとLoadFromFile

TTreeViewのSaveToFileを実行してみるとノードがタブでインデントされたテキストファイルとして出力されます。

TreeViewの表示

SaveToFileしたもの。タブはで表示

そのファイルをそのままLoadFromFileすれば同じ内容のTreeViewが出来上がるわけなのですが、一つ問題があります。
それはノードのTextの先頭がタブや半角スペースであった場合です。
上の例ではTextの先頭にタブや半角スペースがないのでうまくいきますが、そうでない(Textの先頭がタブか半角スペースの)場合ノードの親子関係が崩れたりエラーになったりしてしまいます。
TTreeViewのLoadFromFileは単純に先頭のタブまたは半角スペースの数でノードのレベルを決めているので、本来ノードのTextの内容であるはずのタブや半角スペースまでインデントであると判断してしまうためです。

この問題はTextの先頭にタブや半角スペース使わないようにするか、あるいはインデントのためのタブや半角スペースとTextの先頭のタブや半角スペースとの区別がつくように工夫することで解決できます。
Textの先頭にタブや半角スペースを使わないというのが、まぁ手っ取り早い解決策なのですが、Textの内容の自由度が下がりますし何よりそのためのエラー回避策を講じなければなりません。
どうせ手間をかけるならSaveToFileとLoadFromFileに少し手を加えてあげる方が良いかなということで。

procedure gpcTreeViewSaveToFile(TreeView: TTreeView; sFileName: String);
var
  i: Integer;
  ls_Indent: String;
  lsl_List:  TStrings;
begin
  lsl_List := TStringList.Create;
  try
    for i := 0 to TreeView.Items.Count -1 do
    begin
      with TreeView.Items[i] do
      begin
        ls_Indent := Format('%*s', [Level, '']); //Level分の半角空白に置き換える
        if (Text[1] = #9)  //タブ
        or (Text[1] = ' ') //半角空白
        then
        begin
          //インデントとTextの間に#1を挟むことでインデントと区別できるようにする
          lsl_List.Add(ls_Indent + #1 + Text);
        end else
        begin
          lsl_List.Add(ls_Indent + Text);
        end;
      end;
    end;
    lsl_List.SaveToFile(sFileName);
  finally
    lsl_List.Free;
  end;
end;

SaveToFileではノード(TreeView.Items[i])のTextプロパティの先頭がタブか半角スペースであればインデントとの間に#1を加えてインデントと区別できるようにします。
この例ではFormat関数を使っているのでインデントがタブではなく半角スペースになります。

procedure gpcTreeViewLoadFromFile(TreeView: TTreeView; sFileName: String);
var
  i: Integer;
begin
  TreeView.LoadFromFile(sFileName);
  for i := 0 to TreeView.Items.Count -1 do
  begin
    if (TreeView.Items[i].Text[1] = #1) then
    begin
      TreeView.Items[i].Text := Copy(TreeView.Items[i].Text, 2, MAXINT);
    end;
  end;
end;

LoadFromFileではSaveToFileで加えた#1を取り除く処理が入ります。


2009-02-10:SaveToFile LoadFromFile追記。
2009-01-26: