Solved

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

Posted on 2006-11-02
14
409 Views
Last Modified: 2010-04-04
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.
0
Comment
Question by:cyman73
  • 8
  • 4
  • 2
14 Comments
 
LVL 28

Expert Comment

by:TName
ID: 17861461
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.
0
 
LVL 28

Expert Comment

by:TName
ID: 17861657
Changed the WMNCLButtonDown:

procedure TForm2.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
   if (Message.HitTest = HTCLOSE) then
     Stop:=True;
   inherited;
end;
0
 
LVL 28

Expert Comment

by:TName
ID: 17861869
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;

0
 

Author Comment

by:cyman73
ID: 17861939
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.


0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 17861949
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.
0
 
LVL 28

Expert Comment

by:TName
ID: 17862213
>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...
0
 

Author Comment

by:cyman73
ID: 17862526
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.
0
Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

 
LVL 28

Expert Comment

by:TName
ID: 17862547
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.
0
 
LVL 28

Accepted Solution

by:
TName earned 500 total points
ID: 17862849
>Besides many other software packages use this call, so I didn't want to update all of them.
Ok, I understand.

The problem is that the modal window is still there (not freed) when you close it, so everything (variables, labels) should be reset in the OnShow procedure, because that procedure will fire when you show the form again.

BTW, a last version of WMNCLButtonDown ;)

procedure TFrmAnimation.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
   if (Message.HitTest = HTCLOSE) then
     Stop:=True;
   Application.ProcessMessages;
   inherited;
end;
0
 
LVL 28

Expert Comment

by:TName
ID: 17863105
>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...
0
 

Author Comment

by:cyman73
ID: 17863571
Hey TName,
Does your implementation in essence put the animation in its own thread?

0
 
LVL 28

Expert Comment

by:TName
ID: 17865031
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.
0
 

Author Comment

by:cyman73
ID: 17866877
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.
0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 17872544
You're welcome
0

Featured Post

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…
This tutorial demonstrates a quick way of adding group price to multiple Magento products.

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now