【问题标题】:Why the destructor of TTreeNode is called BEFORE parent TTreeview destructor?为什么在父 TTreeview 析构函数之前调用 TTreeNode 的析构函数?
【发布时间】:2021-12-29 13:27:53
【问题描述】:

深表歉意,我很努力,但我找不到任何合理的解释。

还有我自己的TMyTreeNode类:

TMyTreeNode = class (TTreeNode)
private
  FFont:TFont;
  FBrush:TBrush;
public
  constructor Create; 
  destructor Destroy; override;
  property Font:TFont read FFont write FFont;
  property Brush:TBrush read FBrush write FBrush;
end;

constructor THierarchyTreeNode.Create(AOwner: TTreeNodes);
begin
  inherited Create(AOwner);
  FFont := TFont.Create;
  FBrush := TBrush.Create;
end;

destructor TMyTreeNode.Destroy;
begin
  FFont.Free;
  FBrush.Free;
  inherited;
end;

TTreeView 的后代,其中TMyTreeNode 是动态创建的。

当要删除 TMyTreeNode 时,我会覆盖动态方法 procedure TMyTreeview.Delete(Node: TTreeNode); 以实现特定行为。我需要遍历所有其他 TMyTreeNode 对象来更改它们的属性。为简单起见,我强迫他们展示他们的文字:

TMyTreeview = class(TTreeView)
protected
  function CreateNode: TTreeNode; override;
  procedure Delete(Node: TTreeNode); override;
public
  destructor Destroy; override;
end;
        
function TTreeViewHierarchy.CreateNode: TTreeNode;
// var LClass: TTreeNodeClass;
begin
// LClass := THierarchyTreeNode;
// if Assigned(OnCreateNodeClass) then
//   OnCreateNodeClass(Self, LClass);
// Result := LClass.Create(Items);
// The constructor of THierarchyTreeNode is not called, because constructor of TTreeNode is not virtual !!!;
  Result := THierarchyTreeNode.Create(Items);
end; 

procedure TMyTreeview.Delete(Node: TTreeNode);
var
  i: Integer;
begin
  inherited;
  for i := 0 to Items.Count-1 do ShowMessage(Items[i].Text); //Example  
end;

destructor TMyTreeview.Destroy;
begin
  inherited;
end;

假设 5 个 TMyTreeNode 对象被添加到 TMyTreeview 中。当myTreeview.Items.Delete(myTreeview.Selected); 987654331 TMyTreeNode首先被调用默认的析构函数时,然后TMyTreeview.Delete()

ShowMessage() 出现 5 次。这表明,将被删除的节点和所有其他 TMyTreeNode 对象都存在并且被正确引用。

这是预期的行为。

但是当我关闭应用程序时,我收到一条错误消息:

...class ETreeViewError with message 'Invalid index'

TMyTreeview.Delete() 方法中。

有两件事让我很困惑:

  1. 如果关闭(销毁)应用程序的MainFormTMyTreeNode.Destroy()TMyTreeview.Destroy() 之前被调用,为什么? TTreeNodes 的所有者是 TCustomTreeView,我希望 TTreeNodes 会在祖先的析构函数中被释放 (TTreeView.Destroy())。

  2. 如果我承认上述奇怪的事实,那么procedure TMyTreeview.Delete(Node: TTreeNode); 会失败。 Items 似乎已经被取消引用,Items.Count 没有提供正确的结果(它包含取消引用的指针)。但这没有任何意义。

提前致谢。我花了几个小时来调试它,但尽管我在 Delphi 有长期经验,但我无法得到它。

【问题讨论】:

  • 如果你把继承放在你的showmessage循环之后会有什么不同吗?
  • @whosrdaddy 不......我试过......顺便说一句,我承认似乎合乎逻辑
  • 你不是要重写 TTreeNode 的虚拟构造函数,并获取树视图类来实例化你的派生类吗?有一个我不记得的机制。如果您想知道析构函数为何按此顺序运行,请查看使用调试器和调用堆栈的代码。这将很容易理解。
  • @DavidHeffernan 我不能强制调试器跳转到 ComCtrls.pas,它总是跳过。
  • @DavidHeffernan 。大卫,我还重写了 TMyTreeNode 的构造函数(见上文),但奇怪的是,我不能在那里设置断点。字体和画笔为零....为什么?我在网上找不到任何东西,看起来你可能是对的,但无论如何它令人困惑

标签: delphi delphi-7


【解决方案1】:

当组件TTreeview要被销毁时,它会在procedure TCustomTreeView.WndProc(var Message: TMessage);中得到WM_DESTROY消息。

随后调用procedure TCustomTreeView.CNNotify(var Message: TWMNotify);,其中TWMNotify.NMHdr^.Code = TVN_DELETEITEM

TVN_DELETEITEM:
        begin
          Node := GetNodeFromItem(PNMTreeView(NMHdr)^.itemOld);
          if Node <> nil then
          begin
            Node.FItemId := nil;
            FChangeTimer.Enabled := False;
            if FStateChanging then
              Node.Delete
            else
              Items.Delete(Node);
          end;
        end;

它只是在调用TTreeview.destructor 之前删除TTreeNodes。在这种情况下 ComponentState 设置为 csDestroying,因此摆脱陷阱的一种方法可能是:

procedure TMyTreeview.Delete(Node: TTreeNode);
var i:Integer;
begin
   inherited;
   if not (csDestroying in ComponentState) 
        then for i := 0 to Items.Count-1 do ShowMessage(Items[i].Text);
end;

顺便说一句。谢谢,大卫...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-30
    • 2021-08-04
    • 2021-04-01
    • 2014-03-05
    • 2018-05-24
    • 2015-08-21
    • 1970-01-01
    相关资源
    最近更新 更多