【问题标题】:Ownership principles when using tpagecontrol使用 tpagecontrol 时的所有权原则
【发布时间】:2017-10-04 20:22:50
【问题描述】:

两天前我对这个question给出了一个接受的答案(这是最困扰我的):

  newtabsheet:=ttabsheet.Create(PageControl1);
  NewTabSheet.PageControl := PageControl1;
  newtabsheet.Caption:='tab1';
  i1:=tabs.Count;
  tabs.Add(newtabsheet);

我已经尽我所能分析了这段代码,这些就是我的结果。

第 1 行:我创建了一个 ttabsheet,以 pagecontrol1 作为父/所有者(基于下面的构造函数)。

constructor TTabSheet.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Align := alClient;
  ControlStyle := ControlStyle + [csAcceptsControls, csNoDesignVisible,
    csParentBackground, csPannable];
  Visible := False;
  FTabVisible := True;
  FHighlighted := False;
end;

然后我在变量Newtabsheet 中存储了对它的引用(此评论基于@David Heffernan 对one 我的问题的回答)。

第 2 行:我使用引用将 pagecontrol1 分配给属性 .pagecontrol

这是设置方法的代码:

procedure TTabSheet.SetPageControl(APageControl: TPageControl);
begin
  if FPageControl <> APageControl then
  begin
    if FPageControl <> nil then
      FPageControl.RemovePage(Self);
    Parent := APageControl;
    if APageControl <> nil then
      APageControl.InsertPage(Self);
  end;
end;

基于此,我认为(可能是错误的)它将现有父级与新传递的参数进行比较,如果有不同,那么如果 parent&lt;&gt;nil 从以前的 pagecontrol 中删除此 newtabsheet(视觉影响)并将其分配给新的父/所有者,然后如果新的parent&lt;&gt;nil 将其插入新的pagecontrol(视觉效果)。

所以这行是不必要的(可能是错误的),因为我已经在第一行做了。

第 3 行:我使用引用将 tabsheet 的标题分配给 'tab1'。

第 5 行:我使用 add 方法在标签列表 tabs:TList&lt;ttabsheet&gt;; 中存储对 tabsheet 的引用。

这就是事情开始从我头顶飞过的地方。

我认为父/所有者是tabs,但pagecontrol1 仍在显示选项卡(基于对此question 的公认答案,更改父级视觉上会从pagecontrol1 中删除tabsheet) ,但事实并非如此。

现在这可能是错误的,但如果它只是一个参考,那么为什么当我通过执行 PageControl1.ActivePage.freepagecontrol1 删除 tabsheet 时,tabs.count 保持不变。

如果我从标签中删除tabsheet,则pagecontrol1 上的tabsheet 不会被删除(视觉上删除)。

question 我了解到泛型成为父/所有者,您无需担心从pagecontrol1 中释放tabsheet,因为选项卡是父级,您只需要将其从@987654354 中释放@。

我的问题:这段代码发生了什么,为什么我会遇到这种行为?

澄清

导致问题的原因是当我删除页面控件中的 ttabsheet 时,为什么当我尝试在列表中使用对它的引用时它没有引发错误,它们是两个不同的对象。

【问题讨论】:

  • 通用列表对页面控件一无所知。它不会干扰父母或所有者的关系。不确定问题到底是什么。这么多代码摘录。
  • @DavidHeffernan 我的问题是为什么当我从列表中删除标签页时它没有从 pagecontrol1 中删除,反之亦然?。
  • 为什么要这样? tabs 列表只是对标签页的并行引用。所有者(= 负责生命周期管理)是 PageControl
  • Owner负责在不再需要时释放对象。 Parent 为视觉控件提供了一个表面来呈现自己。 OwnerParent 可以相同,但不必相同。对于PageControl(这是一个Windows 控件),PageControl 属性会覆盖Parent 属性,因此更改TTabSheetParent 不会影响其可见性。您指的是另一个问题(在 LB1 和 LB2 之间移动列表项)。那是一个完全不同的问题。问题是关于 Firemonkey 和列表​​框,而不是 Microsoft PageControls。
  • @TomBrunberg 你能否解释一下,我的意思是当我从页面控件中删除选项卡时,为什么它没有从列表中删除。或者至少在使用引用时会出现一些错误。

