Recent

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

d7_2_laz

  • Hero Member
  • *****
  • Posts: 645
This code looks OK, so only the testing (with mousewheel too) will show is it correct or not...

PS. Indent = 3 spaces? weird. 2 spaces better.

Did that intensively with my standalone-app, where i had implemented the customdraws for that.
Any other tester would of course be very welcome. -> One or the other should use "HotTrack" in his code, right?

Back to the NPP plugin: flicker is (nearly (*)) fully solved here, as mentioned.

rdipardo had added a treeview demo in his template yesterday (and i could spend a demo test project with that, as noted at the beginning here). But there's no special custom draw dealing with hover herein. It covers only painting of the background, expand sign, and item text correctly, and with the update also the scrollbars themed. So it's no good test area for this special thing 'hovering' here.
In my plugin i'm about to convert however there is this hovering mechanism, and it behaves fine, just as in the stand-alone app.

(*) Let's say flicker shrinks from 90% to 2%, as there is a very very little painting movement - only in the plugin! - one still can see when hovering an item. A bit more than in the stand-alone app. But nothing compared to the original situation.

Indentation: changed to 2 spaces.

I'd like to propose it in the bug-tracker. Where imo it's not a bug at all of course. We're speaking about a bit of more support when entering a still more or less unexplored territory. A little fine-tuning.
What do you think?
Maybe i'll do some more tests about "avoid InvalidateRect twice for the same item" though before; i'll see.
But in any case, I will keep a close eye on it in daily use to see if it proves effective before, for a week.

A word about the origin of the topic: imo a dark theme is a proof that mercilessly exposes any weakness of a code,
any unhandled white areas / erasures within a dark theme are directly visible. And the more if it's in a plugin.
There's a theme code, here by NPP via OS calls, the 'host application's' own drawsings, and the user's own custom drawing on top as a third layer. - Very demanding.
Lazarus 4.2  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 645
In my equality query in reply #6 - for not to call InvalidateRect twice for the same item - i did a mistake on the fly, having used "Index" instead of "AbsoluteIndex". My bad ... sorry!
So now it behaves without abnormalies with both mousewheel flavours:

Code: Pascal  [Select][+][-]
  1. procedure TCustomTreeView.UpdateHotTrack(X, Y: Integer);
  2. var aNode: TTreeNode; R: TRect; previdx: Integer;   // rTop: Integer
  3. begin
  4. .....
  5.   //Invalidate;
  6.   previdx := FHotTrackedIdxPrevNode;
  7.   if FHotTrackedIdxPrevNode > -1 then begin
  8.     aNode := Items[FHotTrackedIdxPrevNode];
  9.     if (Assigned(aNode) And aNode.Visible) then begin
  10.       R := aNode.DisplayRect(False);
  11.       InvalidateRect(Handle, @R, True);
  12.     end;
  13.     FHotTrackedIdxPrevNode := -1;
  14.   end;
  15.   if Assigned(FNodeUnderCursor) then begin
  16.     R := FNodeUnderCursor.DisplayRect(False);
  17.     if not (previdx = FNodeUnderCursor.AbsoluteIndex) then // Not call InvalidateRect twice for the same item
  18.       InvalidateRect(Handle, @R, True);
  19.     FHotTrackedIdxPrevNode := FNodeUnderCursor.AbsoluteIndex;
  20.   end;

Now back to the original topic:
as illustration i add two short videos for to show
- the behaviour before
(just added a shelltreeview to rdiparos's demo. Both actually have only a rudimentary painting. Eg. no icons, no hovering. It should only demonstrate the original flicker topic)
- and after the change the work-in-progress component flicker-free within the plugin.
« Last Edit: December 19, 2024, 10:26:05 am by d7_2_laz »
Lazarus 4.2  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 645
The second one separately due to size restricion
Lazarus 4.2  FPC 3.2.2 Win10 64bit

AlexTP

  • Hero Member
  • *****
  • Posts: 2615
    • UVviewsoft
Thanks. Looks good.
Offtopic: I see issue on Windows 10 with CudaText with dark theme: first painting of UI makes flicker - it shows some white areas, then CudaText dark theme is applied. Flicker of white areas. Did you see it in your project with NPP?

d7_2_laz

  • Hero Member
  • *****
  • Posts: 645
Hello Alex,
no, not especially with this NPP plugin.
But yes, meanwhile it's in general a very well-known terrain to me. Don't know your details here, but there might be a bunch of measures, some also proposed to metadarkstyle (had previously some issues here, see e.g. for flicker in StatusBar):

Basically it' s so that the OS or widgetset might paint the background in light, then comes the dark theme painting, and the component that might paint too. Every light painting that stays a while will be directly noticed as flicker, promised! Especially if a process needs time until it starts.

- Intercept the WM_ERASEBKGND eg. in message loop  resp. WMEraseBackground message, assure to paint dark bg here (instead of let others do white painting) and return True in message loop
- Maybe defer long operations triggered in WM_CREATE to a phase directly afterwards, so that other controls have a chance to build first. Or your control, with somehow deferred generation of it's contents
- Assure that containers(!) of the control, TPanel&Co, will be erased in dark too (easy example on app level below), not focus only the control itself
- Check Invalidates that are too global  ;-)
- I assume that well-known things like property DoubleBuffered, LockWindowUpdate, BeginUpdate/EndUpdate are already checked
- For some base components like TreeView ListView, others might have equivalents, it might be worthy to try out ovrrride possibilities at early creation stage, eg. in CreateWnd, maybe other window styles, SendMessages etc.
  Caution, some measure might speedup in light mode, but might not be good for dark mode, and vice versa
  So one might have something like:
