Why does CM_CONTROLLISTCHANGE perform for indirect parent controls?

I noticed that if I have a main container / parent ( MainPanel

), adding a child panel ( ChildPanel

) to it will execute CM_CONTROLLISTCHANGE

on MainPanel

(in TWinControl.InsertControl()

), which is fine,

But if I insert a child control ( ChildButton

) in ChildPanel

, a CM_CONTROLLISTCHANGE

will run again for the main one MainPanel

!

Why? I expected CM_CONTROLLISTCHANGE

to only work for ChildPanel

when inserted ChildButton

into 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

      

PS: I don't know if it matters, but I'm on Delphi 5.

+3


source to share


1 answer


This question is actually very easy to answer with a debugger. You could do it yourself pretty easily. Turn on Debug DCU and set a breakpoint inside the statement if

at TMainPanel.CMControlListChange

.

The first time this breakpoint is hit is when a child panel is inserted. This is as you would expect, adding an immediate child of the main panel - the child panel. The second point where the breakpoint is triggered is of interest. This is when adding a child of the child panel.

When this breakpoint is triggered, the call stack looks like this:

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 (???)

At this point, we can simply check the call stack by double clicking on each item. I started with TForm1.Button1Click

, which confirms what we are really responding to ChildButton.Parent := ChildPanel

. Work on your list.

Two items up we come to TWinControl.InsertControl

, and when we double-click on that item, we find:



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

      

Here AControl

is a button and a Self

is a child panel. Let's continue until TWinControl.CMControlListChange

. Now that this message is being processed, and yet we Self

will have a child panel. The body of this function:

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

      

And this is the answer to the riddle. The VCL passes the message to the parent chain. This call then leads to the top of the call stack TMainPanel.CMControlListChange

, where Self

it is now the main panel that was FParent

in the call TWinControl.CMControlListChange

.

I know that I could just point to TWinControl.CMControlListChange

and that would answer the question directly. But what I really want to say is that issues like this are fairly easy to solve with relatively simple debugging.

Please note that I debugged this Delphi 6 which is the most available Delphi 5 version I have, but the principles outlined here and the answer are still valid across all versions.

+4


source







All Articles