我有一个第三方树程序包(LMD Innovative的ElXTree),正在我的程序中用作网格。每当我选择一个单元格时,该行就会获得焦点并突出显示,就像我想要的那样。
当我通过单击网格中的一个单元格调用提供的Inplace编辑器时,该行将获得焦点。因为在编辑模式下选择了该单元格,所以只有单元格才突出显示(而不是整行),正如我所希望的那样。
我不想要的是:当我就地编辑一个单元格时,通过单击它来调用另一个单元格的就地编辑器,首先将旧单元格的行作为焦点并突出显示。然后立即将其焦点移开并取消突出显示,并将新单元格所在的行作为焦点并突出显示。然后,该新行立即变为未突出显示的位置,除了在原地编辑的单元格之外。这会引起恼人的两次闪烁,我想摆脱它。
我有程序包的源代码,并且一直在调试它。我敢肯定,如果我能找到引起双重关注的东西,我将能够弄清楚如何进行简单的修改以防止这种情况。
当我放置断点时,我发现自己处于TApplication.Run的消息处理循环中的Forms单元中。该循环正在处理的许多消息中,有两个是设置焦点的消息。我可以逐行跟踪该程序,直到“派遣”消息的“类”单元中的StdWndProc。我有关于消息的所有信息(句柄,参数等)。
我没有和不知道的是消息从何处发起的。调用堆栈中没有ElXTree单元可以提示我。这些例程之一必须发送的消息与当前调用堆栈无关。
如果我能确定该消息是从哪里发送的(即哪个例程发送了该消息),那么我将开始运行。
有什么办法可以找到消息发自何处?或者,还有其他方法可以解决这个双重聚焦问题吗?
作为参考,我正在使用Delphi 2009。
更多的信息:
ElXTree可以使用几十个自己的Windows消息。就我而言,两个相关的是:
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例程是第四行:
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之后添加刷新命令。一旦做到这一点,我就会再次闪烁两次。这是一个真正的麻烦问题。
我也许可以找到一种分页的解决方法,但是我希望该控件的开发人员能够以更好的解决方法对我做出回应。
这样的事情并不是编程的乐趣之一。 :-(
最佳答案
这些消息将发布到消息队列中,而不是同步发送。这很清楚,因为您正在将它们追溯到TApplication.Run
,这是抽取主线程的消息队列的例程。这就是为什么您看不到调用站点的原因。它们是通过在第三方组件中或更可能是Windows中对PostMessage的调用生成的。
我不知道这些组件,所以我怀疑我可以帮助您解决问题。我认为您应该联系应该知道该怎么做的组件供应商。