Code: Pascal  [Select][+][-]
  1. procedure <TSomeListView>.CreateWnd;
  2. var Style: Dword;
  3. begin
  4.   inherited CreateWnd;
  5.   ControlStyle := ControlStyle + [csOpaque];   //  Add
  6. ,,.
  7.   if HandleAllocated then begin
  8.      If Not IsDarkModeEnabled then begin
  9.         Style := ListView_GetExtendedListViewStyle(Handle);
  10.         Style := Style or LVS_EX_DOUBLEBUFFER;
  11.         ListView_SetExtendedListViewStyle(Handle, Style);
  12.      end else begin
  13. ...
  14.     end;
  15. ...

--- Easy examples for overriding WMEraseBgnd on app level for standard (not subclassed) controls:

Code: Pascal  [Select][+][-]
  1. // For dark mode flickering
  2.   TSplitter = class(ExtCtrls.TSplitter)
  3.     procedure WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
  4.   end;
  5.   TPanel = class(ExtCtrls.TPanel)
  6.     procedure WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
  7.   end;
  8.   TEdit = class(StdCtrls.TEdit)     // Fix dark mode white flicker in a very long entry field
  9.    procedure WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
  10.   end;
  11.  
  12. // For dark mode flickering
  13. procedure TSplitter.WMEraseBkgnd(var Message: TLMEraseBkgnd);
  14. var vRect: TRect;
  15. begin
  16.   if Not IsDarkModeEnabled then
  17.      exit;
  18.   if Message.DC = 0 then Exit;
  19.   Brush.Color := $00353535;
  20.   vRect := Rect(0, 0, Width, Height);
  21.   FillRect(Message.DC, vRect, Brush.Reference.Handle);
  22. end;
  23.  
  24. // Drawback here: generates flicker at Resize. So, achieve via a flag (in Tag) that this will only be executed in the starting phase
  25. procedure TPanel.WMEraseBkgnd(var Message: TLMEraseBkgnd);
  26. var vRect : TRect;
  27. begin
  28.   if Not IsDarkModeEnabled then
  29.      exit;
  30.   if Not (Self.Tag = -1) then  // Values to be set somewhere else, at me eg. initialized in FormCreate and reset in TFormXYZ.AppIdle
  31.      exit;
  32.   if Message.DC = 0 then Exit;
  33.   Brush.Color := $00353535;
  34.   vRect := Rect(0, 0, Width, Height);
  35.   FillRect(Message.DC, vRect, Brush.Reference.Handle);
  36. end;
  37.  
  38. // Drawback here: generates flicker at Resize. So, achieve via a flag (in Tag) that this will only be executed in the starting phase
  39. procedure TEdit.WMEraseBkgnd(var Message: TLMEraseBkgnd);
  40. var vRect : TRect;
  41. begin
  42.   if Not IsDarkModeEnabled then
  43.      exit;
  44.   if Not (Self.Tag = -1) then  // Values to be set somewhere else, at me eg. initialized in FormCreate and reset in TFormXYZ.AppIdle
  45.      exit;
  46.   if Message.DC = 0 then Exit;
  47.   Brush.Color := $00353535;
  48.   vRect := Rect(0, 0, Width, Height);
  49.   FillRect(Message.DC, vRect, Brush.Reference.Handle);
  50. end;

