我有一个第三方树程序包(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的调用生成的。

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

10-06 12:39