Jump to content
aehimself

TTreeNode leak when VCL styles are active

Recommended Posts

Not yet. But I see that the tree view does not receive WM_DESTROY message after pressing a close button. Which is quite strange.

Edited by balabuev
  • Thanks 1

Share this post


Link to post
Guest
On 2/23/2021 at 5:24 PM, aehimself said:

Now, if the TreeView on the TabSheet has ANY items created if the application has a VCL style active, those items will not be freed up.

here my tests:

  • RAD Studio 10.3.3 Arch
  • VCL project with "1 Frame" and "2 TreeViews"
  • the frames is created by code on FormMain OnCreate, and destroyed on FormMain OnDestroy events, as usual!
  • using reportmemoryleaks for tests!

image.thumb.png.92ac688cec0c40b60bf5f051cb075122.png  image.thumb.png.02ed803f5d9af059437d3c6e1effcd8b.png

 

FormMain unit

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    spdbtn_ShowMyFrameOnPanel2: TSpeedButton;
    pnlFormMainClientArea: TPanel;
    ComboBox1: TComboBox;
    procedure spdbtn_ShowMyFrameOnPanel2Click(Sender: TObject);
    procedure ComboBox1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  Vcl.Themes,
  uFrameWithTreeViews;

var
  myFrameWithTreeViews: TfrmeFrameWithTreeViews = nil;

procedure TForm1.ComboBox1Click(Sender: TObject);
begin
  if (ComboBox1.ItemIndex > -1) then
  begin
    TStyleManager.TrySetStyle(ComboBox1.Items[ComboBox1.ItemIndex], true);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ComboBox1.Items.AddStrings(TStyleManager.StyleNames);
  ComboBox1.ItemIndex := ComboBox1.Items.IndexOf(TStyleManager.ActiveStyle.Name);
  Caption             := TStyleManager.ActiveStyle.Name;
  //
  myFrameWithTreeViews := TfrmeFrameWithTreeViews.Create(nil);
  //
  if not(myFrameWithTreeViews = nil) then
    pnlFormMainClientArea.Caption := 'myFrameWithTreeViews was created successfully'
  else
    pnlFormMainClientArea.Caption := 'myFrameWithTreeViews WAS NOT created!';
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(myFrameWithTreeViews);
end;

procedure TForm1.spdbtn_ShowMyFrameOnPanel2Click(Sender: TObject);
begin
  spdbtn_ShowMyFrameOnPanel2.Enabled := false; // just one time!
  //
  pnlFormMainClientArea.Font.Size := 12; // for better look!
  //
  if not(myFrameWithTreeViews = nil) then
  begin
    myFrameWithTreeViews.Parent := pnlFormMainClientArea;
    myFrameWithTreeViews.Align  := TAlign.alClient;
  end;
end;

initialization

ReportMemoryLeaksOnShutdown := true;

finalization

end.

 

Frame unit