Does one of those things somehow influences?
« Last Edit: December 19, 2024, 07:35:42 pm by d7_2_laz »
Lazarus 4.2  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 645
Oh, just looked ... in my pagecontrrol unit, where a pagecontrol and your attabs are in, i have:

Code: Pascal  [Select][+][-]
  1.   TPageControl = class(ComCtrls.TPageControl)
  2.       procedure WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
  3.   end;
  4.  
  5.   TATTabs = class(attabs.TATTabs)
  6.       procedure WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
  7.   end;
  8.  
  9. procedure TPageControl.WMEraseBkgnd(var Message: TLMEraseBkgnd);
  10. var vRect : TRect;
  11. begin
  12.   if Not IsDarkModeEnabled then
  13.      exit;
  14.   if Message.DC = 0 then Exit;
  15.   if FFirsttime then
  16.      FFirsttime := False
  17.   else
  18.      exit;
  19.   Brush.Color := $00353535;
  20.   vRect.Left := 0;
  21.   vRect.Top := 0;
  22.   vRect.Width := Width;
  23.   vRect.Height := Height;
  24.   FillRect(Message.DC, vRect, Brush.Reference.Handle);
  25. end;
  26.  
  27. procedure TATTabs.WMEraseBkgnd(var Message: TLMEraseBkgnd);
  28. var vRect : TRect;
  29. begin
  30.   if Not IsDarkModeEnabled then
  31.      exit;
  32.   if Message.DC = 0 then Exit;
  33.   Brush.Color := $00353535;
  34.   vRect.Left := 0;
  35.   vRect.Top := 0;
  36.   vRect.Width := Width;
  37.   vRect.Height := Height;
  38.   FillRect(Message.DC, vRect, Brush.Reference.Handle);
  39. end;

« Last Edit: December 19, 2024, 07:36:08 pm by d7_2_laz »
Lazarus 4.2  FPC 3.2.2 Win10 64bit

AlexTP

  • Hero Member
  • *****
  • Posts: 2615
    • UVviewsoft
Erasebkgnd: I am almost suceeded! Added LM_ERASE..... handler to ATSynEdit and I see initial repaint does flicker with green (testing color in handler).
Thanks!

I also modified WM_ERASE.... handler in ATTabs so now you don't need your workaround.
« Last Edit: December 19, 2024, 06:45:18 pm by AlexTP »

d7_2_laz

  • Hero Member
  • *****
  • Posts: 645
Thanks for feedback Alex, please tell if it has good effects at the end.

Good test with color green (case there are some nested containers interfering .. which one might be responsible?)

For the "WMEraseBkgnd" examples, i just added a (needed) code comment above, where i had set the "Self.Tag" flag here. I have:  set in FormCreate, changed in AppIdle. This could be interesting when dealing with problems exclusively in the start phase. In FormActivate is was not always helpful. - Example:

Code: Pascal  [Select][+][-]
  1. procedure TFormXYZ.FormCreate(Sender: TObject);
  2. begin
  3.     if IsDarkModeEnabled then begin
  4.        efDirOut.Tag := -1;  // Flag to process WMEraseBkgnd
  5.        PanelUpper.Tag := -1;
  6.        Application.OnIdle := AppIdle;
  7.     end;
 

And sometimes the procedure was good for the start phase, but counterproductive for Resize, window Maximize, return from the task bar etc. A question of testing ...

And thanks for the info about the change in ATTabs!
« Last Edit: December 19, 2024, 08:22:26 pm by d7_2_laz »
Lazarus 4.2  FPC 3.2.2 Win10 64bit

440bx

  • Hero Member
  • *****
  • Posts: 5805
I just wanted to mention that sometimes it is simpler (and cheaper code-wise) to simply set the background brush (which is used to erase the background) to a NULL_BRUSH.

Basically this setting allows erasing the background  without erasing the background since the NULL_BRUSH has no effect on the background.

disclaimer: I don't know how well this will work in the LCL but works very nicely when using just the WinApi.

HTH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 645
Interesting 440bx! But wouldn't that mean that potentially any other unwanted color would remain for a while in the target area? Maybe you could point to some example snippet.