标签: delphi generics


【解决方案1】:

Owner 负责内存管理。将TPageControl 分配为TTabSheetOwner 意味着TPageControl 将在TPageControl 被销毁时销毁TTabSheetOwner 和 ownee 包含指向彼此的指针,因此它们可以相互通知与内存管理相关的重要事件。

Parent 负责窗口管理和视觉呈现。将TPageControl 指定为TTabSheetParent 意味着TTabSheet 的窗口是TPageControl 的窗口的子窗口,并将显示在TPageControl 的客户区内窗户。 Parent 和 child 包含指向彼此的指针,因此它们可以相互通知与窗口管理相关的重要事件。

OwnerParent两个不同的东西。它们可以是同一个对象,但它们不一定是(例如 - 在设计时创建的组件始终归 TFormTFrameTDataModule 所有正在设计中,但可以是容器组件的子组件,例如 TPanel 等)。

TList 不负责什么。它只是一个任意值的动态数组,在这种情况下恰好是TTabSheet 指针。而已。 TListTTabSheet 之间根本没有所有者/父关系。

TTabSheet被销毁时,它自身和它的TPageControl之间存在关系链接。 TTabSheet 将自己从其 TPageControl 的管理中移除。它不再归TPageControl 所有,也不再是TPageControl 的子代。这两个对象清除彼此的指针。

TTabSheetTList 之间根本没有关系链接。 TTabSheet 没有 TList 甚至存在的概念。当TTabSheet 被添加到TList 或从TList 中删除时,TList 只是添加/删除指向TTabSheet 对象的指针。 TList 不会通知 TTabSheet 关于插入/删除。并且没有从TTabSheetTList 的指针,因此当TTabSheet 被销毁时,没有适当的机制允许从TList 中删除指向TTabSheet 的指针。指针保留在列表中,您可以继续使用指针(用于比较等),只要您不取消引用它来访问已销毁的TTabSheet

如果要在TTabSheet被销毁时自动从TList中删除TTabSheet指针,则需要调用TTabSheetFreeNotification()方法。然后,您的Notification() 回调可以从TList 中删除TTabSheet 指针以响应opRemove 通知,例如:

TMyForm = class(TForm)
  ...
private
  tabs: TList<TTabSheet>;
  ...
protected
  procedure Notification(AComponent: TComponent; Operation: TOperation); override; // <-- ADD THIS
  ...
end;

...  

procedure TMyForm.DoSomething;
var
  NewTabSheet: TTabSheet;
  ...
begin
  ...
  NewTabSheet := TTabSheet.Create(PageControl1);
  NewTabSheet.PageControl := PageControl1;
  NewTabSheet.Caption := 'tab1';
  tabs.Add(NewTabSheet);
  NewTabSheet.FreeNotification(Self); // <-- ADD THIS
  ...
end;

procedure TMyForm.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent is TTabSheet) then
    tabs.Remove(TTabSheet(AComponent)); // <-- HERE
end;

如果您希望 TTabSheet 在其指针从 TList 中删除时自动从其 TPageControl 中删除,请改为切换到 TObjectList。它的OwnsObjects 属性默认为true,因此当使用Remove()Delete() 方法从列表中删除指针时,它将破坏TTabSheetExtract() 方法不会破坏TTabSheet,所以在你的Notification() 回调中使用它而不是Remove(),例如:

TMyForm = class(TForm)
  ...
private
  tabs: TObjectList<TTabSheet>;
  ...
end;

...  

procedure TMyForm.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent is TTabSheet) then
    tabs.Extract(TTabSheet(AComponent)); // <-- HERE
end;

【讨论】:

  • 我已经尝试过这个解决方案,在这个问题中,它给了我一个错误。你能告诉我更多关于免费通知方法stackoverflow.com/questions/44487294/….
  • 代码太多,描述太少。我不会花时间试图弄清楚。您需要自己调试它。
  • 后来发现FreeNotification方法的使用有问题。在我上面链接的问题中。谢谢
猜你喜欢
  • 2012-09-15
  • 2016-09-22
  • 1970-01-01
  • 1970-01-01
  • 2018-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多