show window before terminate - delphi

I have a delphi application that, on startup, checks to see if a process is already running, if it is running, I pass data over to that process and terminate the current process. The problem: In terminating the current process, the window of the app flashes for a split second prior to termination. All the code is in the application initialization, before that main form is even created, so I don't understand how it could show the form for a split second. I have tried numerous things like making the window invisible, nothing seems to work. Is there something I am doing wrong.

You are apparently not terminating soon enough. I'd do something like
program Project1;
uses
Forms,
Windows,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
function PrevInstance: boolean;
begin
...
end;
procedure PassData;
begin
...
end;
begin
if PrevInstance then
begin
PassData;
Exit;
end;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Update: I believe you do something like
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure MyInitProc;
begin
if true then Application.Terminate;
end;
initialization
InitProc := #MyInitProc;
end.
This will not work, because Application.Terminate doesn't terminate the application immediately. Instead, it simply posts a WM_QUIT message. This message will be received and acted upon after all initialisation is completed.

Related

COM Server application and GetObject - reusing the running application

I'm trying to automate an application (Windows 8, Delphi XE.) For my testing I'm doing the following:
Created a small test application, consisting of a form and a memo (Form1)
Added a new ActiveX Object, CoClass name TestOLE, Threading mode Apartment, Instancing Multiple (as per this article.)
Added one method Method1 which only adds some text to the memo control in Form1
I then start the application and double click on a file named test.vbs which contains the following code:
dim obj
set obj = GetObject("", "Project1.TestOLE")
obj.AddSomeText "Hola mundo"
When the application is running, I see that a new form is created, the text is added and then it exits.
What I want to accomplish is that the opened application should have its memo text changed.
I've repeated creating new projects with both MultipleInstance and SingleInstance, and in an outburst of heuristic anger, I even changed the threading model to single, to no avail.
I see two flags in the type library editor: "Replaceable" and "Aggregatable." However, selecting "Replaceable" ends up in an error in the generated RIDL file.
I've been reading a lot about GetObject. It appears that its documentation is even wrong (it says you can omit the first parameter but I've found that doesn't work).
Is this the right way to write an automation server in Delphi that can be reused?
Well, I got it working (I hope.)
Reading more of the same article cited above, found the following:
Know how to implement servers that support GetActiveObject.
Adding a global object, and registering in the Running Object Table (ROT) accomplishes the desired task of having the COM call passed to the running application:
Project file:
program TestOLEProject3;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
TestOLEProject3_TLB in 'TestOLEProject3_TLB.pas',
Unit2 in 'Unit2.pas' {TestOLE: CoClass},
Unit3 in 'Unit3.pas';
{$R *.TLB}
{$R *.res}
begin
Application.Initialize;
RegisterGlobalTestOLE;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Unit2.pas:
unit Unit2;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, TestOLEProject3_TLB, StdVcl;
type
TTestOLE = class(TAutoObject, ITestOLE)
protected
procedure Method1; safecall;
procedure Quit; safecall;
end;
implementation
uses ComServ, Unit1, Unit3;
procedure TTestOLE.Method1;
begin
Form1.Memo1.Lines.Add('Wheeee');
end;
procedure TTestOLE.Quit;
begin
RevokeGlobalTestOLE;
end;
initialization
TAutoObjectFactory.Create(ComServer, TTestOLE, CLASS_TestOLE, ciMultiInstance,
tmApartment);
end.
Unit3.pas (functions to register and unregister the global object):
unit Unit3;
interface
procedure RegisterGlobalTestOLE;
procedure RevokeGlobalTestOLE;
implementation
uses TestOLEProject3_TLB, ComObj, ActiveX;
var
GlobalTestOLEHandle: longint = 0;
procedure RegisterGlobalTestOLE;
var
GlobalTestOLE: ITestOLE;
begin
GlobalTestOLE := CoTestOLE.Create;
OleCheck(RegisterActiveObject(GlobalTestOLE, CLASS_TestOLE,
ACTIVEOBJECT_STRONG, GlobalTestOLEHandle));
end;
procedure RevokeGlobalTestOLE;
begin
if (GlobalTestOLEHandle <> 0) then
begin
OleCheck(RevokeActiveObject(GlobalTestOLEHandle, nil));
GlobalTestOLEHandle := 0;
end;
end;
end.

