Delphi FireDac application blocking with unavailable host - delphi

Source: Delphi XE10; Os: W10; Database server: FB 2_5_1;
I created client application to connect more than one database. My application create TSource->Create TThread -> create DataModule with FDConnection.
Every TSource have own TThread which have own DataModule;
To tests I have two definitions: HostLocal and HostRemote;
My Test:
a)Run: Connection.Open for HostRemote;
b)Next Run: Connection.Open for HostLocal;
When HostRemote is unavailable than HostLocal wait for I dont know for what.
Why unavailable connection blocked another correct connection?
EDIT:
Database Server was updated to 3.0.0
Code:
begin
Application.Initialize;
Application.CreateForm(TFormMain, FormMain);
Application.Run;
end.
unit UnitFormMain;
procedure TFormMain.FormCreate(Sender: TObject);
begin
Source0 := TFormSource.Create(Self);
Source1 := TFormSource.Create(Self);
end;
procedure TFormMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil( Source0 );
FreeAndNil( Source1 );
end;
procedure TFormMain.Button1Click(Sender: TObject);
begin
Source0.Show;
Source1.Show;
Source0.ThStart( 0 );
Source1.ThStart( 1 );
end;
unit UnitFormSource;
TFormSource = class(TForm)
Memo: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
th: TMyThread;
public
procedure ThStart( value: Integer );
procedure MyTerminate( Sender: TObject );
end;
...
procedure TFormSource.FormCreate(Sender: TObject);
begin
th := TMyThread.Create( TRUE );
th.OnTerminate := MyTerminate;
th.Priority := TThreadPriority.tpLower;
th.FreeOnTerminate := TRUE;
end;
procedure TFormSource.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil( th );
Action := TCloseAction.caFree;
end;
procedure TFormSource.ThStart(value: Integer);
begin
th.SetSwitch( value );
th.Resume;
Memo.Lines.Add( TimeToStr(Now) + ' Start' );
end;
procedure TFormSource.MyTerminate(Sender: TObject);
begin
Memo.Lines.Add( TimeToStr(Now) + ' Terminate' );
end;
unit UnitThread;
TMyThread = class(TThread)
private
Switch: Integer;
dm: TDMFireBird;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
procedure SetSwitch(value: Integer);
end;
...
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
dm := TDMFireBird.Create(nil);
Switch := -1;
end;
destructor TMyThread.Destroy;
begin
FreeAndNil(dm);
inherited;
end;
procedure TMyThread.Execute;
begin
with dm.FDConnection.Params do
begin
Clear;
Add('DriverID=FB');
end;
case Switch of
0:
with dm.FDConnection.Params do
Add('Protocol=Local');
1:
with dm.FDConnection.Params do
begin
Add('Protocol=TCPIP');
Add('Server=10.0.0.1'); // unavailable connection - server not exist
Add('Port=3050');
end;
end;
with dm.FDConnection.Params do
begin
Add('Database=D:\temp\test.fdb');
Add('User_Name=SYSDBA');
Add('Password=masterkey');
end;
try
dm.FDConnection.Open;
except
// to-do: update error message
end;
end;
procedure TMyThread.SetSwitch(value: Integer);
begin
Switch := value;
end;
unit UnitDMFireBird;
TDMFireBird = class(TDataModule)
FDConnection: TFDConnection;
FDTransaction: TFDTransaction;
private
{ Private declarations }
public
{ Public declarations }
end;
Test no.1
Code:
Source0.ThStart( 0 );
// Source1.ThStart( 1 );
Result for Source0:
14:46:20 Start
14:46:20 Terminate
Result for Source1: no result
Comment: At the same time first connection was opened;
Test no.2
Code:
Source0.ThStart( 0 );
Source1.ThStart( 1 );
Result for Source0:
14:48:16 Start
14:48:40 Terminate
Result for Source1:
14:48:16 Start
14:48:40 Terminate
Comment: Terminate for both at the same time.
My question. Why one blocked another ? Why (for test no.2) first correct connection wait for second unavailable connection? In test no.2 first connection should to open like in test no.1 -> fast, without wait.

Related

Why does the service OnCreate get called multiple times?

