Recent

Author Topic: [SOLVED] Treeview flicker w. Hottrack when running inside NPP Plugin (dark mode)  (Read 8645 times)

d7_2_laz

  • Hero Member
  • *****
  • Posts: 626
Oh thanks 440bx. As this did remind me on the old MS tool Spy++, i did a quick check with this, and indeed .. 'Window' (see screenshot) (independent from if form or any control are focused).
So, one step forward. The If-code will be executed. But it hasn't any effect yet.
The current value for hbrBackground is 0, that might possibly not be correct. (?)
For comparison, lpszClassName (before assignment, only for inspecting:) is 'Window', so this matches the really existing value.
Maybe the renewed 'RegisterClassEx' is an inappropriate call for to update a elass information ?
Lazarus 3.8  FPC 3.2.2 Win10 64bit

440bx

  • Hero Member
  • *****
  • Posts: 5010
It looks like setting the class background brush to a null brush does not "fit" well in the LCL.

Since I know next to nothing about the LCL, I have nothing else to offer.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 626
Ok 440bx, but i'd like to remark that there might be also some other reasons possible:
- The subclassing NPP theme layer had already picked up the ownership of the drawing
- the null_brush works for the form, but this is not noticeable (no form's flicker before, and not after). But the treeview flicker continues  because it's Invalidates causes control's repaint in light
- RegisterClassEx is not designed for/not able to have an update capability
- Within FormCreate it is done too late
For to come nearer, some checks:
Code: Pascal  [Select][+][-]
  1.   wnd.hCursor := Windows.LoadCursor(0, IDC_WAIT); // This should directly be visible on the form ..
  2.   anAtom := Windows.RegisterClassEx(wnd);
No effect. 'anAtom' is 0. It should contain a unique id for the class, so '0' is probably wrong.
That does mean imo, no modification of the class attributes had happened. So it's not likely it's about the NULL_BRUSH only.

Next checks:
- try to do it at an earlier stage (CreateWnd):

Code: Pascal  [Select][+][-]
  1.   protected
  2.     procedure CreateParams(var Params: TCreateParams); override;
  3.     procedure CreateWnd; override;
  4.  
  5. procedure THelloWorldDockingForm.CreateWnd;
  6. var wnd: Windows.TWNDClassEx; szClassName: String; anAtom: ATOM;
  7. begin
  8.   inherited CreateWnd;
  9.   szClassName := 'Window';
  10.   if GetClassInfoEx(hInstance, PChar(szClassName), @wnd) then
  11.   begin
  12.     wnd.hCursor := Windows.LoadCursor(0, IDC_WAIT);
  13.     anAtom := Windows.RegisterClassEx(wnd); // After that, 'anAtom' is 0.
  14.   end;
  15. end;

No change of the cursor. - Now, an attempt using CreateParams, where one can influence styles:

Code: Pascal  [Select][+][-]
  1. procedure THelloWorldDockingForm.CreateParams(var Params: TCreateParams);
  2. begin
  3.   //inherited CreateParams(Params);
  4.   with Params do begin
  5.        //Style := Style or WS_BORDER;
  6.        //ExStyle := WS_EX_TOOLWINDOW or WS_EX_TOPMOST;
  7.        //WindowClass.Style := CS_SAVEBITS;
  8.        WindowClass.hbrbackground := HBRUSH(GetStockObject(NULL_BRUSH));;
  9.        WindowClass.hCursor := Windows.LoadCursor(0, IDC_WAIT);
  10.   end;
  11.   inherited CreateParams(Params);
  12. end;

No wait cursor for the form, and no reduction of the treeview's flicker.

I'll give up this way and go back to observe if i see side-effects of the proposed code change in TCustomTreeView.UpdateHotTrack next days.
« Last Edit: December 21, 2024, 11:49:07 am by d7_2_laz »
Lazarus 3.8  FPC 3.2.2 Win10 64bit

440bx

  • Hero Member
  • *****
  • Posts: 5010
I see a problem in the code you showed, the size field of the wndclassex structure has not been initialized as required. That will cause the registration to fail.

see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa for details.

HTH.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 626
Ok, good point!, as they say "set this member before calling the GetClassInfoEx function"
So:
Code: Pascal  [Select][+][-]
  1.   szClassName := 'Window';
  2.   wnd.cbSize := SizeOf(TWndClassEx);
  3.   if GetClassInfoEx(hInstance, PChar(szClassName), @wnd) then begin
  4.      wnd.hbrBackground := HBRUSH(GetStockObject(NULL_BRUSH));
  5.      wnd.hCursor := Windows.LoadCursor(0, IDC_WAIT);
  6.      anAtom := Windows.RegisterClassEx(wnd);
  7.   end;
-> anAtom stays '0'; no wait cursor to see  ..

For to see, if the WinApi demo program 'StretchBlt' would allow at all to do a second call of RegisterClassEx, i did the following check - > see screenshot.
Lazarus 3.8  FPC 3.2.2 Win10 64bit

440bx

  • Hero Member
  • *****
  • Posts: 5010
I've never tried registering a class more than once.  Windows probably considers that an error, you should use GetLastError to find out why the call failed.

Also, once a class is registered, changing any of its "characteristics" is done using SetClassLong.  Read about it at https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslonga

You should use SetClassLongPtr but read the above linked page first.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 626
With GetLastError I was just about   :)  -> image.  -  As expected ...