Proper Catastrophic Error Handling

There's something I keep running into that I really haven't solved with Delphi programs and was wondering if anyone could instruct me on it. As the topic says, how do you do proper catastrophic error handling? For instance:
// is file necessary for the program present?
if not FileExists(FilePath1) then
begin
raise Exception.Create(FilePath1 + ' does not exist and is required for this program to function.');
// I obviously need to do something here to make the program QUIT and not have
// any more code run.
Application.Terminate;
Abort;
end;
I can use the exception unit there as well and throw out an exception, but the program continues as before. I've used the halt call in the past, but it seems to not do any cleanup or the like so I end up making a big procedure with close and free calls for everything I've done just to be sure (and even then I'm not sure of any of the behind the scenes stuff).
So what is the right way to handle such things?
Edit: To clarify, I'm wanting to know about how to make the program do what clean-up it needs to do and then EXIT NOW and not do any other code.
To perform abnormal termination call Halt() passing the exit code.
if CatastropicErrorDetected then
begin
... show error message
Halt(1);
end;
On Windows this results in a call to TerminateProcess and will stop execution there and then.
You note that no cleanup is performed and usually that's what you want. Since you are performing this check at application startup there should be nothing to cleanup.
IMHO the only clean way would be checking for "Fatal conditions" before Application is running.
program Project2;
uses
Forms,Dialogs,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
ReportMemoryLeaksOnShutDown := true;
Application.Initialize;
if True then // your condition here
begin
MessageDLG('Fatal Error',mtError,[mbok],0);
end
else
begin
Application.CreateForm(TForm1, Form1);
Application.Run;
end;
end.
Any other approach will have side effects
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private-Deklarationen }
FSL:TStringList;
public
Destructor Destroy;override;
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
destructor TForm1.Destroy;
begin
FreeAndNil(FSL);
Showmessage('Will never be seen with Application.Terminate + HALT');
inherited;
end;
procedure TForm1.FormCreate(Sender: TObject);
const
Testing=0; // try 1 and 2 too
begin
FSL:=TStringList.Create;
Try
raise Exception.Create('Terminating now');
except
case Testing of
0: begin
// exception object will not be freed other code will be prevented, Form won't be shown
Application.Terminate;
HALT;
end;
1: begin
// exception object will not be freed Form won't be shown
HALT;
end;
2: begin
// clean but Form will be shown
Application.Terminate;
end;
end;
end;
end;
end.
You can instruct the application global object to terminate the program by calling Application.Terminate.
Call Terminate to end the application programmatically. By calling Terminate rather than freeing the application object, you allow the application to shut down in an orderly fashion.
Terminate calls the Windows API PostQuitMessage function to perform an orderly shutdown of the application. Terminate is not immediate.
Since the call can occur deeper in the stack, you can also raise an Exception, and you code your program to not handle it in order to let the execution reach the main application loop and the default exception handler catch it.
That way, you effectively prevent's more code to run in your application.
In code it may look like this:
if not FileExists(FilePath1) then
begin
MessageDlg(FilePath1 + ' does not exist and is required for this program to function.',
mtWarning, [mbOK], 0);
Application.Terminate;
Abort; //raising a EAbort just as an example
end;
Depending on where this code is called, I advise you not to show the message directly, but rather raise an exception with the message and let the application object default HandleException method show the message for you:
if not FileExists(FilePath1) then
begin
Application.Terminate;
raise EMyFatalException.Create(FilePath1
+ ' does not exist and is required for this program to function.');
end;
Which looks more natural to me. EMyFatalException is a hypothetical exception class you can declare and never handle in your except clauses.
You can write your own Application.OnException handler, ex:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure HandleException(Sender: TObject; E: Exception);
end;
var
Form1: TForm1;
type
EMyFatalError = class(Exception);
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.OnException:= HandleException;
raise EMyFatalError.Create('OOPS!');
end;
procedure TForm1.HandleException(Sender: TObject; E: Exception);
begin
Application.ShowException(E);
if E is EMyFatalError then
Application.Terminate;
end;
end.

