3

我有一个第三方树包(LMD 创新的 ElXTree),我在我的程序中用作网格。每当我选择一个单元格时,该行就会获得焦点并突出显示,就像我想要的那样。

当我通过单击网格中的一个单元格来调用提供的 Inplace 编辑器时,该行获得焦点。因为在编辑模式下选择了单元格,所以只有单元格被突出显示(而不是整行),这也是我想要的。

我不想要的是:当我就地编辑一个单元格时,我通过单击另一个单元格来调用它的就地编辑器,首先具有旧单元格的行被赋予焦点并突出显示。然后它立即将其焦点移开并取消突出显示,并且具有新单元格的行被赋予焦点并突出显示。然后,除了正在就地编辑的单元格之外,该新行立即变为未突出显示。这会导致烦人的双闪,我想摆脱它。

我有包的源代码,一直在调试。我敢肯定,如果我能找到引起双重聚焦的原因,我将能够弄清楚如何进行简单的修改来防止它。

当我放置断点时,我发现我处于 Forms 单元中 TApplication.Run 的消息处理循环中。此循环正在处理的许多消息中的两条是设置焦点的消息。我可以将程序逐行跟踪到 Classes 单元中的 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 例程是第 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 之后添加刷新命令。一旦我这样做了,我又得到了双闪。这是一个真正的混乱问题。

我也许能够找到分页的解决方法,但我希望该控件的开发人员能够以更好的修复响应我。

这样的事情不是编程的乐趣之一。:-(

4

1 回答 1

3

These messages are posted to the message queue rather than sent synchronously. This is clear because you are tracing them back to TApplication.Run which is the routine that pumps your main thread's message queue. That's why you don't see the call site on the stack. They are generated by calls to PostMessage, either in the 3rd party component or possibly more likely by Windows.

I don't know these components so I doubt I can help solve your problem. I think you should contact the component vendor who should know what to do.

于 2011-06-23T07:16:13.340 回答