プログラムでグリッドとして使用しているサードパーティのツリーパッケージ(LMD InnovativeによるElXTree)があります。セルを選択するたびに、その行にフォーカスが移り、必要に応じて強調表示されます。
グリッド内のセルをクリックして付属のインプレースエディターを呼び出すと、その行にフォーカスが移ります。セルは編集モードで選択されているため、必要に応じて、セルのみが強調表示されます(行全体ではありません)。
必要ないのはこれです。あるセルをインプレース編集していて、別のセルをクリックしてインプレースエディターを呼び出すと、最初に古いセルの行にフォーカスが移され、強調表示されます。次に、すぐにフォーカスが削除されて強調表示が解除され、新しいセルのある行にフォーカスが移されて強調表示されます。次に、その新しい行は、インプレース編集されているセルを除いて、すぐにハイライト解除されます。これは迷惑な二重点滅を引き起こします、そして私はそれを取り除きたいです。
私はパッケージのソースコードを持っていて、それを通してデバッグしてきました。ダブルフォーカシングを引き起こしているものを見つけることができれば、それを防ぐための簡単な変更を行う方法を理解できると確信しています。
ブレークポイントを設定すると、FormsユニットのTApplication.Runのメッセージ処理ループにいることがわかります。このループが処理している多くのメッセージのうちの2つは、フォーカスを設定するためのメッセージです。メッセージがディスパッチされるClassesユニットのStdWndProcまで、プログラムを1行ずつトレースできます。メッセージに関するすべての情報(ハンドル、パラメーターなど)があります。
私が持っていないこととわからないことは、メッセージがどこから開始されたかです。コールスタックには、私を手がかりにするElXTreeユニットがありません。これらのルーチンの1つは、現在のコールスタックとは無関係にメッセージを送信している必要があります。
そのメッセージがどこから送信されたのか(つまり、どのルーチンがメッセージを送信したのか)を知ることができれば、私はオフになって実行されます。
メッセージの送信元を見つける方法はありますか?あるいは、私が抱えているこのダブルフォーカシングの問題を回避できる他の方法はありますか?
参考までに、Delphi2009を使用しています。
さらに詳しい情報:
ElXTreeには、動作する独自のWindowsメッセージが数十あります。私の場合、関連する2つは次のとおりです。
procedure WMSetFocus(var Msg: TWMSetFocus); message WM_SETFOCUS;
procedure WMKillFocus(var Msg: TWMKillFocus); message WM_KILLFOCUS;
procedure TElXTreeView.WMSetFocus(var Msg: TWMSetFocus); { private }
begin
inherited;
FHasFocus := True;
if (FOwner.HideSelection or (FOwner.HideSelectColor <> FOwner.FocusedSelectColor) or (FOwner.HideSelectTextColor <> FOwner.FocusedSelectTextColor)) and
(FOwner.Items.Count > 0) then
Invalidate;
with FOwner do
if Flat or FUseCustomScrollBars or IsThemed then
UpdateFrame;
end; { WMSetFocus }
procedure TElXTreeView.WMKillFocus(var Msg: TWMKillFocus); { private }
begin
FMouseSel := False;
FPressed := False;
FHasFocus := False;
inherited;
FHintItemEx := nil;
DoHideLineHint;
if HandleAllocated then
begin
with FOwner do
if Flat or FUseCustomScrollBars or IsThemed then
begin
UpdateFrame;
DrawFlatBorder(False, False);
if FUseCustomScrollBars then
begin
HScrollBar.HideHint;
VScrollBar.HideHint;
end;
end;
if (FOwner.HideSelection or (FOwner.HideSelectColor <> FOwner.FocusedSelectColor) or (FOwner.HideSelectTextColor <> FOwner.FocusedSelectTextColor)) and
(FOwner.Items.Count > 0) then
Invalidate;
end;
end; { WMKillFocus }
たとえば、WMSetFocusルーチンにブレークポイントを設定すると、次の呼び出しスタックが取得されます。
呼び出しスタック内の他の唯一のElXTreeルーチンは、4行目のルーチンです。
procedure TElXTreeView.WndProc(var Message: TMessage);
var P1: TPoint;
Item: TElXTreeItem;
HCol: Integer;
IP: TSTXItemPart;
begin
if (FHintItem <> nil) and (FOwner.FHideHintOnMove) then
begin
if ((Message.Msg >= WM_MOUSEMOVE) and (Message.Msg <= WM_MOUSELAST)) or (Message.Msg = WM_NCMOUSEMOVE) then
begin
GetCursorPos(P1);
P1 := ScreenToClient(P1);
Item := GetItemAt(P1.X, P1.Y, IP, HCol);
if Item <> FHintItem then
DoHideLineHint;
inherited;
Exit;
end
else
if
((Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST)) or
((Message.Msg = CM_ACTIVATE) or (Message.Msg = CM_DEACTIVATE)) or
(Message.Msg = CM_APPKEYDOWN) or (Message.Msg = CM_APPSYSCOMMAND) or
(Message.Msg = WM_COMMAND) or
((Message.Msg > WM_MOUSEMOVE) and (Message.Msg <= WM_MOUSELAST))
or (Message.Msg = WM_NCMOUSEMOVE) then
DoHideLineHint;
end;
if (FHintItem <> nil) and ((Message.Msg = CM_ACTIVATE) or (Message.Msg = CM_DEACTIVATE))
or (Message.Msg = WM_NCMOUSEMOVE) then
DoHideLineHint;
inherited;
end;
このルーチンにブレークポイントを設定すると、「継承された」行に渡されてからシステム関数が呼び出され、最終的にメッセージが処理されるStdWndProcに到達するように見えます(元の質問で説明したように)。
これを正確にトレースする際の問題は、コードをデバッグしながら、マウスをクリックしてプログラムのビジュアルコントロール上にマウスポインタを置いたままにする必要があることです。デバッグ中にマウスを動かしたり使用したりする際に間違いがあると、処理に影響を与える追加のマウスイベントが発生する可能性があります。これにより、デバッグが非常に困難になります。
しかし、StdWndProcを注意深く追跡して、回線に焦点を合わせてディスパッチされるイベントを確認できます。私ができないように見えるのは、メッセージの問題を見つけることです。
さて、なぜ私はメッセージを発行するのかわからないのですか?さて、Davidが言うように、それはPostMessageまたはSendMessageコマンドからのものだと思います。ElXTreeでこれらすべての呼び出しが行われる場所を探すと、次の10個しか見つかりません。
Result := SendMessage(hWnd, SBM_SetScrollInfo, Integer(Redraw), Integer(@ScrollInfo));
SendMessage(hWnd, SBM_GetScrollInfo, 0, Integer(@ScrollInfo));
SendMessage(FHScrollBar.Handle, Message.Msg, Message.wParam, Message.lParam);
SendMessage(FVScrollBar.Handle, Message.Msg, Message.wParam, Message.lParam);
case Key of
VK_LEFT: begin
PostMessage(FOwner.Handle, WM_HSCROLL, SB_LINELEFT, 0);
Exit;
end;
VK_RIGHT: begin
PostMessage(FOwner.Handle, WM_HSCROLL, SB_LINERIGHT, 0);
Exit;
end;
end;
FScrollbarsInitialized := True;
if UseCustomScrollbars then
PostMessage(Handle, WM_UPDATESBFRAME, 0, 0);
end;
procedure TCustomElXTree.WMSysColorChange(var Msg: TWMSysColorChange);
begin
inherited;
PostMessage(FVScrollBar.Handle, Msg.Msg, TMessage(Msg).WParam, TMessage(Msg).LParam);
PostMessage(FHScrollBar.Handle, Msg.Msg, TMessage(Msg).WParam, TMessage(Msg).LParam);
PostMessage(FHeader.Handle, Msg.Msg, TMessage(Msg).WParam, TMessage(Msg).LParam);
end; { WMSysColorChange }
最初の7つはスクロールバーを扱います。次の3つはColorChangeです。
メッセージの発行について、他のすべてのLMDコンポーネントルーチンも調べましたが、有望なものはありません。
だから私はまだ立ち往生していて、行に焦点を合わせるように求めているそのメッセージの送信者を見つける方法についてのヒントまたは手がかりが必要です。
回避策:
さて、Windowsがマウスイベントを開始していることに気づいたら、ほとんどの点滅を止める何かをすることができました。しかし、それは本当のハックです。誰かがもっと良いことを知っているなら、私はそれについて聞きたいです。
TElXTreeView.WndProcで、継承されたステートメントを次のように置き換えました。
if (Message.Msg = WM_SETFOCUS) or (Message.Msg = WM_KILLFOCUS) then begin
FOwner.Items.BeginUpdate;
inherited;
FOwner.Items.EndUpdate;
end
else
inherited;
これが行うことは、呼び出されたルーチン内でマルチフォーカスが発生するのを防ぐことです。
編集可能なエントリをクリックしている場合を除いて、それは仕事をします。編集モードに入る前に、最初にエントリを強調表示します。これは、ハイライトがMouseDownで発生するのに対し、編集モードに入るのはMouseUpで発生するためです。私はこれを回避する方法を見つけることができるかもしれませんが、最初の試みは失敗しました。しかし、それは二重点滅ほど悪くはなく、必要に応じて私はそれと一緒に暮らすことができました。
私の脳を押し上げるのを手伝ってくれた皆さんに感謝します。受け入れられた答えは、私に重要な手がかりを与えてくれたデビッドに行きます。
...多分私はあまりにも早く話しました。他のいくつかのコントロール、たとえばグリッドが表示されているページは、コントロール間でページングするときに更新されないことがわかりました。EndUpdateの後にRefreshコマンドを追加してみました。それをしたら、またダブル点滅しました。これは本当に厄介な問題です。
ページングの回避策を得ることができるかもしれませんが、そのコントロールの開発者がより良い修正で私に応答することを願っています。
このようなことは、プログラミングの喜びの1つではありません。:-(