MainFormOnTaskbar + tooltip causes focus-stealing

I built the code below using Delphi XE2. It creates Form1, and Form1 immediately creates an instance of Form2. When I press the button on Form2 a second Form2 is created.
Now if I hover the mouse over the button on this second, topmost, Form2 and wait for the tooltip to appear, the moment the tooltip appears, the first Form2 comes to the front, stealing focus.
The problem occurs only if Application.MainFormOnTaskbar is True. It also relies on the first Form2 being created from Form1's FormCreate method. If I use PostMessage() to delay the creation of the first Form2 until the application has finished initialising, the problem goes away.
I'd like to understand why this is happening. I have already learned that Delphi's Application object handles a lot of things including hint display, and I know that Delphi can recreate a window's handle during initialisation, but I haven't been able to follow this through to explain fully the behaviour described above (or indeed whether the above two facts are even relevant).
Project1.dpr
program Project1;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True; // False makes problem go away
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Unit1.pas
unit Unit1;
interface
uses
Vcl.Forms, Unit2;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
public
procedure CreateForm2;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
CreateForm2;
end;
procedure TForm1.CreateForm2;
var
frm : TForm2;
begin
frm := TForm2.Create(Application); // (Could pass Self - makes no difference)
frm.Show;
end;
end.
Unit2.pas
unit Unit2;
interface
uses
Vcl.Forms, System.Classes, Vcl.Controls, Vcl.StdCtrls, WinApi.Windows;
type
TForm2 = class(TForm)
Button1: TButton; // This button has a hint
procedure Button1Click(Sender: TObject);
end;
var
Form2: TForm2;
implementation
uses
System.SysUtils, Unit1;
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
Form1.CreateForm2;
end;
end.
The key issue here is that the first instance of TForm2 is created as window that is owned by the application window, Application.Handle. And here I am referring to the Windows meaning of owner. In VCL language this is known as the popup parent.
Now, when you create that first TForm2 instance, the Application.MainForm property is still nil. And because you did not explicitly assign PopupParent, the code in TCustomForm.CreateParams sets the owner to be the application window.
You simply do not want your windows to be owned by the hidden application window. This is the reason why that first TForm2 instance sometimes appears behind all the other windows, in particular behind your main form. It has simply been created with the wrong owner.
The form that is owned by Application.Handle gets shown in THintWindow.ActivateHint. That happens due to the line that reads ParentWindow := Application.Handle. This is followed by a call to SetWindowPos(Handle, ...) which results in the incorrectly owned form coming to the front. Presumably that form comes to the front because it is also owned by Application.Handle. Right now I don't have a clear explanation for the precise mechanism, but I don't find that terribly interesting because the form is clearly setup wrongly.
In any case, the fundamental problem is that you have created a window that is incorrectly owned. The solution therefore is to make sure that the window is owned correctly. Do that by assigning the PopupParent. For example:
procedure TForm1.CreateForm2;
var
frm : TForm2;
begin
frm := TForm2.Create(Application); // (Could pass Self - makes no difference)
frm.PopupParent := Self;
frm.Show;
end;

How can I display a form for set configuration before the main form?