type
  TfrmeFrameWithTreeViews = class(TFrame)
    Splitter1: TSplitter;
    tvOnPanelLeft: TTreeView;
    pnlToPageControl: TPanel;
    Splitter2: TSplitter;
    pnlWithPageControlInto: TPanel;
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    tvOnTabSheet1: TTreeView;
    TabSheet2: TTabSheet;
    pnlWithMemoInto: TPanel;
    Memo1: TMemo;
    pnlToTreeViewOnLeftOnFrame: TPanel;
    Panel1: TPanel;
    spdbtnAddNewItemsOnLeft: TSpeedButton;
    Panel2: TPanel;
    spdbtnAddNewItemsOnRight: TSpeedButton;
    pgctrlOnFrame: TPageControl;
    TabSheet3: TTabSheet;
    tvOnpgctrlOnFrameOnTabSheet3: TTreeView;
    SpeedButton1: TSpeedButton;
    procedure spdbtnAddNewItemsOnLeftClick(Sender: TObject);
    procedure spdbtnAddNewItemsOnRightClick(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

implementation

{$R *.dfm}

procedure TfrmeFrameWithTreeViews.spdbtnAddNewItemsOnLeftClick(Sender: TObject);
begin
  tvOnPanelLeft.Items.Add(nil, Format('NewItem_%d', [tvOnPanelLeft.Items.Count + 1]));
end;

procedure TfrmeFrameWithTreeViews.spdbtnAddNewItemsOnRightClick(Sender: TObject);
begin
  tvOnTabSheet1.Items.Add(nil, Format('NewItem_%d', [tvOnTabSheet1.Items.Count + 1]));
end;

procedure TfrmeFrameWithTreeViews.SpeedButton1Click(Sender: TObject);
begin
  tvOnpgctrlOnFrameOnTabSheet3.Items.Add(nil, Format('NewItem_%d', [tvOnpgctrlOnFrameOnTabSheet3.Items.Count + 1]));
end;

end.

 

NOTE: now add a new PageControl "on" Frame, and same logic to add new "items" and change styles on app!

image.thumb.png.4f60d826c0ddc79edfac00dfc9cedddb.png

 

hug

Edited by Guest

Share this post


Link to post

I'm not sure whether it's even worth further debugging.

I just got an email from quality emba that they are unable to reproduce this:

https://quality.embarcadero.com/browse/RSP-20010

 

and there is also this:

 

https://quality.embarcadero.com/browse/RSP-15109

 

The whole unit is a mess.

 

(And I don't think they fixed it. Did they? 10.4.2 anyone?)

Edited by Attila Kovacs

Share this post


Link to post

I have to admit that this is one of rare cases when I cannot find the reason of issue :classic_blush:

 

What I've found:

 

- Upon closing the form calls DestroyWindow in its destructor to destroy self handle. As specified in MSDN, DestroyWindow destroys all children windows (recursively) and then - the window itself. And it should send WM_DESTROY messages. 

- So, this is not Delphi stuff (no recursive itertation of children, etc) - all handles of all controls are destroyed with a single call to DestroyWindow.

- However, WM_DESTROY is not received by the tree view. Moreover, WM_NCDESTROY message (which should be sent after WM_DESTROY) is received - and so, this is an indicator that everything is still set up at the moment (constrol exists; its handle exists and valid; its WndProc is still wired and continues to receive messages).

 

So, I believe that something crazy happens recursively, which prevents DestroyWindow to do its work correctly. Maybe DestroyWindow or some other incompatible API is called second time from one of messages sent by the original DestroyWindow call - something like that.

 

I've tried to Google, but was unable to find any results where people discuss mising WM_DESTROY messages.

 

Another idea is that some hook (maybe even style hook) eats the message. But, unfortunately, tree view's style hook also does not receive WM_DESTROY.

 

Who steal the message!!!

 

  • Like 1

Share this post


Link to post
Guest

maybe if he can upload the files envolved as original for test?

 

hug

Share this post


Link to post
20 minutes ago, emailx45 said:

maybe if he can upload the files envolved as original for test?

Test project  (was already provided in one of previous posts)

 

  • Like 1

Share this post


Link to post
Guest
3 hours ago, balabuev said:

was already provided in one of previous posts

i dont saw it, thanks

 

hug

Share this post


Link to post
On 2/24/2021 at 3:26 PM, balabuev said:

What is interesting: removing the bottom list view will stop the bug from occurring.

This is indeed weird.  In fact, just turning the Visible property of the ListView to False prevents the error.

 

Some further observations:

  • I confirm that WMDestroy is not received by the treeview
  • The bug is definitely related to Vcl.Styles.  If you change the style to a Vcl.Style and then back to Windows, the error does not occur
  • You can prevent the error by calling Treeview.Items.Clear in the FormCloseQuery event handler.  Strangely calling RecreateWnd in the same handler also prevents the error.

Share this post


Link to post
Guest
On 2/26/2021 at 11:39 AM, balabuev said:

 (was already provided in one of previous posts)

hi @balabuev

trying just building, no executing Debug or Release:  why your project try execute "Powershell later building..."? 

Quote

[Exec Error] The command "powershell.exe Expand-Archive -Force ..\..\Cairo\Dlls\librsvg-Win32.zip .\build\ " exited with code 1.

hug

Edited by Guest

Share this post


Link to post
3 minutes ago, emailx45 said:

hi @balabuev

trying just building, no executing Debug or Release:  why your project try execute "Powershell later building..."? 

hug

You need to remove the build event from the project options.  This was stated earlier in this thread.

  • Like 1

Share this post


Link to post
Guest

my fault!! all the same...

 

hug

Edited by Guest

Share this post


Link to post

The issue results from the TListView being recreated as a response to the CM_STYLECHANGED message (see CustomListView.WndProc).

 

If you add the following at the top of UMainForm.pas

 

  TListView = class(Vcl.ComCtrls.TListView)
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

with the following implementation

procedure TListView.WndProc(var Message: TMessage);
begin
  if (Message.Msg = CM_STYLECHANGED)  then begin
    if ViewStyle = vsReport then
      UpdateColumns;
  end else
    inherited;
end;

the error does not happen.   There does not appear to be much point in recreating the ListView, since its handle would have already been recreated, when the form it resides gets recreated.  Still it remains a mystery why the recreation of the ListView affects the TreeView!

 

The memory leak is also prevented by calling RecreateWnd after changing the style.

 

In the process of debugging, I discovered another Vcl bug: [RSP-33221] CM_STYLEDCHANGE is broadcast twice - Embarcadero Technologies

 

 

Edited by pyscripter
  • Like 2

Share this post


Link to post
Guest

and more, "TCustomListView.WnProc ... process" calls:

unit: Vcl.Controls.pas, line: 7185 (RAD 10.3.3)
function TControl.Perform(Msg: Cardinal; WParam: WPARAM; LParam: LPARAM): LRESULT;
...
  if Self <> nil then
    WindowProc(Message); // call the "procedure TCustomListView.WndProc(var Message: TMessage);" line 19652, again!!! then this is 2ªx

 

hug

Edited by Guest

Share this post


Link to post

1. I have reduced the test project (in source.zip on page 1) further and have a very small observation: With only three components (RadioGroup, TreeView, ListView) the memory leak disapears. But the memory leak (after switching style at runtime) is back when TreeView and ListView are children of (the same) Panel. I needed to bring back the panel! In other words: The observation is that the parent Panel plays a role. Why is it that we need a Panel (or maybe other common parent component) to see the problem?

2. Confirmed that the TListView.WndProc override 'works' in Tokyo.


 

  • Like 1

Share this post


Link to post
Guest

as another container, it sould provide the "pacific end", be a TPanel, TLaiout ot TForm.

of course, if you added a Item in a object, then, you should "clear" it, as do IDE on Desingtime.

 

hug

Share this post


Link to post
8 hours ago, pyscripter said:

In the process of debugging, I discovered another Vcl bug: [RSP-33221] CM_STYLEDCHANGE is broadcast twice - Embarcadero Technologies

This whole saga is typical of the VCL Styles support - in short, it's a mess.

 

My guess is they really don't have anyone inhouse that is an expert with this stuff, so various people (contractors?) tinker with individual issues without an overall picture of things and with an impact analysis. You only have to look at how double buffering is handled 15 different ways in the vcl for various controls. Then there's the overpainting that happens when controls are resized (the whole form is repainted for each control). 

 

https://quality.embarcadero.com/browse/RSP-30639

 

And the response to it has been laughable at best. They "fixed" the design issue by tinkering with one affected control (TMemo) but left the rest (citing the need for interface changes) - but now they expect us to enter separate issues for each affected control! I suspect that is so they can split the tasks up to different people... so we can get more of the same tinkering.  

 

Seriously, if you are not already using VCL Styles, then don't bother with it.

  • Thanks 1

Share this post


Link to post
Guest

as @Vincent Parrett pointed, my thesis on the need for a remaster of language itself is necessary. Otherwise, the fight will be eternal and will win the bug, as it is already known. or worse, the developer will continue to pay for a commercial beta ... without complaining!

 

hug

Edited by Guest

Share this post


Link to post
16 hours ago, pyscripter said:

The issue results from the TListView being recreated as a response to the CM_STYLECHANGED message

Good observation. And its quite strange that your workaround affects the issue, because:

  • In the demo you can switch between VCL Styles any times and, then, switch back to Windows style. Obviously, in this case CM_STYLECHANGED will be processed one or more times, but no bug will occurred an the end.
  • CM_STYLECHANGED does not received after form's close button click.

So, there should be some resulting side effect - a difference in the application's state with and without workaround. So, I've decided again to look at the child windows of the panel. The code:

 

var
  s: string;

function EnumProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall
var
  c: TWinControl;
begin
  c := FindControl(hwnd);
  if c = nil then
    s := s + 'nil'
  else
    s := s + c.ClassName;
  s := s + #13#10;
  Result := True;
end;

procedure TMainForm.Button1Click(Sender: TObject);
begin
  s := '';
  EnumChildWindows(ClientPanel.Handle, @EnumProc, 0);
  ShowMessage(s);
end;

 

With the system style:

image.png.d1926889a258159a8be5d3c9e96ec329.png

 

With VCL style:

image.png.0ebcb5f3d44660b19a6f60ed60160b5a.png

 

And with the workaround from @pyscripter:

image.png.818d30b21793c0ffccdcb0c2c05ec3b8.png

 

So, with workaround, we have at least different child windows order.

 

  • Like 2

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×