Delphi:TreeView元素与Form位置的变化

时间:2014-04-25 07:58:20

标签: delphi count treeview element handle

我们发现某些内容似乎是一个错误(?),并导致我们的代码中出现错误。

Delphi XE3,Win32。两种形式,主要有按钮:

procedure TForm4.Button1Click(Sender: TObject);
begin
    with TForm1.Create(Application) do
    begin
        ShowModal;
        Release;
    end;
end;

Form1正在执行此操作:

procedure TForm1.FormCreate(Sender: TObject);
var
    i, j: integer;
    mn: TTreeNode;
begin
    for i := 1 to 10 do
    begin
        mn := TreeView1.Items.Add(nil, 'M' + IntToStr(i));
        for j := 1 to 10 do
        begin
            TreeView1.Items.AddChild(mn, 'C' + IntToStr(j));

        end;
    end;
    Position := poDesigned;
    beep;
    Caption := IntToStr(TreeView1.Items.Count);
end;

在此之后,我在标题中得到0个元素。

但是当我在这个表格中有一个带有此代码的按钮时......

procedure TForm1.Button1Click(Sender: TObject);
begin
    Caption := IntToStr(TreeView1.Items.Count);
end;

...

然后我可以看到好的数字(110个元素)。

如果我在更改位置后编写TreeView1.Handleneeded,则计数也很好。

问题是基于RecreateWnd,它调用DestroyHandles。 但它们只会在Show中修复(在Activate事件中我也可以看到好的结果)。

TreeView是一个特殊的控件,因为树元素是子元素,并且计数是通过它们来计算的,无论它有真正的子对象列表。

ReCreateWnd经常通过其他方法调用的主要问题,因此它也可能导致其他部分出现问题,而且我无法在所有.Count计算之前放置HandleNeeded。

(我们有特殊的基本形式,将位置更正为poDesigned,如果它是poScreenCenter,它可以在以后定位。这发生在FormCreate调用之后,在内部方法。我们发现这个问题只有这些形式,但后来我们也可以用一个简单的代码重现它)

所以问题是 - 这个问题的全球解决方案是什么?

(您是否也在XE5中遇到过这种情况?)

感谢您提供所有帮助,信息,文档。

2 个答案:

答案 0 :(得分:1)

设置HWND时,表单的Position会被销毁。这也会摧毁所有儿童的HWND。当您阅读HWND时,尚未重新创建TreeView的Count,这就是为什么它报告为0.在设置TreeView.HandleNeeded之后调用Position强制TreeView重新启动 - 立即创建它的HWND,这将重新加载TreeView的HWND被销毁时已在内部缓存的任何TreeNode(但仅当TreeView.CreateWndRestores属性为True时,默认情况下为)。

TreeView将其子节点存储在其HWND内。阅读Items.Count只会询问HWND它有多少个节点,所以如果没有HWND,则Count将为0. TTreeView不保留它自己的TTreeNode对象列表,它只是将它们作为用户定义的数据分配给物理节点本身。从树中删除节点后,TTreeView将释放关联的TTreeNode对象。在HWND重新创建的情况下,TTreeView会缓存TTreeNode数据,然后在重新创建HWND时将其重新分配给新节点。但同样,它并没有跟踪TTreeNode个对象。

TTreeView可以做的是在HWND销毁期间存储当前节点数,然后Items.Count如果HWND尚未重新生成TTreeView则返回该值创造了。但是,唉,TTreeView不这样做。但您可以通过继承CreateWnd()来手动实现,以拦截DestroyWnd()Items.Count方法,然后编写自己的函数,在HandleAllocated时返回实际的HWND为true,如果为false,则返回缓存值。

如果该表单对用户可见,则其HWND(以及其子级的HWND将可用,因为如果没有Items.Count,控件将不可见,因此{如果TTreeView对用户可见,则{1}}可用。如果HWND在可见时被销毁,VCL将立即重新创建HWND,以便用户看不到丢失的控件。但是,如果在销毁HWND时用户看不到Form(或TreeView),则在Form / TreeView可见时实际需要时才会重新创建HWND再次。

由于您在创建表单时强制Position始终为poDesigned,为什么不在设计时将Position设置为poDesigned?否则,你可以在填充TreeView之前设置Position,而不是之后,至少:

procedure TForm1.FormCreate(Sender: TObject);
var
  i, j: integer;
  mn: TTreeNode;
begin
  Position := poDesigned; // <-- here
  for i := 1 to 10 do
  begin
    mn := TreeView1.Items.Add(nil, 'M' + IntToStr(i)); // <-- first call to Add() forces HWND recreation if needed
    for j := 1 to 10 do
    begin
      TreeView1.Items.AddChild(mn, 'C' + IntToStr(j));
    end;
  end;
  Beep;
  Caption := IntToStr(TreeView1.Items.Count); // <-- correct value reported
end;

在所有版本的Delphi中都会发生这种情况。这就是VCL中的工作原理。

另外,您应该使用Free()代替Release()Release()仅在表单需要Free()本身时才使用,例如在事件处理程序中,通过延迟Free()直到表单变为空闲。由于表单在ShowModal()退出时已经关闭并处于空闲状态,因此立即Free()表单是安全的。

答案 1 :(得分:0)

我认为你反应过度。你说:

  

ReCreateWnd经常通过其他方法调用的主要问题,因此它也可能导致其他部分出现问题,而且我无法在所有.Count计算之前放置HandleNeeded。

但是在评论中你说这些其他场景是:

  

CMCtlD3Changed CMSysColorChange BORDERSTYLE SetAxBorderStyle SetBorderIcons Dock SetPosition SetPopupMode set_PopupParent RecreateAsPopup ShowModal SetMainFormOnTaskBar

确实,这些方法会导致窗口娱乐。但为什么这对你很重要?您是否经常分配到MainFormOnTaskBar,然后立即请求树视图中的项目计数?你经常更换Ctl3D吗?您是否动态更改BorderIcons?我非常怀疑。

所以我认为你需要解决与Position设置时间相关的直接问题。我会通过确保在填充树视图之前设置Position来处理这个问题。通过在设计时设置Position,或者在填充树视图之前完成此操作。

当然,可能还有与窗口创建有关的其他问题。你需要根据具体情况对待它们。我怀疑你希望某种神奇的开关让这个问题消失。我不相信有一个。如果您在创建句柄之前尝试读取项目计数,那么您将遇到此问题。解决方案是在创建句柄之前不读取项目计数。