Question

We have found something is seems to be a bug(?), and cause bug in our code.

Delphi XE3, Win32. Two forms, the main have button:

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

The Form1 is doing this:

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;

After this I get 0 elements in caption.

But when I have a button in that form with this code...

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

...

Then I can see the good number (110 elements).

If I write TreeView1.Handleneeded after Position changing, the count is also good.

The problem is based on RecreateWnd, which calls DestroyHandles. But they will be repaired only in Show (in the Activate event I can see good result too).

TreeView is a special control, because the tree elements are children, and the count is calculated by on them, no matter it have real sub-object list.

The main problem that ReCreateWnd called often by another methods to, so it can cause problems in another sections too, and I cannot put HandleNeeded before all .Count calculation.

(We have special base form that correct the Position to poDesigned if it was poScreenCenter to it can be positionable later. This happens after FormCreate call, in an inner method. We found this problem only with these kind of forms, but later we could reproduce it in a simple code too)

So the question is - what is the global solution to this problem?

(Did you experience this in XE5 too?)

Thank you for all help, info, doc.

Was it helpful?

Solution

The Form's HWND is getting destroyed when you set the Position. That also destroys all child HWNDs. The TreeView's HWND has not been re-created yet when you read its Count, which is why it reports 0. Calling TreeView.HandleNeeded after setting the Position forces the TreeView to re-create its HWND immediately, which will re-load any TreeNodes that had beencached internally when the TreeView's HWND was destroyed (but only if the TreeView.CreateWndRestores property is True, which it is by default).

A TreeView stores its child nodes inside of its HWND. Reading the Items.Count merely asks the HWND how many nodes it has, so if there is no HWND then the Count will be 0. TTreeView does not keep its own list of TTreeNode objects, it merely assigns them as user-defined data in the physical nodes themselves. When nodes are removed from the tree, TTreeView frees the associated TTreeNode objects. In the case of HWND recreation, TTreeView caches the TTreeNode data and then re-assigns it back to new nodes when the HWND is re-created. But again, it does not keep track of the TTreeNode objects.

What TTreeView could have done is store the current number of nodes during HWND destruction, and then have Items.Count return that value if the HWND has not been re-created yet. But alas, TTreeView does not do that. But you could implement that manually by subclassing TTreeView to intercept the CreateWnd() and DestroyWnd() methods, and then write your own function that returns the actual Items.Count when HandleAllocated is true and returns your cached value if it is false.

If the Form is visible to the user then its HWND (and the HWND of its children) will be available, since a control is not visible without an HWND, so Items.Count will be available if the TTreeView is visible to the user. If its HWND is ever destroyed while visible, the VCL will re-create the HWND immediately so the user does not see a missing control. However, if the Form (or TreeView) is not visible to the user when the HWND is destroyed, the HWND will not be re-created until it is actually needed when the Form/TreeView is made visible again.

Since you are forcing the Position to always be poDesigned at the time of Form creation, why not just set the Position to poDesigned at design-time? Otherwise, you can simply set the Position before populating the TreeView instead of afterwards, at least:

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;

This happens in all versions of Delphi. This is simply how things work in the VCL.

On a side note, you should use Free() instead of Release(). Release() is meant to be used only when a Form needs to Free() itself, such as in an event handler, by delaying the Free() until the Form becomes idle. Since the form is already closed and idle by the time ShowModal() exits, it is safe to Free() the Form immediately.

OTHER TIPS

I think you are over-reacting. You state:

The main problem that ReCreateWnd called often by another methods to, so it can cause problems in another sections too, and I cannot put HandleNeeded before all .Count calculation.

But then in comments you say that these other scenarios are:

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

Indeed these methods will cause window recreation. But why does that matter for you? Do you routinely assign to MainFormOnTaskBar and then immediately request the count of items in your tree view? Do you change Ctl3D regularly? Are you changing BorderIcons dynamically? I very much doubt it.

So I think you need to tackle the immediate problem which relates to the timing of the setting of Position. I would deal with that by making sure that Position is set before you populate the tree view. Do that by either setting Position at design time, or simply before you populate the tree view.

Of course, there may well be other problem related to window creation. You'll need to treat them on a case by case basis. I suspect that you are hoping for some sort of magic switch that just makes this issue disappear. I don't believe that there is one. If you try to read the item count before the handle is created, then you will suffer from this problem. The solution is not to read the item count before the handle is created.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top