in my project i have two form's(form1,form2), form1 is configuration form.
i want to show Form1 and when we click Button1 then show Form2 and free(Release) Form1. how can to i do this?
i use this code. but this project start and then exit automatically.A Friend said because the application message loop never start, and application terminates because main form does not exist. how i can to solve this problem?
uses Unit2;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);
Release;
end;
///
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Form1:= TForm1.Create(Application);
Application.Run;
end.
Do exactly what you asked in the question title: Create and show the configuration form, and then create and show the main form. The trick is in how you create them. Only use Application.CreateForm for the one form that you want to be your main form. Use the ordinary object creation technique for all other forms.
Modify your DPR file like so:
var
ConfigForm: TConfigForm;
begin
Application.Initialize;
ConfigForm := TConfigForm.Create(nil);
try
if ConfigForm.ShowModal <> mrOK then
exit;
finally
ConfigForm.Free;
end;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.
You need to create Form2 first and this will be your main form. You want it to start hidden and be shown after Form1 has done its job. Something like this:
uses Unit2;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Form2.Show;
Release;
end;
///
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm2, Form2);
Form2.Hide;
Form1 := TForm1.Create(Application);
Form1.Show;
Application.Run;
end.
The reason is that the app terminates when your main form closes. And your main form is typically the first one that you create.
You can prohibit Form1 to be shown by setting ShowMainForm to false. Leave the code in the DPR just as the IDE creates it:
uses
Forms,
Unit2 in 'Unit2.pas' {Form2},
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm2, Form2);
Application.Run;
end.
In the FormCreate event of Form2 just set ShowMainForm to false and call Show to make Form2 visible:
procedure TForm2.FormCreate(Sender: TObject);
begin
Application.ShowMainForm := False;
Show;
end;
and in the ButtonClick event of Form2 show Form1 and close Form2:
procedure TForm2.Button1Click(Sender: TObject);
begin
Form1.Show;
Close;
end;
This keeps all necessary changes inside unit2.
Edit
Some remarks that came to mind after sleeping a night over it:
Form2 should be the last one auto-created, i.e. the one directly before the Application.Run statement.
The Form1.Show statement in the ButtonClick event should be moved to the FormClose event. Thus the user can close the form with the Windows close button or whatever he likes best.
If for some reason Form1 should never be shown, some code must be added to close the application. A Halt may serve here.

How i can to Destroy (free) a Form from memory?

i have 2 Form (Form1 and Form2) in the my project, Form1 is Auto-create forms, but Form2 is Available forms.
how i can to create Form2 and unload Form1?
I received a "Access validation" Error in this code.
Here is Form1 code:
1. uses Unit2;
//*********
2. procedure TForm1.FormCreate(Sender: TObject);
3. var a:TForm2;
4. begin
5. a := TForm2.Create(self);
6. a.Show;
7. self.free; // Or self.destory;
8. end;
Thanks.
I modified that "Serg" code to this :
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses Unit2;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);
Release;
end;
end.
///
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Form1:= TForm1.Create(Application);
Application.Run;
end.
but this project start and then exit automatically, Why?
i want to show Form1 and when we click Button1 then show Form2 and free(Release) Form1. how i can to this?
When you destroy a form, it's better to use Release.
Release is almost the same as free, but it waits for pending messages to avoid crashes.
You should never use Destroy. Free/Release calls the destructor.
Self is the current object (in your code Form1, so self.Free kills the current form. Which results in the access violation. Form1 is auto created, it is also auto destroyed so you shouldn't destroy it yourself. If you don't want it, hide it.
And you should keep a reference to the newly created form if you want to handle it later.
Your modified code should be like:
uses Unit2;
TForm1 = class (TForm)
private
FChild : TForm2;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FChild := TForm2.Create(nil);
Hide; // Hides form 1
FChild.Show;
end;
procedure TForm2.FormDestroy(Sender: TObject);
begin
FChild.Release;
end;
But Why do you want to create another form in the form create of the first form. Why not remove the first form entirely and only use the second one (auto created)?
You are trying to do something strange.
You cannot free main form without closing application, so your Form1 should not be autocreated form, both Form1 and Form2 should be created manually.
First, you should edit your project source like this to create Form1 manually:
program Project9;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Form1:= TForm1.Create(Application);
Application.Run;
end.
Form1.OnCreate should be written as
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);
Release;
end;
that will make Form2 the main form of your application. As already answered you should use Release method to free the form.
If Form1 is 'autocreate', it's owned by the application object - you shouldn't free it in your code. If Form1 owns Form2, application cleans up both.
I'd do it like this, but not sure it meets your requirements:
procedure TForm1.FormCreate(Sender: TObject);
var a:TForm2;
begin
a := TForm2.Create(nil);
try
a.Show;
finally
freeandNil(a);
end;
end;
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
...
procedure TForm1.FormClose(Sender: TObject; var CanClose: Boolean);
begin
if MessageDlg ('Are you want to exit?', mtConfirmation,
[mbYes, mbNo], 0) = mrNo then
CanClose := False;
end;
So, that is all...
If all Form1 should do is initialize something but not being shown, consider using a datamodule instead. These cannot be shown but can still be autocreated.

Resources