The code below, via...
procedure TTimetellServiceServerMonitor.ServiceDebugLog(const AMsg: String);
const cDebugLogFile = 'd:\temp\service.log';
... outputs this debug info showing that we go through the OnCreate several times (I added the - - descriptions):
- testsvcserverMonitor /install -
S 1802 servicecreate
S 1802 AfterInstall
- start from services app -
S 1741 servicecreate
S 1741 servicestart
S 1741 MonitorThread.Start
- stop from services app -
S 1741 servicestop
- testsvcserverMonitor /uninstall -
S 1336 servicecreate
S 1336 beforeuninstall
I assign a random tag value to the service in its OnCreate and you can see that these are different.
Why does this happen, is there a bug, should I prevent it and how?
(Windows 32 bit, Delphi 10.4.2. Sydney)
.pas code:
unit USvcServerMonitor;
interface
uses
WinApi.Windows, WinApi.Messages, System.SysUtils, System.Classes, Vcl.SvcMgr, WinApi.WinSvc;
type
TMonitorServiceThread = class(TThread) // Worker thread
private
FCheckLiveEvery,
FLastLiveCheck : TDateTime;
public
procedure Execute; override;
end;
type
TApplicationMonitor = class(TService)
procedure ServiceBeforeUninstall(Sender: TService);
procedure ServiceCreate(Sender: TObject);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
// procedure ServiceExecute(Sender: TService); Not necessary, WorkerThread does the work
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceAfterInstall(Sender: TService);
private
procedure ServiceDebugLog(const AMsg: String);
public
function GetServiceController: TServiceController; override;
end;
var
MonitorThread : TMonitorServiceThread;
ApplicationMonitor: TApplicationMonitor;
implementation
{$R *.DFM}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
ApplicationMonitor.Controller(CtrlCode);
end;
function TApplicationMonitor.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TApplicationMonitor.ServiceAfterInstall(Sender: TService);
begin
ServiceDebugLog('AfterInstall');
// StartType is stAuto, but start manually after install
end;
procedure TApplicationMonitor.ServiceBeforeUninstall(Sender: TService);
begin
ServiceDebugLog('beforeuninstall');
end;
procedure TApplicationMonitor.ServiceCreate(Sender: TObject);
begin
Self.Tag := 1000 + Random(1000); // For debugging
ServiceDebugLog('servicecreate');
end;
procedure TApplicationMonitor.ServiceStart(Sender: TService; var Started: Boolean);
begin
ServiceDebugLog('servicestart');
MonitorThread := TMonitorServiceThread.Create(true); // Suspended
ServiceDebugLog('MonitorThread.Start');
MonitorThread.Start;
Started := true;
end;
procedure TApplicationMonitor.ServiceDebugLog(const AMsg: String);
// Quick-n-dirty debugging routine
const cDebugLogFile = 'd:\temp\service.log';
var t: textfile;
begin
if not fileexists(cDebugLogFile) then
begin
assignfile(t,cDebugLogFile);
Rewrite(t);
end
else
begin
assignfile(t,cDebugLogFile);
Append(T);
end;
writeln(T,'S ' + Inttostr(self.Tag) + ' ' + AMsg);
closefile(t);
end;
procedure TApplicationMonitor.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
ServiceDebugLog('servicestop');
MonitorThread.Terminate;
Sleep(100);
MonitorThread.Free;
Sleep(100);
Stopped := True;
end;
{ TMonitorServiceThread }
procedure TMonitorServiceThread.Execute;
begin
inherited;
FLastLiveCheck := Now;
FCheckLiveEvery := 1;
while not Terminated do
begin
try
if (FCheckLiveEvery > 0) and (Now-FLastLiveCheck > FCheckLiveEvery/1440) then
begin
// Do some checks
FLastLiveCheck := Now;
end;
Sleep(500);
finally
end;
end;
end;
end.
.dfm file:
object ApplicationMonitor: TApplicationMonitor
Tag = 123
OldCreateOrder = False
OnCreate = ServiceCreate
AllowPause = False
DisplayName = 'Test Application Monitor Service'
AfterInstall = ServiceAfterInstall
BeforeUninstall = ServiceBeforeUninstall
OnStart = ServiceStart
OnStop = ServiceStop
Height = 250
Width = 400
end
TService is derived from TDataModule, so OnCreate will be called when the TService instance is created. That obviously happens when the service is going to be started, but also when it is installed and uninstalled.
So, no, it is not a bug and you also should not prevent it.
Perhaps it is just that your expectations are wrong?

Delphi Thread doesn't run [duplicate]

