我们发现了一些似乎是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
,也可以在填充树视图之前设置。
当然,很可能还有其他与窗口创建相关的问题。你需要根据具体情况对他们进行治疗。我怀疑你是在希望某种神奇的开关,让这个问题消失。我不相信有。如果您试图在创建句柄之前读取项计数,那么您将遇到此问题。解决方案是在创建句柄之前不读取项计数。