如何在 Delphi 中查找消息发送的位置?

发布于 2024-11-17 00:57:01 字数 5866 浏览 4 评论 0原文

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

当我通过单击网格中的单元格调用提供的就地编辑器时,该行获得焦点。因为单元格是在编辑模式下选择的,所以只有单元格会突出显示(而不是整行),也正如我想要的那样。

我不想要的是这样的:当我就地编辑一个单元格,并通过单击另一个单元格调用就地编辑器时,首先包含旧单元格的行将获得焦点并突出显示。然后,它立即失去焦点并取消突出显示,并且包含新单元格的行将获得焦点并突出显示。然后,除了正在就地编辑的单元格之外,该新行立即变为不突出显示。这会导致恼人的双闪,我想摆脱它。

我有该包的源代码,并且我已经对其进行了调试。我确信,如果我能找到调用双重聚焦的原因,我就能​​找出如何进行简单的修改来防止它。

当我放置断点时,我发现我处于 Forms 单元中 TApplication.Run 的消息处理循环中。该循环正在处理的众多消息中的两条是用于设置焦点的消息。我可以逐行跟踪程序,一直到类单元中的 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 例程中放置断点时,我得到以下调用堆栈:

Call Stack

调用堆栈中唯一的其他 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 并查看调度的使该行聚焦的事件。我似乎无法找出是什么问题导致了该消息。

现在,为什么我不知道该消息的问题是什么?好吧,我假设它来自 PostMessage 或 SendMessage 命令,正如 David 所说。当我在 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 之后添加刷新命令。一旦我这样做了,我又得到了双闪。这真是一个混乱的问题。

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

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

I have a third party tree package (ElXTree by LMD Innovative) that I am using as a grid in my program. Whenever I select a cell, that row gains focus and becomes highlighted, just as I want it.

When I invoke the supplied Inplace editor by clicking on a cell in the grid, that row gains focus. Because the cell is selected in edit mode, only the cell gets highlighted (not the whole row), also just as I want it.

What I don't want is this: When I'm inplace editing one cell, and I invoke the inplace editor for another cell by clicking on it, first the row with the old cell is given focus and is highlighted. It then immediately has its focus taken away and is unhighlighted and the row with the new cell is given focus and highlighted. Then that new row immediately becomes unhighlighted except for the cell being inplace edited. This causes an annoying double flashing and I want to get rid of it.

I have the source code of the package, and I've been debugging through it. I'm sure if I can just find what is invoking the double focusing, I'll be able to figure out how to make simple modifications to prevent it.

When I place breakpoints I find that I am in the message handling loop of TApplication.Run in the Forms unit. Two of the many messages this loop is handling are the ones to set the Focus. I can trace the program line by line right through to StdWndProc in the Classes unit, where the message is Dispatched. I have all the information about the message (the Handle, Parameters, etc).

What I don't have and don't know is where the message is initiated from. There are no ElXTree units in the call stack to clue me in. One of those routines must have sent the message independent of the current call stack.

If I could just find out where that message was sent from (i.e. what routine sent it), then I'll be off and running.

Is there any way to find where the message was sent from? Or alternatively, is there any other way I might be able to get around this double focusing problem I'm having?

For reference, I'm using Delphi 2009.


Further information:

ElXTree has several dozen of its own Windows messages that it works with. In my case, the two relevant ones are:

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 }

When I put a breakpoint in, say, the WMSetFocus routine, I get the following call stack:

Call Stack

The only other ElXTree routine in the call stack is one on the 4th line:

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;

When I put a breakpoint in this routine, it only seems to pass through to the "inherited" line and then call system functions, ultimately getting to the StdWndProc where the messages are handled (as I described in my original question).

The problem involved in tracing this accurately is that I must make mouse clicks and keep the mouse pointer over the visual control in the program while also debugging through the code. Any mistake in moving or using my mouse while debugging can cause additional mouse events that affect the processing This makes it a real bugger to debug.

But I can carefully trace into StdWndProc and see the event that gets dispatched that focuses the line. What I can't seem to do is find out what issues the message.

Now, why don't I know what issues the message? Well, I assume it is from a PostMessage or SendMessage command as David says. When I look for where all these calls are made in ElXTree, I only find these 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 }

The first 7 deal with scrollbars. The next 3 are with ColorChange.

I've looked through all the other LMD component routines as well for the issuing of messages, and nothing there looks promising.

So I'm still stuck and need a hint or a clue as to how to find the sender of that message that is asking for the line to be focused.


Workaround:

Well, once I realized that Windows was initiating the Mouse Events, I was able to do something that stops most of the flashing. It's a real hack though. If someone knows of something better, I'd love to hear about it.

In the TElXTreeView.WndProc, I've replaced the inherited statement with the following:

  if (Message.Msg = WM_SETFOCUS) or (Message.Msg = WM_KILLFOCUS) then begin
      FOwner.Items.BeginUpdate;
      inherited;
      FOwner.Items.EndUpdate;
  end
  else
    inherited;

What this does is stop the multiple focusing from happening within the called routines.

It does the job except in one case: Where I'm clicking on an editable entry, it still does highlight the entry first before going into edit mode. That's because the highlight occurs on the MouseDown but the going into edit mode occurs on the MouseUp. I might be able to find a way around this, but initial attempts were unsuccessful. But it's not as bad as the double flashing, and I could live with it if I have to.

Thanks to those of you who helped give my brain a push. The accepted answer goes to David who gave me the key clue.


... Maybe I spoke too soon. I found some other controls, e.g. the pages with the grid on it, would not update when paging between the controls. I tried adding a Refresh command after the EndUpdate. Once I did that, I got the double flashing again. This is a real messy problem.

I may be able to get a workaround for the paging, but I hope the developer of that control responds to me with a better fix.

Things like this are NOT one of the joys of programming. :-(

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

无语# 2024-11-24 00:57:01

这些消息被发布到消息队列而不是同步发送。这很清楚,因为您将它们追溯到 TApplication.Run,这是泵送主线程消息队列的例程。这就是为什么您在堆栈上看不到调用站点的原因。它们是通过调用 PostMessage 生成的,无论是在第 3 方组件中还是更可能由 Windows 调用。

我不了解这些组件,所以我怀疑我能否帮助解决您的问题。我认为您应该联系应该知道该怎么做的组件供应商。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文