Btw i realized that i had been somehow unprecise that i had not returned a message result in callbacks using LM_ERASEBKGND instead of WM_ERASEBKGND (here it is needed).
In theory this return value (1 resp. true) is a Must (meaning: job done, don't do it again) for the purpose.
Afair in callbacks of 'Message: TLMEraseBkgnd' it hadn't make a difference in practice and so i had negelected it. Not sure yet, would need to re-dig into.
Maybe anybody knows.
Maybe i was wrong and that had been the reason why i had, in a few cases, some Resize flicher issues to handle separately.
Anyhow: to my experience those measures sum up to a better appearance.
« Last Edit: December 20, 2024, 10:27:58 am by d7_2_laz »
Lazarus 4.2  FPC 3.2.2 Win10 64bit

440bx

  • Hero Member
  • *****
  • Posts: 5805
Interesting 440bx! But wouldn't that mean that potentially any other unwanted color would remain for a while in the target area? Maybe you could point to some example snippet.
You'll find fully working example code at
https://forum.lazarus.freepascal.org/index.php/topic,53791.msg398393.html#msg398393
The examples BitBlt, StretchBlt and SelectClipRgn (in the following post) all use a NULL_BRUSH.

Using a NULL_BRUSH should be equivalent to not clearing the background.  IOW, whatever was in the background  remains there after being "erased" with a NULL_BRUSH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 645
Thank you! I'll see if i can a test next within the given context.
Lazarus 4.2  FPC 3.2.2 Win10 64bit

440bx

  • Hero Member
  • *****
  • Posts: 5805
You're welcome.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 645
... but couldn't bring such easily to work.
The examples are constructed as 'traditional' Win-programs based on WinMain, CreateWindowEx, WndProc etc.
But is it possible, and how would it be possible within a FormCreate to modify the background brush handle of the form, better: a control ?

Code: Pascal  [Select][+][-]
  1. procedure THelloWorldDockingForm.FormCreate(Sender: TObject);
  2. var .......  wnd: Windows.TWNDClassEx; szClassName: String;
  3. begin
  4. ....
  5.   szClassName := Application.ClassName;  // 'TApplication'
  6.   if GetClassInfoEx(hInstance, PChar(szClassName), @wnd) then
  7.   //szClassName := 'THelloWorldDockingForm';
  8.   //if GetClassInfoEx(hInstance, PChar(szClassName), @wnd) then
  9.   //if GetClassInfoEx(Self.Handle, PChar(szClassName), @wnd) then
  10.   //szClassName := 'TTreeView';
  11.   //if GetClassInfoEx(TreeView1.Handle, PChar(szClassName), @wnd) then
  12.   begin
  13.     // Keep most properties untouched, let them as Get'ed
  14.     wnd.hbrBackground := HBRUSH(GetStockObject(NULL_BRUSH));
  15.     Windows.RegisterClassEx(wnd);  // Possible as update? To rewrite slightly modified information?
  16.   end;
  17. ....
 
Doesn't succeed, GetClassInfoEx returns False, for form or control. No right idea yet. Maybe there are more correct approaches ...

Besides that: somebody may wonder, why doesn't he apply the TWMEraseBkgnd interception technique to the treeview?
That's already done. But has no effect here (in difference to a stand-alone exe): the code won't never be called.
I would explain it by the fact that the class is already subclassed ("catched") by the Notepad++ plugin theme that takes over responsibility for (dark) background painting
Code: Pascal  [Select][+][-]
  1.    SendMessage(Npp.NppData.NppHandle, NPPM_DARKMODESUBCLASSANDTHEME, DmFlag, Self.TreeView1.Handle);
And so, for instance, i could, in a WndProc, breakpoint on WM_MOUSEWHEEL, but not on WM_ERASEBKGND. Normally, within other contexts, or the metadarkstyle layer, it has a good effect to proceed so, but not here.
In the NPP plugin it doesn't seem to work.

So:  as of now the most promising candidate seems to be for me the proposed code change in TCustomTreeView.UpdateHotTrack  ...
« Last Edit: December 20, 2024, 10:26:32 pm by d7_2_laz »
Lazarus 4.2  FPC 3.2.2 Win10 64bit

440bx

  • Hero Member
  • *****
  • Posts: 5805
Now I remember the LCL does some "unusual" things with windows and that is likely the reason getting the class info doesn't work as expected.

The LCL seems to often use a generic class named "Window" to route message processing to a generic window proc.

The following example code gets the class name from any visible window. 
Code: Pascal  [Select][+][-]
  1. {$APPTYPE        GUI}
  2.  
  3. {$LONGSTRINGS    OFF}
  4. {$WRITEABLECONST ON}
  5. {$DESCRIPTION    'Win32 API function - GetClassName example'}
  6.  
  7. // {$R GetClassName.Res}  { left out }
  8.  
  9. program _GetClassName;
  10.   { Win32 API function - GetClassName example                                 }
  11.  
  12. uses Windows, Messages, Resource, SysUtils;
  13.  
  14. const
  15.   AppNameBase  = 'GetClassName Example';
  16.  
  17.   {$ifdef WIN64}
  18.     Bitness64  = ' - 64bit';
  19.     AppName    = AppNameBase + Bitness64;     { NOTE: also used as class name }
  20.   {$else}
  21.     Bitness32  = ' - 32bit';
  22.     AppName    = AppNameBase + Bitness32;
  23.   {$endif}
  24.  
  25.   AboutBox     = 'AboutBox';
  26.   APPICON      = 'APPICON';
  27.   APPMENU      = 'APPMENU';
  28.  
  29. {-----------------------------------------------------------------------------}
  30.  
  31. {$ifdef VER90} { Delphi 2.0 }
  32. type
  33.   ptrint  = longint;
  34.   ptruint = dword;
  35. {$endif}
  36.  
  37. {-----------------------------------------------------------------------------}
  38.  
  39. function About(DlgWnd : hWnd; Msg : UINT; wParam, lParam : ptrint)
  40.          : ptrint; stdcall;
  41. begin
  42.   About := ord(TRUE);
  43.  
  44.   case msg of
  45.  
  46.     WM_INITDIALOG: exit;
  47.  
  48.     WM_COMMAND:
  49.     begin
  50.       if (LOWORD(wParam) = IDOK) or (LOWORD(wParam) = IDCANCEL) then
  51.       begin
  52.         EndDialog(DlgWnd, ord(TRUE));
  53.  
  54.         exit;
  55.       end;
  56.     end;
  57.   end;
  58.  
  59.   About := ord(FALSE);
  60. end;
  61.  
  62. {-----------------------------------------------------------------------------}
  63.  
  64. procedure DrawWindowFrame(Wnd : HWND);
  65.   { Draws a frame around the parameter Wnd                                    }
  66. var
  67.   dc         : HDC;
  68.   WindowRect : TRECT;
  69.  
  70.   Pen        : HPEN;
  71.   OldPen     : HPEN;
  72.  
  73. begin
  74.   { a 5 pixel wide pen is a reasonable choice. Some windows are "tucked" under}
  75.   { other child windows and a thin frame won't be visible because it falls    }
  76.   { in the "tucked" area.                                                     }
  77.  
  78.   Pen := CreatePen(PS_INSIDEFRAME, 5, RGB(255, 0, 255));
  79.  
  80.   GetWindowRect(Wnd, WindowRect);              { the window rectangle         }
  81.  
  82.   {---------------------------------------------------------------------------}
  83.   { convert the coordinates in WindowRect to be relative to the upper left    }
  84.   { corner of the window.  At this time they are relative to the upper left   }
  85.   { corner of the screen.  After the conversion the (Left, Top) coordinate in }
  86.   { WindowRect will be (0, 0) which matches the preset (Left, Top) coordinate }
  87.   { the window dc.                                                            }
  88.  
  89.   with WindowRect do OffsetRect(WindowRect, - Left, - Top);
  90.  
  91.   {---------------------------------------------------------------------------}
  92.   { we need a dc that doesn't clip the output to the client area and that can }
  93.   { be used to update a locked window (the window to be framed is locked).    }
  94.  
  95.   dc :=  GetDCEx(Wnd,
  96.                  0,                      { no region                          }
  97.                  DCX_WINDOW       or
  98.                  DCX_CACHE        or
  99.                  DCX_EXCLUDERGN   or     { excludes nothing because region = 0}
  100.                  DCX_CLIPSIBLINGS or
  101.                  DCX_LOCKWINDOWUPDATE);
  102.  
  103.   { select the pen and the brush used by the Rectangle API                    }
  104.  
  105.   OldPen := SelectObject(dc, Pen);
  106.   SelectObject(dc, GetStockObject(NULL_BRUSH));  { only the frame gets drawn  }
  107.  
  108.   { select a raster op that causes the original pixels to be restored when the}
  109.   { rectangle is drawn the second time.                                       }
  110.  
  111.   SetROP2(dc, R2_NOTXORPEN);
  112.  
  113.   {---------------------------------------------------------------------------}
  114.   { draw a frame around (inside) the window rectangle                         }
  115.  
  116.   with WindowRect do
  117.   begin
  118.     Rectangle(dc, Left, Top, Right, Bottom);
  119.   end;
  120.  
  121.   SelectObject(dc, OldPen);          { restore the original pen               }
  122.   ReleaseDC(Wnd, dc);
  123.   DeleteObject(Pen);                 { get rid of the pen                     }
  124.  
  125.   {---------------------------------------------------------------------------}
  126.   { release the window dc                                                     }
  127.  
  128.   ReleaseDC(dc, Wnd);
  129. end;
  130.  
  131. {-----------------------------------------------------------------------------}
  132.  
  133. function WndProc (Wnd : hWnd; Msg : UINT; wParam, lParam : ptrint)
  134.          : ptrint; stdcall;
  135.   { main application/window handler function                                  }
  136. const
  137.   GetClassName_Call
  138.    = 'GetClassName (Wnd : HWND; NameBuf : pchar; BufSize : integer) : integer;';
  139.  
  140.   FramedWindow         : hWnd = 0;
  141.  
  142.   Tracking             : BOOL = FALSE;
  143.  
  144.   Hint = 'Press the left mouse button - here - then move the mouse around';
  145.  
  146. var
  147.   ps                 : TPAINTSTRUCT;
  148.  
  149.   ClientRect         : TRECT;
  150.  
  151.   Buf                : packed array[0..511] of char;
  152.   ClassName          : packed array[0..255] of char;
  153.  
  154.   TextSize           : TSIZE;
  155.  
  156.   MouseOnWindow      : HWND;
  157.   TopWindow          : HWND;
  158.   MousePt            : TPOINT;
  159.  
  160. begin
  161.   WndProc := 0;
  162.  
  163.   case Msg of
  164.     WM_LBUTTONDOWN:
  165.     begin
  166.       {-----------------------------------------------------------------------}
  167.       { capture the mouse to make sure we always get the button up which      }
  168.       { is the signal to refresh the client area.                             }
  169.  
  170.       SetCapture(Wnd);
  171.  
  172.       {-----------------------------------------------------------------------}
  173.       { if the window is partially covered by another window (like a menu)    }
  174.       { we want to make sure the window is fully uncovered before we draw     }
  175.       { the frame.  We do this using SetWindowPos and UpdateWindow.           }
  176.  
  177.       SetWindowPos(Wnd,
  178.                    HWND_TOPMOST,
  179.                    0, 0, 0, 0,
  180.                    SWP_NOMOVE or SWP_NOSIZE or SWP_DRAWFRAME);
  181.       UpdateWindow(Wnd);
  182.  
  183.       { it should be ok to draw the window frame now                          }
  184.  
  185.       DrawWindowFrame(Wnd);
  186.       FramedWindow := Wnd;
  187.  
  188.       Tracking := TRUE;                   { we are tracking the mouse         }
  189.  
  190.       InvalidateRect(Wnd, nil, TRUE);
  191.       exit;
  192.     end;
  193.  
  194.     WM_LBUTTONUP:
  195.     begin
  196.       {-----------------------------------------------------------------------}
  197.       { Note that using "if GetCapture = Wnd" to find out if we are           }
  198.       { tracking the mouse can be a source of problems.  In some instances    }
  199.       { Windows (thru DefWindowProc) will capture the mouse for us, so        }
  200.       { having the mouse captured does not necessarily mean that we should    }
  201.       { draw or erase a frame.                                                }
  202.  
  203.       if Tracking then
  204.       begin
  205.         ReleaseCapture;                 { let the cat play with it            }
  206.  
  207.         DrawWindowFrame(FramedWindow);  { erase the frame                     }
  208.         FramedWindow := -1;
  209.         Tracking     := FALSE;
  210.         LockWindowUpdate(0);
  211.  
  212.         SetWindowPos(Wnd,
  213.                      HWND_NOTOPMOST,
  214.                      0, 0, 0, 0,
  215.                      SWP_NOMOVE or SWP_NOSIZE or SWP_DRAWFRAME);
  216.  
  217.         InvalidateRect(Wnd, nil, TRUE); { redraw the client area              }
  218.       end;
  219.  
  220.       exit;
  221.     end;
  222.  
  223.     WM_MOUSEMOVE:
  224.     begin
  225.       { if we are not tracking the mouse then there's nothing to do           }
  226.  
  227.       if not Tracking then exit;
  228.  
  229.       { we don't use the coordinates stored in the lParam because they are    }
  230.       { in client coordinates and may not reflect the current position of     }
  231.       { the mouse if the user moved it after this message was received.       }
  232.  
  233.       GetCursorPos(MousePt);
  234.  
  235.       { get the handle of the window under the cursor                         }
  236.  
  237.       MouseOnWindow := WindowFromPoint(MousePt);
  238.  
  239.       if MouseOnWindow = FramedWindow then exit;  { previously framed         }
  240.  
  241.       { The mouse is on a new window. Erase the previous frame                }
  242.  
  243.       DrawWindowFrame(FramedWindow);
  244.       LockWindowUpdate(0);             { unlock it                            }
  245.       UpdateWindow(FramedWindow);      { let any pending updates thru         }
  246.  
  247.       {-----------------------------------------------------------------------}
  248.       { check that the window handle obtained is valid.  Just in case this    }
  249.       { is one of these windows that "come and go" (timed popups and such)    }
  250.  
  251.       { following comment applies to Win9x only:                              }
  252.  
  253.       { NOTE: strictly speaking we should acquire the Win16Mutex to make      }
  254.       {       sure that the window isn't going to disappear before we         }
  255.       {       attempt to frame it.                                            }
  256.  
  257.       if not IsWindow(MouseOnWindow) then
  258.       begin
  259.         FramedWindow := -1;
  260.  
  261.         exit;
  262.       end;
  263.  
  264.       { draw the frame around the window. Because we did not acquire the      }
  265.       { Win16Mutex there is a _very_ slim chance that the window handle       }
  266.       { may no longer be valid.  We'll live with that possibility for this    }
  267.       { example.                                                              }
  268.  
  269.       { tell the window to update itself before we lock it.  This prevents    }
  270.       { framing half painted windows. Unfortunately this produces flicker     }
  271.       { when the mouse is on windows that paint themselves periodically       }
  272.       { such as the System Monitor.  The flicker can be eliminated by         }
  273.       { always locking the Top Level window instead of the child window.      }
  274.  
  275.       TopWindow := MouseOnWindow;
  276.       while GetParent(TopWindow) <> 0 do TopWindow := GetParent(TopWindow);
  277.  
  278.       UpdateWindow(MouseOnWindow);
  279.  
  280.       if MouseOnWindow <> Wnd then LockWindowUpdate(TopWindow);   { lock it   }
  281.       DrawWindowFrame(MouseOnWindow);                             { frame it  }
  282.  
  283.       { we should release the Win16Mutex here if we had obtained it.          }
  284.       {-----------------------------------------------------------------------}
  285.  
  286.       { keep track of the currently framed window.                            }
  287.  
  288.       FramedWindow := MouseOnWindow;
  289.  
  290.       { update our display to reflect the window size of the new window       }
  291.  
  292.       InvalidateRect(Wnd, nil, TRUE);
  293.  
  294.       exit;
  295.     end;
  296.  
  297.     WM_PAINT:
  298.     begin
  299.       BeginPaint(Wnd, ps);
  300.  
  301.       GetClientRect(Wnd, ClientRect);
  302.  
  303.       SetBkMode(ps.hdc, TRANSPARENT);
  304.       SetTextAlign(ps.hdc, TA_CENTER or TA_BOTTOM);
  305.       SelectObject(ps.hdc, GetStockObject(ANSI_VAR_FONT));
  306.  
  307.       if Tracking then
  308.       begin
  309.         {---------------------------------------------------------------------}
  310.         { show the window handle the mouse is currently on                    }
  311.  
  312.         GetClassName(FramedWindow, ClassName, sizeof(ClassName));
  313.         StrFmt(Buf, 'Class Name: "%s"', [ClassName]);
  314.       end
  315.       else
  316.       begin
  317.         {---------------------------------------------------------------------}
  318.         { give the user a hint about what to do next                          }
  319.  
  320.         lstrcpy(Buf, Hint);
  321.       end;
  322.  
  323.       TextOut(ps.hdc,
  324.               ClientRect.Right   div 2,
  325.               ClientRect.Bottom  div 2,
  326.               Buf,
  327.               lstrlen(Buf));
  328.  
  329.       {-----------------------------------------------------------------------}
  330.       { draw the function call                                                }
  331.  
  332.  
  333.       SelectObject(ps.hdc, GetStockObject(DEFAULT_GUI_FONT));
  334.       lstrcpy(Buf, GetClassName_Call);
  335.  
  336.       { calculate the size of the output string                               }
  337.  
  338.       GetTextExtentPoint32(ps.hdc,
  339.                            Buf,
  340.                            lstrlen(Buf), TextSize);
  341.  
  342.       TextOut(ps.hdc,
  343.               ClientRect.Right div 2,
  344.               ClientRect.Bottom - TextSize.cy,
  345.               Buf,
  346.               lstrlen(Buf));
  347.  
  348.       {-----------------------------------------------------------------------}
  349.       { we're done painting                                                   }
  350.  
  351.       EndPaint(Wnd, ps);
  352.  
  353.       exit;
  354.     end;
  355.  
  356.     WM_COMMAND:
  357.     begin
  358.       case LOWORD(wParam) of
  359.         IDM_ABOUT:
  360.         begin
  361.           DialogBox(hInstance, ABOUTBOX, Wnd, @About);
  362.  
  363.           exit;
  364.         end; { IDM_ABOUT }
  365.  
  366.         IDM_EXIT:
  367.         begin
  368.           DestroyWindow(Wnd);
  369.  
  370.           exit;
  371.         end; { IDM_EXIT }
  372.       end; { case LOWORD(wParam) }
  373.     end; { WM_COMMAND }
  374.  
  375.     WM_DESTROY:
  376.     begin
  377.  
  378.       PostQuitMessage(0);
  379.  
  380.       exit;
  381.     end; { WM_DESTROY }
  382.   end; { case msg }
  383.  
  384.   WndProc := DefWindowProc (Wnd, Msg, wParam, lParam);
  385. end;
  386.  
  387. {-----------------------------------------------------------------------------}
  388.  
  389. function InitAppClass: WordBool;
  390.   { registers the application's window classes                                }
  391. var
  392.   cls : TWndClassEx;
  393.  
  394. begin
  395.   cls.cbSize          := sizeof(TWndClassEx);         { must be initialized   }
  396.  
  397.   if not GetClassInfoEx (hInstance, AppName, cls) then
  398.   begin
  399.     with cls do
  400.     begin
  401.       { cbSize has already been initialized as required above                 }
  402.  
  403.       style           := CS_BYTEALIGNCLIENT;
  404.       lpfnWndProc     := @WndProc;                    { window class handler  }
  405.       cbClsExtra      := 0;
  406.       cbWndExtra      := 0;
  407.       hInstance       := system.hInstance;            { qualify instance!     }
  408.       hIcon           := LoadIcon (hInstance, APPICON);
  409.       hCursor         := LoadCursor(0, idc_arrow);
  410.       hbrBackground   := GetSysColorBrush(COLOR_WINDOW);
  411.       lpszMenuName    := APPMENU;                     { Menu name             }
  412.       lpszClassName   := AppName;                     { Window Class name     }
  413.       hIconSm         := LoadImage(hInstance,
  414.                                    APPICON,
  415.                                    IMAGE_ICON,
  416.                                    16,
  417.                                    16,
  418.                                    LR_DEFAULTCOLOR);
  419.     end; { with }
  420.  
  421.     InitAppClass := WordBool(RegisterClassEx(cls));
  422.   end
  423.   else InitAppClass := True;
  424. end;
  425.  
  426. {-----------------------------------------------------------------------------}
  427.  
  428. Function WinMain : integer;
  429.   { application entry point                                                   }
  430. var
  431.   Wnd : hWnd;
  432.   Msg : TMsg;
  433. begin
  434.   if not InitAppClass then Halt (255);  { register application's class        }
  435.  
  436.   { Create the main application window                                        }
  437.  
  438.   Wnd := CreateWindowEx(WS_EX_CLIENTEDGE,
  439.                         AppName,                { class name                  }
  440.                         AppName,                { window caption text         }
  441.                         ws_Overlapped       or  { window style                }
  442.                         ws_SysMenu          or
  443.                         ws_MinimizeBox      or
  444.                         ws_ClipSiblings     or
  445.                         ws_ClipChildren     or  { don't affect children       }
  446.                         ws_visible,             { make showwindow unnecessary }
  447.                         50,                     { x pos on screen             }
  448.                         50,                     { y pos on screen             }
  449.                         400,                    { window width                }
  450.                         300,                    { window height               }
  451.                         0,                      { parent window handle        }
  452.                         0,                      { menu handle 0 = use class   }
  453.                         hInstance,              { instance handle             }
  454.                         Nil);                   { parameter sent to WM_CREATE }
  455.  
  456.   if Wnd = 0 then Halt;                         { could not create the window }
  457.  
  458.   while GetMessage (Msg, 0, 0, 0) do            { wait for message            }
  459.   begin
  460.     TranslateMessage (Msg);                     { key conversions             }
  461.     DispatchMessage  (Msg);                     { send to window procedure    }
  462.   end;
  463.  
  464.   WinMain := Msg.wParam;                        { terminate with return code  }
  465. end;
  466.  
  467. begin
  468.   WinMain;
  469. end.
Note that I left out the resource file (in line 7) but that should not affect how the program works.  With that code you'll at least know what the class name is.
Also to compile this code (GetClassName.lpr) tell Lazarus it is a "simple program" (don't use any other option.)
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018