我们发现某些内容似乎是一个错误(?),并导致我们的代码中出现错误。
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中遇到过这种情况?)
感谢您提供所有帮助,信息,文档。
答案 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
,或者在填充树视图之前完成此操作。
当然,可能还有与窗口创建有关的其他问题。你需要根据具体情况对待它们。我怀疑你希望某种神奇的开关让这个问题消失。我不相信有一个。如果您在创建句柄之前尝试读取项目计数,那么您将遇到此问题。解决方案是在创建句柄之前不读取项目计数。