gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 15:51:30 26 4
gpt4 key购买 nike

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

但是,如果我将子控件 (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。

最佳答案

这个问题实际上使用调试器很容易回答。你自己可以很容易地做到这一点。启用调试 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(???)

At this point we can simply inspect the call stack by double clicking on each item. I'd start at TForm1.Button1Click which confirms that we are indeed responding to ChildButton.Parent := ChildPanel. The work your way up the list.

Two items up we come to TWinControl.InsertControl and when we double click on this item we find:

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

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

请注意,我已经调试了这个 Delphi 6,这是我所拥有的与 Delphi 5 最接近的可用版本,但此处概述的原则以及答案在所有版本中仍然有效。

关于delphi - 为什么 CM_CONTROLLISTCHANGE 对间接父控件执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45305584/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com