'SetClassLongPtrW' might be a promising pointer, possibly something like:

Code: Pascal  [Select][+][-]
  1.     //SetClassLongPtrW(hInstance, GCL_HBRBACKGROUND, 0);
  2.     //SetClassLongPtrW(Self.handle, GCL_HBRBACKGROUND, 0);
  3.     //SetClassLongPtrW(Treeview1.handle, GCL_HBRBACKGROUND, 0);
  4.     //SetClassLongPtrW(Self.Handle, GCL_HBRBACKGROUND, COLOR_BACKGROUND);
  5.     //SetClassLongPtrW(TreeView1.Handle, GCL_HBRBACKGROUND, COLOR_BACKGROUND);
  6.  
  7.     SetClassLongPtrW(hInstance, GCL_HBRBACKGROUND, HBRUSH(GetStockObject(NULL_BRUSH)));
  8.     SetClassLongPtrW(Self.Handle, GCL_HBRBACKGROUND, HBRUSH(GetStockObject(NULL_BRUSH)));
  9.     SetClassLongPtrW(Treeview1.handle, GCL_HBRBACKGROUND, HBRUSH(GetStockObject(NULL_BRUSH)));
  10.     // or:
  11.     SetClassLongPtrW(Treeview1.handle, GCL_HBRBACKGROUND, LONG_PTR(GetStockObject(NULL_BRUSH)));

(I remember i had used this once, ages ago, but only for the light theme, it hadn't played very well together with metadarkstyle)

Within the plugin demo dockingform, dark, it has no visible effect for me.
Lazarus 3.8  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 626
Now back to the code change proposal.
Due to daily practice i've improved it a bit, as that's better for a rare use case where it should painted although the cursor is in a line in a x-pos right after a node's caption. This one would not be fully fetched by:
Code: Pascal  [Select][+][-]
  1.   FNodeUnderCursor := GetNodeAt(X, Y);
If the cursor is after ending of the node's caption, a redraw won't be triggered.
That should happen also when the cursor is within a line, not only within a caption text.
But: principle is: i never wont't touch FNodeUnderCursor itself, for not to touch the used behaviour of drag & drop, no way.
So, a local variable is needed.
Compared with the situation before (Invalidate all) it doesn't play a role at all. It's simply a little bit more where InvalidateRect will be triggered.

Another point: one may argue: and what's about MultiSelect? Ok, then ('for paranoia') do conventional behavior: Invalidate.
Otherwise, please note: possibly only two lines are affected, Where the cursor is, and where he was.

Code: Pascal  [Select][+][-]
  1. procedure TCustomTreeView.UpdateHotTrack(X, Y: Integer);
  2. var aNode, nodeUnderCursorY: TTreeNode; R: TRect; previdx: Integer;
  3. begin
  4. .......
  5.   FNodeUnderCursor := GetNodeAt(X, Y);
  6. .......
  7.   // Invalidate;    // Not so global please - invalidae only the affected lines (at least if not MultiSelect)
  8.  
  9.   if Self.MultiSelect then
  10.     Invalidate
  11.   else begin
  12.     nodeUnderCursorY := GetNodeAtY(Y);  // Instead of GetNodeAt(X, Y) FNodeUnderCursor. Don't want to touch FNodeUnderCursor itself!
  13.     previdx := FHotTrackedIdxPrevNode;
  14.     if FHotTrackedIdxPrevNode > -1 then begin
  15.       aNode := Items[FHotTrackedIdxPrevNode];
  16.       if (Assigned(aNode) And aNode.Visible) then begin
  17.         R := aNode.DisplayRect(False);
  18.         InvalidateRect(Handle, @R, True);
  19.       end;
  20.       FHotTrackedIdxPrevNode := -1;
  21.     end;
  22.     if Assigned(nodeUnderCursorY) then begin
  23.       R := nodeUnderCursorY.DisplayRect(False);
  24.       if not (previdx = nodeUnderCursorY.AbsoluteIndex) then // Not call InvalidateRect twice for the same item
  25.         InvalidateRect(Handle, @R, True);
  26.       FHotTrackedIdxPrevNode := nodeUnderCursorY.AbsoluteIndex;
  27.     end;
  28.   end;
  29.  
« Last Edit: December 22, 2024, 11:29:03 am by d7_2_laz »
Lazarus 3.8  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 626
I'd opened an item in the bugtracker
https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/41290
and a lately found little update preventing an additional condition about "not call InvalidateRect twice for the same item".
Lazarus 3.8  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 626
Lazarus 3.8  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 626
It's in Fixes_4 and so either in an 4.0 RC3 or 4.0.
Lazarus 3.8  FPC 3.2.2 Win10 64bit

 

TinyPortal © 2005-2018