Recent

Author Topic: How to fire an event, when form loses focus [SOLUTION, WIN ONLY]  (Read 401 times)

HuntingKashket

  • New Member
  • *
  • Posts: 33
  • I'm interested in upgrading everything
Hello, dear forum members!
Today i found a new bug in a user-written component (pointed to edit sizes), which made me enough crazy about this fact, to not use this component as is.
I've been very confused, when onDeactivate, onHide events and even WM_ACTIVATE/WM_ACTIVATEAPP messages gave me no result, so, i made this solution:

Code: Pascal  [Select]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, LMessages,
  9.   Windows;
  10.  
  11. type
  12.  
  13.   { TForm1 }
  14.  
  15.   TForm1 = class(TForm)
  16.   private
  17.     fOnUnFocus: TNotifyEvent;//event storage
  18.     lastMessage: UINT; //to avoid double calls
  19.     fOldWindowProc, fNewWindowProc: TWndMethod;//old and new WindowProcedures
  20.     procedure chkwnd(var TheMessage: TLMessage);//WindowProcedure(real)
  21.   public
  22.    property WindowProc: TWndMethod read fNewWindowProc write fNewWindowProc;
  23.    //Property for custom WindowProcedure, if you need to set it...
  24.    Constructor Create(TheOwner: TComponent); override;
  25.    //Constructor
  26.    Property onUnFocus: TNotifyEvent read fOnUnFocus write fOnUnFocus;
  27.    //Here the event stored
  28.   end;
  29.  
  30. var
  31.   Form1: TForm1;
  32.  
  33. implementation
  34.  
  35. {$R *.lfm}
  36.  
  37. { TForm1 }
  38. constructor TForm1.Create(TheOwner: TComponent);
  39. begin
  40.   Inherited; //Call form creating routines
  41.   fOldWindowProc := inherited WindowProc;//get the old WindowProcedure
  42.   Inherited WindowProc := @Self.chkwnd; //Set the real WindowProcedure to ours
  43. end;
  44.  
  45. procedure TForm1.chkwnd(var TheMessage: TLMessage);
  46. var I: integer;//iter
  47.     ForeGroundHWND: HWND;//it talks for itself
  48.     Call: boolean;//to be, or not to be? to do a call, or not to do a call?
  49. begin
  50.   if (TheMessage.msg = LM_CANCELMODE) and (lastMessage <> TheMessage.Msg) then
  51.      begin //If message is WindowMessage about focus cancelling
  52.      Call := true; //here we set the existential variable
  53.      ForeGroundHWND := GetForeGroundWindow;//Gets the current Top Window, ForeGround
  54.                     For I := 0 to Screen.FormCount-1 do //iterate throught forms
  55.                         if Screen.Forms[I].Handle = ForeGroundHWND then
  56.                            //if the new focus handle is exist in form list
  57.                            begin
  58.                                 //then, break from circle and DO NOT CALL THE Event
  59.                                 Call := False;
  60.                                 break;
  61.                            end;
  62.      //Else call the event
  63.      {
  64.        P.s maybe i'm just doesn't uderstand, what i can use 'Else' instead
  65.        of variable inside the iteration, but...
  66.        i haven't tested circles in FPC yet.
  67.      }
  68.      if Call and Assigned(fOnUnfocus) then
  69.         fOnUnfocus(Self as TObject);
  70.      end;
  71.   // set the trigger-variable
  72.   lastMessage := TheMessage.msg;
  73.   // and... send message to the parent & nested procedures
  74.   if Assigned(fOldWindowProc) then
  75.      fOldWindowProc(TheMessage);
  76.   if Assigned(fNewWindowProc) then
  77.      fNewWindowProc(TheMessage);
  78. end;
  79.  
  80.  
  81. end.
  82.  
  83.  

P.s hope it will help someone  ;)
« Last Edit: May 28, 2019, 06:51:05 pm by HuntingKashket »
Leu Zenin
-------------------------------
Lazarus 2.1.0  with FPC 3.1
Windows 8.1 x64

furious programming

  • Sr. Member
  • ****
  • Posts: 354
  • I click a little.
Re: How to fire an event, when form loses focus [SOLUTION, WIN ONLY]
« Reply #1 on: May 28, 2019, 06:49:44 pm »
Use the Application.OnDeactivate event — assign your own method to it.
Lazarus 2.0.4 with FPC 3.0.4, Windows XP (all 32-bit)

HuntingKashket

  • New Member
  • *
  • Posts: 33
  • I'm interested in upgrading everything
Re: How to fire an event, when form loses focus [SOLUTION, WIN ONLY]
« Reply #2 on: May 28, 2019, 06:57:38 pm »
Application.OnDeactivate can be assigned just once, or, i need to store the previous event code, to resolve conflicts with 'user' applications.
I think, method with WinAPI is better way to hook system event, than setting TApplication property, in this case (the target is to make component, which can be used multiple times in the same application without limitation of events/properties) :)
Leu Zenin
-------------------------------
Lazarus 2.1.0  with FPC 3.1
Windows 8.1 x64

furious programming

  • Sr. Member
  • ****
  • Posts: 354
  • I click a little.
Re: How to fire an event, when form loses focus [SOLUTION, WIN ONLY]
« Reply #3 on: May 28, 2019, 08:00:39 pm »
Application.OnDeactivate can be assigned just once […]

If you want to use more OnDeactivate handlers, then use Application.AddOnDeactivateHandler.

Quote
[…] to resolve conflicts with 'user' applications.

Huh? What ”conflicts” and which ”user applications”?

Quote
I think, method with WinAPI is better way to hook system event […]

You are reinventing the wheel instead of using ready-made mechanisms that work the same (in addition multiplatform!). You don't like TApplication? Just declare your own LM_ACTIVATE handler and check whether the message contains WA_INACTIVE.

Simple example below:

Code: Pascal  [Select]
  1. unit MainWindow;
  2.  
  3. {$MODE OBJFPC}{$LONGSTRINGS ON}
  4.  
  5. interface
  6.  
  7. uses
  8.   Forms, StdCtrls, LMessages;
  9.  
  10. type
  11.   TMainForm = class(TForm)
  12.     CStateCheckBox: TCheckBox;
  13.   protected
  14.     procedure WMActivate(var AMessage: TLMActivate); message LM_ACTIVATE;
  15.   end;
  16.  
  17. var
  18.   MainForm: TMainForm;
  19.  
  20. implementation
  21.  
  22. {$R *.lfm}
  23.  
  24. procedure TMainForm.WMActivate(var AMessage: TLMActivate);
  25. begin
  26.   CStateCheckBox.Checked := AMessage.Active = WA_INACTIVE;
  27. end;
  28.  
  29. end.

Tested on Windows XP — works perfect. Sources in the attachment.
« Last Edit: May 28, 2019, 08:47:16 pm by furious programming »
Lazarus 2.0.4 with FPC 3.0.4, Windows XP (all 32-bit)

HuntingKashket

  • New Member
  • *
  • Posts: 33
  • I'm interested in upgrading everything
Re: How to fire an event, when form loses focus [SOLUTION, WIN ONLY]
« Reply #4 on: May 28, 2019, 09:41:37 pm »
I need to learn Lazarus deeper.
Original code used for Delphi, where this method is unavailable...
I just have no time (or enough brains) to check all of the abilities.

P.s spec: component contains bicycle, which sets WindowProc method, so, it's easier (for me) to add some code, instead of rewriting all mechanism.
I've just used what i remember, and nothing more, yes, it's a bad practice, you're right

Upd2: Delphi is strange. TWMActivate works when changing between application forms only. Maybe bug, idk, but, amount of that dirt in delphi is enough to say, that i need to forget its Pascal and learn FP.
« Last Edit: May 28, 2019, 09:52:23 pm by HuntingKashket »
Leu Zenin
-------------------------------
Lazarus 2.1.0  with FPC 3.1
Windows 8.1 x64

furious programming

  • Sr. Member
  • ****
  • Posts: 354
  • I click a little.
Re: How to fire an event, when form loses focus [SOLUTION, WIN ONLY]
« Reply #5 on: May 28, 2019, 11:56:24 pm »
Do what you want — there are few solutions to do this.

But if I would like to add such a function to the program myself, I would use the TApplication class. 8)
Lazarus 2.0.4 with FPC 3.0.4, Windows XP (all 32-bit)