【问题标题】:Why does CM_CONTROLLISTCHANGE performs for indirect parent controls?为什么 CM_CONTROLLISTCHANGE 对间接父控件执行?
【发布时间】:2017-07-25 13:59:54
【问题描述】:

我注意到,如果我有一个主容器/父级 (MainPanel),向它添加一个子面板 (ChildPanel),将在 MainPanel (TWinControl.InsertControl()) 上执行 CM_CONTROLLISTCHANGE,这很好.

但是,如果我将子控件 (ChildButton) 插入到 ChildPanel,则会为主 MainPanel 再次触发 CM_CONTROLLISTCHANGE

这是为什么呢?将ChildButton 插入ChildPanel 时,我希望CM_CONTROLLISTCHANGE 只为ChildPanel 触发。

MCVE

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls;

type
  TMainPanel = class(ExtCtrls.TCustomPanel)
  private
    procedure CMControlListChange(var Message: TCMControlListChange); message CM_CONTROLLISTCHANGE;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
  public
    MainPanel: TMainPanel;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TMainPanel.CMControlListChange(var Message: TCMControlListChange);
begin
  if Message.Inserting then
  begin
    Form1.Memo1.Lines.Add('TMainPanel.CMControlListChange: Inserting ' + Message.Control.ClassName);
    // Parent is always nil
    if Message.Control.Parent = nil then Form1.Memo1.Lines.Add('*** Parent=nil');
  end;
  inherited;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  ChildPanel: TPanel;
  ChildButton: TButton;
begin
  FreeAndNil(MainPanel);

  MainPanel := TMainPanel.Create(Self);
  MainPanel.SetBounds(0, 0, 200, 200);
  MainPanel.Parent := Self;

  ChildPanel := TPanel.Create(Self);
  ChildPanel.Parent := MainPanel;

  ChildButton := TButton.Create(Self);
  ChildButton.Parent := ChildPanel; // Why do I get CM_CONTROLLISTCHANGE in "MainPanel"?
end;

end.

DFM

object Form1: TForm1
  Left = 192
  Top = 114
  Width = 685
  Height = 275
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Shell Dlg 2'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 592
    Top = 8
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object Memo1: TMemo
    Left = 456
    Top = 40
    Width = 209
    Height = 193
    TabOrder = 1
  end
end

P.S:我不知道这是否重要,但我在 Delphi 5 上。

【问题讨论】:

  • 所有未处理的 windows 消息都通过父链向上传播。如果孩子不处理消息,父母就有机会这样做,依此类推。
  • @Dsm 你确定吗?这是怎么发生的?一些VCL代码?或者是 Windows 功能。我必须承认我对你所写的说法持怀疑态度。
  • @Dsm, CM_CONTROLLISTCHANGE 是自定义 notification VCL 消息 AFAIK(不确定它是否与本机 Window 消息相同)。我无法在来源中找到如何它是“通过父链向上传播”。所以我仍然不知道发生了什么以及如何。我的主要问题是我无法分辨CMControlListChange 消息处理程序中插入控件的Parent 是谁(但这可能是另一个问题)。
  • @Dsm 很难看出怎么会这样。例如,考虑一个班级私人消息。因为它们是类私有的,所以它们只能被发送到该类的窗口。如果它们被发送到不同类别的窗口,则该消息 ID 可能是不同类别私人消息的 ID。所以我只能得出结论,你在第一条评论中所说的显然是错误的。我建议谨慎的做法是删除所有这些 cmets,以免给读者造成混淆。
  • @Dsm, “所有未处理的 Windows 消息都通过父链向上传播” - 错误。 VCL 专门将CM_CONTROLLISTCHANGE 向上传播到父链。但不是为了任何消息。例如,CM_CONTROLCHANGE发送给不在链上的直接父级。

标签: delphi vcl delphi-5


【解决方案1】:

