Delphi:TreeView元素与Form位置更改



我们发现了一些似乎是bug(?)的东西,并在我们的代码中导致了bug。

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.Handleneed,则计数也很好。

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

TreeView是一个特殊的控件,因为树元素是子元素,计数是根据它们来计算的,无论它有真实的子对象列表。

ReCreateWnd经常被另一个方法调用的主要问题是,所以它也可能在其他部分引起问题,并且我不能把HandleNeeded放在所有.Count计算之前。

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

(你在XE5中也经历过这种情况吗?)

谢谢你的帮助,信息,医生。

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

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

TTreeView本可以在HWND销毁期间存储当前节点数,然后如果HWND尚未重新创建,则让Items.Count返回该值。但遗憾的是,TTreeView并没有做到这一点。但是,您可以手动实现这一点,方法是将TTreeView子类化以拦截CreateWnd()DestroyWnd()方法,然后编写自己的函数,当HandleAllocated为true时返回实际的Items.Count,如果为false则返回缓存值。

如果表单对用户可见,则其HWND(及其子级的HWND)将可用,因为控件在没有HWND的情况下是不可见的,因此如果TTreeView对用户可见则Items.Count将可用。如果它的HWND在可见时被破坏,VCL将立即重新创建HWND,这样用户就不会看到丢失的控件。但是,如果HWND被销毁时表单(或TreeView)对用户不可见,则HWND将不会被重新创建,直到表单/TreeView再次可见时真正需要它。

既然在创建表单时强制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()仅在Form需要Free()自身时使用,例如在事件处理程序中,通过延迟Free()直到Form变为空闲。由于在ShowModal()退出时表单已关闭且处于空闲状态,因此立即Free()表单是安全的。

我觉得你反应过度了。您所在的州:

ReCreateWnd经常被另一个方法调用的主要问题是,所以它也可能在其他部分引起问题,并且我不能把HandleNeeded放在所有.Count计算之前。

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

CMCtlD3Changed CMSysColorChange BORDERSTYLE SetAxBorderStyle SetBorderCons Dock SetPosition SetPopupMode set_PopupParent RecreaceAsPopup ShowModal SetMainFormOnTaskBar

事实上,这些方法会引起开窗娱乐。但为什么这对你来说很重要?您是否经常分配给MainFormOnTaskBar,然后立即请求树视图中的项目计数?你定期更换Ctl3D吗?您是否在动态更改BorderIcons?我非常怀疑。

因此,我认为您需要解决与Position设置时间有关的紧迫问题。我将通过确保在填充树视图之前设置Position来处理这个问题。可以在设计时设置Position,也可以在填充树视图之前设置。

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

最新更新