为什么CM_CONTROLLISTCHANGE执行间接父控件?

时间:2017-07-25 13:59:54

标签: delphi vcl delphi-5

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

但如果我将一个子控件(ChildButton)插入ChildPanel,则CM_CONTROLLISTCHANGE将再次针对主MainPanel触发!

为什么?我希望CM_CONTROLLISTCHANGE仅在ChildPanel插入ChildButton时触发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的。

1 个答案:

答案 0 :(得分:4)

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

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

当此断点触发时,调用堆栈如下所示:

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现在是主要面板,在调用FParent时为TWinControl.CMControlListChange

我知道我可以简单地指出TWinControl.CMControlListChange并且可以直接回答这个问题。但我真的想说明通过相对简单的调试很容易解决这些问题。

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