使用调试器实际上很容易回答这个问题。你自己也可以很容易地做到这一点。启用调试 DCU 并在 TMainPanel.CMControlListChange 中的 if 语句内设置断点。

第一次触发此断点是在插入子面板时。正如您所期望的那样,正在添加主面板的直接子面板,即子面板。断点触发的第二次是兴趣点。这就是添加子面板的子项的时候。

当这个断点触发时,调用栈是这样的:

TMainPanel.CMControlListChange((45100, $22420EC, True, 0)) TControl.WndProc((45100, 35922156, 1, 0, 8428, 548, 1, 0, 0, 0)) TWinControl.WndProc((45100, 35922156, 1, 0, 8428, 548, 1, 0, 0, 0)) TWinControl.CMControlListChange((45100, 35922156, 1, 0, 8428, 548, 1, 0, 0, 0)) TControl.WndProc((45100, 35922156, 1, 0, 8428, 548, 1, 0, 0, 0)) TWinControl.WndProc((45100, 35922156, 1, 0, 8428, 548, 1, 0, 0, 0)) TControl.Perform(45100,35922156,1) TWinControl.InsertControl($22420EC) TControl.SetParent($2243DD4) TForm1.Button1Click(???)

此时我们可以通过双击每个项目来简单地检查调用堆栈。我将从TForm1.Button1Click 开始,这证实了我们确实在回复ChildButton.Parent := ChildPanel。您的作品名列前茅。

两个项目我们来到TWinControl.InsertControl,当我们双击这个项目时,我们发现:

Perform(CM_CONTROLLISTCHANGE, Integer(AControl), Integer(True));

这里,AControl 是按钮,Self 是子面板。让我们继续向上直到TWinControl.CMControlListChange。现在,这是处理该消息的地方,我们仍然有 Self 作为子面板。这个函数的主体是:

procedure TWinControl.CMControlListChange(var Message: TMessage);
begin
  if FParent <> nil then FParent.WindowProc(Message);
end;

这就是谜题的答案。 VCL 将消息向上传播到父链。然后,该调用将转到调用堆栈的顶部,TMainPanel.CMControlListChange,其中Self 现在是主面板,在对TWinControl.CMControlListChange 的调用中是FParent

我知道我可以简单地指向TWinControl.CMControlListChange,这样就可以直接回答这个问题。但我真的想说一点,这样的问题很容易通过相对简单的调试来解决。

请注意,我已经调试了这个 Delphi 6,它是我所拥有的最接近 Delphi 5 的现成版本,但这里概述的原则和答案在所有版本中仍然有效。

【讨论】:

  • 谢谢!显然,我已经仔细调试过了,但我错过了TWinControl.CMControlListChange 部分。较新的版本有CM_CONTROLLISTCHANGING,您可以在其中确定父级,但Delphi 5 没有。你知道我是否可以告诉 打电话给CM_CONTROLLISTCHANGE 并在那时确定Message.Control 父母? (可能这做不到?)
  • 你到底想做什么?如果我知道我可能会提出一种方法来做到这一点。
  • 类似于“TFlowPanel”的东西。每当插入子控件时,我将其添加到我的FControlList,然后根据FControlList 对齐FlowPanel 中的控件。问题是,如果我将子控件放在该 FlowPanel 的直接子控件上,它也会添加到 FControlList...
  • 我认为这可能很难实现。您可以在收到CM_CONTROLLISTCHANGE 时发布消息,然后在处理该消息时维护您的列表。但这有其自身的问题。
  • 有趣的想法。谢谢。但问题是到那时AlignControl 将已经被调用(列表中没有新控件),我试图避免(在发布的消息中再次调用它)。也许我可以处理我认识父母的CM_CONTROLCHANGE,但这会产生相同的效果。再次感谢。
猜你喜欢
  • 2015-07-14
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
  • 1970-01-01
  • 2019-06-12
  • 1970-01-01
  • 2014-06-23
  • 1970-01-01
相关资源
最近更新 更多