This question already has an answer here:
Delphi Access Violation when moving button on form
(1 answer)
Closed 7 years ago.
I'm trying to search for all files in all subfolders so it takes long time and application stop responding, so I used Thread (it's first time work with Threads) I read about it and I found this way to create and execute threads, but nothing happen when I call the thread, and I don't understand why I couldn't use the added components on the main form, I had to re-declare it again?
what I miss here?
type
TSearchThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
procedure AddAllFilesInDir(const Path: string; ListBox:TsListBox);
var
SR: TSearchRec;
I: Integer;
begin
if FindFirst(IncludeTrailingBackslash(Path) + '*.*', faAnyFile or faDirectory, SR) = 0 then
try
repeat
if (SR.Attr and faDirectory) = 0 then
ListBox.Items.Add(Path+'\'+SR.Name)
else if (SR.Name <> '.') and (SR.Name <> '..') then
AddAllFilesInDir(IncludeTrailingBackslash(Path) + SR.Name, ListBox);
Form1.sPanel2.Caption := Path+'\'+SR.Name;
Form1.sPanel2.Refresh;
ListBox.Refresh;
until FindNext(Sr) <> 0;
finally
FindClose(SR);
end;
end;
procedure TSearchThread.Execute;
var FileList: TsListBox;
I: Integer;
{Here I had to re-declare objects}
sDirectoryEdit1: TsDirectoryEdit;
sListBox1: TsListBox;
begin
FileList := TsListBox.Create(nil);
FileList.Parent := sListBox1;
FileList.Visible := False;
AddAllFilesInDir(sDirectoryEdit1.Text+'\', FileList);
for I := 0 to FileList.Count -1 do
if sListBox1.Items.IndexOf(FileList.Items.Strings[I]) = -1 then
sListBox1.Items.Add(FileList.Items.Strings[I]);
FileList.Clear;
end;
procedure TForm1.sDirectoryEdit1Change(Sender: TObject);
begin
TSearchThread.Create(False);
end;
Ok, let me give it a try:
First a new version of your thread:
uses
IOUtils;
type
TFileFoundEvent = procedure(const Path: string; const SearchRec: TSearchRec) of object;
TSearchThread = class(TThread)
private
FPath: string;
FSearchRec: TSearchRec;
FFileFoundEvent: TFileFoundEvent;
protected
procedure Execute; override;
public
Constructor Create(const aPath: string; aFileFoundEvent: TFileFoundEvent); reintroduce;
end;
{ TSearchThread }
constructor TSearchThread.Create(const aPath: string; aFileFoundEvent: TFileFoundEvent);
begin
// Create the Thread non suspended
inherited Create(false);
// Copy parameters to local members.
FFileFoundEvent := aFileFoundEvent;
FPath := aPath;
// Make the sure the thread frees itself after execution
FreeOnTerminate := True;
end;
procedure TSearchThread.Execute;
var
FilterPredicate: TDirectory.TFilterPredicate;
begin
// FilterPredicate is an in-place anonymous method to be called each time the TDirectory.GetFiles finds a file
FilterPredicate := function(const Path: string; const SearchRec: TSearchRec): Boolean
begin
// Since we can not access from within Synchronize we need to copy iot to a member of the class
FSearchRec := SearchRec;
// You cannot access VCL objects directly from a thread.
// So you need to call Syncronize
// For more info look in the online help
// http://docwiki.embarcadero.com/Libraries/Seattle/en/System.Classes.TThread.Synchronize
Synchronize(nil,
procedure
begin
FFileFoundEvent(FPath, FSearchRec);
end);
Result := True;
end;
// Do the search
TDirectory.GetFiles(FPath, TSearchOption.soTopDirectoryOnly, FilterPredicate)
end;
The main diffrence are that I pass a callback proceudre onto the constructor of the thread. And ofcause I uses TDirectory.GetFiles to search for files. You'll find TDirectory.GetFiles in IOUtils
Then you need to use it: Place a Listbox on your from and then call it like this :
Form definition:
type
TForm1 = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
private
procedure FileFoundEvent(const Path: string; const SearchRec: TSearchRec);
public
{ Public declarations }
end;
...
implementation
procedure TForm1.FileFoundEvent(const Path: string; const SearchRec: TSearchRec);
begin
ListBox1.Items.Add(SearchRec.Name);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
TSearchThread.Create(ExtractFilePath(Application.ExeName), FileFoundEvent);
end;
If you don't want to see the ongoing results of the searching, but rather want some speed you can create a version of the searchthread that gives you the result all at once:
uses
IOUtils;
type
TSearchThread = class(TThread)
private
FSearchPath: String;
FResultBuffer: TStrings;
protected
procedure Execute; override;
public
constructor Create(const aSearchPath: string; aResultBuffer: TStrings); overload;
end;
constructor TSearchThread.Create(const aSearchPath: string; aResultBuffer: TStrings);
begin
inherited Create(false);
FSearchPath := IncludeTrailingPathDelimiter(aSearchPath);
FResultBuffer := aResultBuffer;
FreeOnTerminate := True;
end;
procedure TSearchThread.Execute;
var
FBuffer: TStringlist;
Filename: String;
begin
Synchronize(nil,
procedure
begin
FResultBuffer.Text := 'Searching ' + FSearchPath;
end);
FBuffer := TStringlist.Create;
for Filename in TDirectory.GetFiles(FSearchPath, TSearchOption.soAllDirectories, nil) do
FBuffer.Add(Filename);
Synchronize(nil,
procedure
begin
FResultBuffer.Assign(FBuffer);
end);
FreeAndNil(FBuffer);
end;
This thread you have to call in a bit diffent way.
The form setup i still the same as before: A Listbox on a Form.
type
TForm1 = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
private
Stopwatch: TStopwatch;
procedure SearchThreadTerminate(Sender: TObject);
public
{ Public declarations }
end;
And then the implementation:
procedure TForm1.FormCreate(Sender: TObject);
begin
Stopwatch := TStopwatch.StartNew;
with TSearchThread.Create('C:\Program Files (x86)\Embarcadero\', ListBox1.Items) do
OnTerminate := SearchThreadTerminate;
end;
procedure TForm1.SearchThreadTerminate(Sender: TObject);
begin
Stopwatch.Stop;
Caption := 'Elapsed Milliseconds: ' + IntToStr(Stopwatch.ElapsedMilliseconds) + ' Files found: ' + IntToStr(ListBox1.Items.Count);
end;
The advantage of this version is speed. Updaing the screen is slow, and the first solution updated the screen for each file it found, while this one only updates the screen twice.
Try it out.

How to make GIF animate on "please, wait form"?

I would like make a quick non-closable modal dialog, that pops up while do some tasks and goes away when tasks finish.
There are some inherent difficulties:
Don't block the main UI thread;
Don't leave system ghosts windows;
Move tasks to running into a separate thread;
Allow update the waiting message to the user;
Handling exceptions from thread to the application;
Show animated GIF in the dialog;
How to get around these pitfalls?
Below, a practical example of how I would use it:
TWaiting.Start('Waiting, loading something...');
try
Sleep(2000);
TWaiting.Update('Making something slow...');
Sleep(2000);
TWaiting.Update('Making something different...');
Sleep(2000);
finally
TWaiting.Finish;
end;
type
TWaiting = class(TForm)
WaitAnimation: TImage;
WaitMessage: TLabel;
WaitTitle: TLabel;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
strict private
class var FException: Exception;
private
class var WaitForm : TWaiting;
class procedure OnTerminateTask(Sender: TObject);
class procedure HandleException;
class procedure DoHandleException;
public
class procedure Start(const ATitle: String; const ATask: TProc);
class procedure Status(AMessage : String);
end;
implementation
{$R *.dfm}
procedure TWaiting.FormCreate(Sender: TObject);
begin
TGIFImage(WaitAnimation.Picture.Graphic).Animate := True;
end;
procedure TWaiting.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
class procedure TWaiting.Start(const ATitle: String; const ATask: TProc);
var
T : TThread;
begin
if (not Assigned(WaitForm))then
WaitForm := TWaiting.Create(nil);
T := TThread.CreateAnonymousThread(
procedure
begin
try
ATask;
except
HandleException;
end;
end);
T.OnTerminate := OnTerminateTask;
T.Start;
WaitForm.WaitTitle.Caption := ATitle;
WaitForm.ShowModal;
DoHandleException;
end;
class procedure TWaiting.Status(AMessage: String);
begin
TThread.Synchronize(TThread.CurrentThread,
procedure
begin
if (Assigned(WaitForm)) then
begin
WaitForm.WaitMessage.Caption := AMessage;
WaitForm.Update;
end;
end);
end;
class procedure TWaiting.OnTerminateTask(Sender: TObject);
begin
if (Assigned(WaitForm)) then
begin
WaitForm.Close;
WaitForm := nil;
end;
end;
class procedure TWaiting.HandleException;
begin
FException := Exception(AcquireExceptionObject);
end;
class procedure TWaiting.DoHandleException;
begin
if (Assigned(FException)) then
begin
try
if (FException is Exception) then
raise FException at ReturnAddress;
finally
FException := nil;
ReleaseExceptionObject;
end;
end;
end;
end.
Usage:
procedure TFSales.FinalizeSale;
begin
TWaiting.Start('Processing Sale...',
procedure
begin
TWaiting.Status('Sending data to database');
Sleep(2000);
TWaiting.Status('Updating Inventory');
Sleep(2000);
end);
end;

delphi Form with multi instance use

i've an FTP uploader project that uses a form created on run time to start uploading to multiple FTP Servers ( using Indy ) , my issue is as follows ( and i really need your help ) .
On a Form i put an IdFTP Component + an Upload button + public properties named FTPSrvAdrs and SrcFile + TrgFolder like this way :
type
TFtpUploader = class(TForm)
IdFTP: TIdFTP;
StartUpload:TButton;
UploadProgress:TProgressBar;
procedure StartUploadClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FFtpSrvAdrs:String;
FSrcFile:String;
FTargetFtpFld:String;
Procedure StartMyUpload();
procedure SetFtpAdrs(const value:string);
procedure SetSrcFile(const value:string);
procedure SetTargetFtpFld(const value:string);
{ Private declarations }
public
{ Public declarations }
property FtpAdrs:string read FFtpSrvAdrs write SetFtpAdrs;
property SourceFile:string read FSrcFile write SetSrcFile;
property TargetFtpFld:string read FTargetFtpFld write SetTargetFtpFld;
end;
var
FtpUploader: TFtpUploader;
implementation
procedure TFtpUploader.StartUploadClick(Sender: TObject);
begin
StartMyUpload();
end;
procedure TFtpUploader.SetFtpAdrs(const value: string);
begin
FFtpSrvAdrs:=value;
end;
procedure TFtpUploader.SetSrcFile(const value: string);
begin
FSrcFile:=value;
end;
procedure TFtpUploader.SetTargetFtpFld(const value: string);
begin
FTargetFtpFld:=value;
end;
procedure TFtpUploader.StartMyUpload;
var
FtpUpStream: TFileStream;
begin
ftpUpStream:= TFileStream.create(FSrcFile, fmopenread)
try
with IdFTP do begin
Host:= FFtpSrvAdrs;
Username:='MyUserName';
Password:='MyPassword';
end;
IdFTP.Connect(true, 1200)
IdFTP.Passive:= true;
IdFTP.ChangeDir(FTargetFtpFld)
IdFTP.Put(ftpUpStream,FSrcFile, false);
finally
ftpUpStream.Free;
end;
end;
procedure TFtpUploader.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree;
end;
This Form will be created on RunTime ( 4 times = 4 buttons will launch it separately like this way :
in the main form i've this procedure :
Procedure MainForm.UploadTo(FTPSrv,SrcFile,FtpTargetFld:String);
var
FUploadFrm:TFtpUploader;
begin
FUploadFrm:=TFtpUploader.Create(nil);
if assigned(FUploadFrm) then
begin
FUploadFrm.FtpAdrs:=FTPSrv;
FUploadFrm.SourceFile:=SrcFile;
FUploadFrm.TargetFtpFld:=FtpTargetFld;
FUploadFrm.Show;
end;
end;
procedure MainForm.Button1Click(Sender: TObject);
begin
UploadTo('MyFtpSrv_1','MySrcFile_1','MyFtpTargetFld_1');
end;
procedure MainForm.Button2Click(Sender: TObject);
begin
UploadTo('MyFtpSrv_2','MySrcFile_2','MyFtpTargetFld_2');
end;
// same with other 2 buttons
the FtpUploader form is Created / Opened ( 4 instances ) ,The ISSUE IS when i click on StartUpload button the FTP upload process is not started on all these 4 instances , but i've to wait each upload process is done ( finished ) and the other will auto-start , that means not all upload processes are started in same time .
Thank you .
It seems you have to either change Indy library for some non-blocking in-background library (event based or completion port based), or to make your program multi-threading (with it's own bunch of problems like user clicking a button 20 times or closing the form while the process is going, or even closing the program on the run).
Based on http://otl.17slon.com/book/doku.php?id=book:highlevel:async it can look anything like this:
TFtpUploader = class(TForm)
private
CanCloseNow: boolean;
...
procedure TFtpUploader.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Self.CanCloseNow
then Action := caFree
else Action := caIgnore;
end;
procedure TFtpUploader.MyUploadComplete;
begin
Self.CanCloseNow := True;
Self.Close;
end;
procedure TFtpUploader.StartMyUpload;
begin
Self.CanCloseNow := false;
Self.Enabled := False;
Self.Visible := True;
Application.ProcessMessages;
Parallel.Async(
procedure
var
FtpUpStream: TFileStream;
begin
ftpUpStream:= TFileStream.create(FSrcFile, fmopenread)
try
with IdFTP do begin
Host:= FFtpSrvAdrs;
Username:='MyUserName';
Password:='MyPassword';
Connect(true, 1200)
Passive:= true;
ChangeDir(FTargetFtpFld)
// this does not return until uploaded
// thus would not give Delphi a chance to process buttons
// pressed on other forms.
Put(ftpUpStream,FSrcFile, false);
end;
finally
ftpUpStream.Free;
end;
end
,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
MyUploadComplete;
end;
);
end;
Or you can use simplier AsyncCalls library http://andy.jgknet.de/blog/bugfix-units/asynccalls-29-asynchronous-function-calls/

Creating replacement TApplication for experimentation?

I got the crazy idea one day to make a completely new replacement of TApplication for experimentation. I got everything to compile and run, and it does show the main form properly, everything responds good, but upon closing the form, the application does not halt. I'm sure I copied all the necessary stuff from the original Forms.pas TApplication (registering close event) but I don't see it working. I have to terminate the debug session the nasty way.
My goal in this little experiment is to build a lightweight application for very simple things instead of all the possible things a TApplication can handle, and also mostly so I have some good experience in such a field.
Here's the unit as I have it now, and below is the implementation of it.
unit JDForms;
interface
uses
Forms, Classes, SysUtils, StrUtils, Windows, Win7, XPMan, Variants,
Messages, Dialogs;
type
TJDForm = class;
TJDApplication = class;
TJDApplicationThread = class;
TJDForm = class(TCustomForm)
private
public
published
end;
TJDApplication = class(TComponent)
private
fRunning: Bool;
fTerminated: Bool;
fThread: TJDApplicationThread;
fMainForm: TJDForm;
fOnMessage: TMessageEvent;
fShowMainForm: Bool;
fHandle: HWND;
procedure ThreadTerminated(Sender: TObject);
procedure HandleMessage;
procedure ProcessMessages;
function ProcessMessage(var Msg: TMsg): Boolean;
procedure ThreadSync(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
property Thread: TJDApplicationThread read fThread;
procedure Initialize;
procedure Run;
procedure CreateForm(InstanceClass: TComponentClass; var Reference);
procedure Terminate;
property Terminated: Bool read fTerminated;
procedure HandleException(Sender: TObject);
property Handle: HWND read fHandle;
published
property ShowMainForm: Bool read fShowMainForm write fShowMainForm;
property OnMessage: TMessageEvent read fOnMessage write fOnMessage;
end;
TJDApplicationThread = class(TThread)
private
fOwner: TJDApplication;
fStop: Bool;
fOnSync: TNotifyEvent;
procedure DoSync;
protected
procedure Execute; override;
public
constructor Create(AOwner: TJDApplication);
destructor Destroy; override;
procedure Start;
procedure Stop;
published
property OnSync: TNotifyEvent read fOnSync write fOnSync;
end;
var
JDApplication: TJDApplication;
implementation
procedure DoneApplication;
begin
with JDApplication do begin
if Handle <> 0 then ShowOwnedPopups(Handle, False);
//ShowHint := False;
Destroying;
DestroyComponents;
end;
end;
{ TJDApplication }
constructor TJDApplication.Create(AOwner: TComponent);
begin
fRunning:= False;
fTerminated:= False;
fMainForm:= nil;
fThread:= TJDApplicationThread.Create(Self);
fThread.FreeOnTerminate:= True;
fThread.OnTerminate:= ThreadTerminated;
fShowMainForm:= True;
end;
procedure TJDApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var
Instance: TComponent;
begin
Instance:= TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
try
Instance.Create(Self);
except
TComponent(Reference):= nil;
raise;
end;
if (fMainForm = nil) and (Instance is TForm) then begin
TForm(Instance).HandleNeeded;
fMainForm:= TJDForm(Instance);
end;
end;
procedure TJDApplication.HandleException(Sender: TObject);
begin
{
if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
if ExceptObject is Exception then
begin
if not (ExceptObject is EAbort) then
if Assigned(FOnException) then
FOnException(Sender, Exception(ExceptObject))
else
ShowException(Exception(ExceptObject));
end else
SysUtils.ShowException(ExceptObject, ExceptAddr);
}
end;
procedure TJDApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then begin
//Idle(Msg);
end;
end;
function TJDApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
//if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
//not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end else begin
fTerminated:= True;
end;
end;
end;
procedure TJDApplication.ProcessMessages;
var
Msg: TMsg;
begin
while ProcessMessage(Msg) do {loop};
end;
procedure TJDApplication.Initialize;
begin
if InitProc <> nil then TProcedure(InitProc);
end;
procedure TJDApplication.Run;
begin {
fRunning := True;
try
AddExitProc(DoneApplication);
if FMainForm <> nil then
begin
case CmdShow of
SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;
SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if FMainForm.FWindowState = wsMinimized then
Minimize else
FMainForm.Visible := True;
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
end;
finally
FRunning := False;
end;
}
fRunning:= True;
try
AddExitProc(DoneApplication);
if fMainForm <> nil then begin
fHandle:= fMainForm.Handle;
if fShowMainForm then begin
fMainForm.Show;
end;
fThread.Start;
repeat
try
HandleMessage;
//--- THREAD HANDLING MESSAGES ---
except
HandleException(Self);
end;
until fTerminated;
end else begin
//Main form is nil - can not run
end;
finally
fRunning:= False;
fTerminated:= True;
end;
end;
procedure TJDApplication.Terminate;
begin
fTerminated:= True;
try
fThread.Stop;
except
end;
if CallTerminateProcs then PostQuitMessage(0);
end;
procedure TJDApplication.ThreadTerminated(Sender: TObject);
begin
//Free objects
end;
procedure TJDApplication.ThreadSync(Sender: TObject);
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then begin
//Idle(Msg);
end;
end;
{ TJDApplicationThread }
constructor TJDApplicationThread.Create(AOwner: TJDApplication);
begin
inherited Create(True);
fOwner:= AOwner;
end;
destructor TJDApplicationThread.Destroy;
begin
inherited;
end;
procedure TJDApplicationThread.DoSync;
begin
Self.fOwner.ThreadSync(Self);
// if assigned(fOnSync) then fOnSync(Self);
end;
procedure TJDApplicationThread.Execute;
var
ST: Integer;
begin
ST:= 5;
fStop:= False;
while (not Terminated) and (not fStop) do begin
//----- BEGIN -----
Synchronize(DoSync);
//----- END -----
//Sleep(1000 * ST);
end;
end;
procedure TJDApplicationThread.Start;
begin
fStop:= False;
Resume;
end;
procedure TJDApplicationThread.Stop;
begin
fStop:= True;
Suspend;
end;
initialization
JDApplication:= TJDApplication.Create(nil);
finalization
if assigned(JDApplication) then begin
JDApplication.Free;
JDApplication:= nil;
end;
end.
And here's an application using this:
program Win7FormTestD7;
uses
Forms,
W7Form1 in 'W7Form1.pas' {Win7Form1},
JDForms in 'JDForms.pas';
begin
JDApplication.Initialize;
JDApplication.CreateForm(TWin7Form1, Win7Form1);
JDApplication.Run;
end.
The form 'W7Form1' is just a plain form with a couple random controls on it to test with.
Users here should not ask the question of why I want to do this, I have my reasons. I learn by doing, not by someone showing me or by reading some book or finding a bunch of code which I don't know how it works. This is a way for me to better learn the workings of applications and be able to expand my knowledge in the field to be able to build more complex applications in the future.
Keep in mind that TCustomForm has no concept of your TJDApplication class, it only works with the Forms.TApplication class instead. Make sure your TJDApplication.Run() method is exiting when the Forms.TApplication.Terminated property has been set to True.
If building lightweight application is your motto, I suggest you to play around with :
The KOL Library
The SDA Framework
VCL Light by Paul TOTH
LVCL based on VCL Light code by SO member Arnaud Bouchez.

Resources