コンポーネントメモ・TTreeView
TTreeViewの覚書。
右マウスボタンのOnMouseDownイベントが遅れる
- 右マウスボタンのOnMouseDownイベントが遅れて発生する。
普通OnMouseDownイベントはマウスボタンを押した直後に発生すると思うのですが、TTreeViewでは右マウスボタンを離してから発生します。
- RightClickSelectプロパティがTrueの場合さらに遅れてOnMouseUpイベントの後に発生する。
通常とは逆でOnMouseUpイベントが起きてからOnMouseDwonイベントが起きます。
ヘルプには
RightClickSelect は Selected プロパティの値にのみ影響します。新しいノードが右マウスボタンで選択された場合に,そのノードをツリービューで強調表示するわけではありません。
とあります。
このことから考えると、RightClickSelectがTrueの場合、普通にOnMouseDownの後にOnMouseUpイベントを発生させると右クリックしたノードが強調表示されてしまうので、それへの対策としてイベントの発生の順番を変えているのだろうかなと。
右マウスボタンを押した直後に何らかの処理をしたい場合、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: