Link to home
Start Free TrialLog in
Avatar of cyman73
cyman73

asked on

Can't close a form with biSystemMenu X button when loop executing

Hello all,

I call a form in my main application the following way:  FrmAnimation.ShowModal;

From the FrmAnimation form I execute an Animation Procedure.  While this Animation Procedure ( a while loop executing away) is active, I cannot close the form if I want to exit by pressing the X button from the System Menu.  I made sure to add Application.ProcessMessages statements in the while loop, however the close action is not registered until I stop the while loop/Animation Procedure.  The while loop is stopped if I click anywhere on the form.  I click on the X button, while animation is ON, the form closes immediately after clicking on the form itself.

OnClose and OnCloseQuery are not executed when this while loop is executing.

Can anyone give me any ideas on how to close this form when animation is ON.  I want to give the user the ability to close the form whenever they click the X button without delay.  Any help is greatly appreciated.

Thank you.
Avatar of TName
TName

Hi, try this (show this form modal):


unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TForm2 = class(TForm)
    Timer1: TTimer;
    procedure Timer1Timer(Sender: TObject);
  private
    Stop:Boolean;
    procedure Animate;
    procedure WMNCLButtonDown(var Message: TWMNCLButtonDown); message WM_NCLBUTTONDOWN;
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.Animate;
var
   i:Integer;
begin
 Stop:=False;
 i:=GetTickCount;
 while (GetTickCount < i+5000) and (Stop=false) do begin
    Caption:=IntToStr(GetTickCount);
    Application.ProcessMessages;
 end;
end;

procedure TForm2.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
   inherited;
   Stop:=True;
end;

procedure TForm2.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled:=False;
  Animate;
end;

end.
Changed the WMNCLButtonDown:

procedure TForm2.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
   if (Message.HitTest = HTCLOSE) then
     Stop:=True;
   inherited;
end;
BTW, if you do this, don't let Form2 be auto-created. (Project>Options>Forms - Auto-create Forms or delete its creation in the .dpr)
Create it and free it when needed, otherwise it might mess up the main form's message handling.
e.g.

with TForm2.Create(nil) do begin
     ShowModal;
     Free;  
end;

Avatar of cyman73

ASKER

Sorry for being so green.  

I think I understand what is going in the code you suggested and I'm trying to implement it.  

With regards to your last comment, I can't make any changes to the main form.  I need to implement the changes in the FrmAnimation only and so I need to leavel FrmAnimation.ShowModal as it was.

If I use the WMNCLButtonDown() procedure, will this then catch any mouse down events?

I wonder why the mouse event is not "registered" when clicking on the title bar/system menu, at least when the loop is executing.

Should find out if it works soon.


Avatar of Pierre Cornelius
Or you could just intercept the WM_CLOSE message in the WndProc method. e.g.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
    Button1: TButton;
    procedure FormClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    Animate: boolean;
    procedure DoAnimation;
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.DoAnimation;
begin
  while Animate do
  begin
    if ProgressBar1.Position = ProgressBar1.Max
      then ProgressBar1.Position:= ProgressBar1.Min;
    ProgressBar1.StepIt;
    Application.ProcessMessages;
  end;
end;

procedure TForm1.FormClick(Sender: TObject);
begin
  Animate:= false;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Animate:= true;
  DoAnimation;
end;

procedure TForm1.WndProc(var Message: TMessage);
begin
  if Message.Msg = WM_CLOSE
    then Animate:= false;
  inherited;
end;

end.
>If I use the WMNCLButtonDown() procedure, will this then catch any mouse down events?
Not the regular mouse down, only the left clicks in the non client area (mainly titlebar).

>I can't make any changes to the main form.

If you cannot even replace
  FrmAnimation.ShowModal;
with
  FrmAnimation:=TFrmAnimation.Create(nil);
  FrmAnimation.ShowModal;
  FrmAnimation.Free;
in your main form (may I ask why?), then you should probably better try another method. PierreC's solution might be better suited...
Avatar of cyman73

ASKER

in your main form (may I ask why?), then you should probably better try another method. PierreC's solution might be better suited...
So this will update a portion of an application.  The update is only for the animation.  Besides many other software packages use this call, so I didn't want to update all of them.

What is the difference between
WndProc and WMNCLButtonDown

I got bot implementations working but I have an issue.  It seems that if I close the FrmAnimation multiple times, then I try to open it, the forms opens and closes right away before any user input.  This with WMNCLButtonDown.  Should I be flushing this message or consuming it somehow?  

I tried WndProc, but same issue.  The window closes by itself just after opening after multiple open/close cycles.

Thanks.
Ok, creating at least the timer at runtime seems to solve the problem.
This should work with

FrmAnimation.ShowModal;



unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TFrmAnimation = class(TForm)
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    Stop:Boolean;
    Timer:TTimer;
    procedure Animate;
    procedure TimerProc(Sender: TObject);
    procedure WMNCLButtonDown(var Message: TWMNCLButtonDown); message WM_NCLBUTTONDOWN;
  public
    { Public declarations }
  end;

var
FrmAnimation:TFrmAnimation;

implementation

{$R *.dfm}

procedure TFrmAnimation.FormShow(Sender: TObject);
begin
  Timer:=TTimer.Create(Nil);
  with Timer do begin
     Interval:=1000;
     Enabled:=True;
     OnTimer:=TimerProc;
  end;
end;

procedure TFrmAnimation.Animate;
var
   i:Integer;
begin
 Stop:=False;
 i:=GetTickCount;
 while (GetTickCount < i+5000) and (Stop=false) do begin
    Caption:=IntToStr(GetTickCount);
    Application.ProcessMessages;
 end;
end;

procedure TFrmAnimation.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
   if (Message.HitTest = HTCLOSE) and TForm(Self).Showing then
     Stop:=True;
   inherited;
end;

procedure TFrmAnimation.TimerProc(Sender: TObject);
begin
  Timer.Enabled:=False;
  Animate;
end;

procedure TFrmAnimation.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  Timer.Free;
end;

end.
ASKER CERTIFIED SOLUTION
Avatar of TName
TName

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>What is the difference between
>WndProc and WMNCLButtonDown

Very simply put:
Intercepting the messages sent by windows to the form (by overriding WndProc), you can filter the messages and react if a certain message is sent. In this case, if the message is "close", then first set variable x to value y.
WMNCLButtonDown handles the message that is sent when the user clicks on the non-client area of the window. In this case, you can say: if the click was on the close button, then first do this and that...
Avatar of cyman73

ASKER

Hey TName,
Does your implementation in essence put the animation in its own thread?

You mean, because of the timer? No, that would be a different matter.
It just makes sure the while loop is stopped in time.
The timer is just used to start the loop.
Avatar of cyman73

ASKER

Thank you for your help.  I was able to fix this issue using your suggestion TName.  Thank you to Pierre for your suggestion as well.
You're welcome