IdTcpClient Only Receive one time Windows Service - delphi

I don't know why but my windows service application only receive the information from my TcpServer one time (At Windows Service Startup), thread still running but always stuck at Service1.Cliente.IOHandler.ReadBytes(FData, szProtocol, False);
Tested on a normal windows application and works fine, but when move to windows service only receive one time and stop.
PS: The thread still running.
constructor TReadingThread.Create(AClient: TIdTCPClient);
begin
inherited Create(True);
FClient := AClient;
end;
procedure TReadingThread.Execute;
begin
FreeOnTerminate := False;
while not Terminated do
begin
if Service1.Cliente.Connected then
begin
if not Service1.Cliente.IOHandler.InputBufferIsEmpty then
begin
Service1.Cliente.IOHandler.ReadBytes(FData, szProtocol, False);
if (FData <> nil) and Assigned(FOnData) then Synchronize(DataReceived);
CriaLog('Received something');
end;
end;
Sleep(1);
end;
end;
procedure TReadingThread.DataReceived;
begin
if Assigned(FOnData) then FOnData(FData);
end;
The same code at normal application works fine, but when the application it's a windows service this problem happen.
Answer for Remy, Here is how szProtocol are defined and what more that use:
type
TCommand = (
cmdConnect,
cmdDisconnect,
cmdLibera);
type
TClient = record
HWID : String[40];
Msg : String[200];
end;
const
szClient = SizeOf(TClient);
type
TProtocol = record
Command: TCommand;
Sender: TClient;
DataSize: Integer;
end;
const
szProtocol = SizeOf(TProtocol);
My TThread Structure who i use to receive informations was defined as:
type
TDataEvent = procedure(const LBuffer: TIdBytes) of object;
TReadingThread = class(TThread)
private
FClient : TIdTCPClient;
FData : TIdBytes;
FOnData : TDataEvent;
procedure DataReceived;
protected
procedure Execute; override;
public
constructor Create(AClient: TIdTCPClient); reintroduce;
property OnData: TDataEvent read FOnData write FOnData;
end;
This procedure is who show me what are received from server and i do some actions.
procedure TService1.DataReceived(const LBuffer: TIdBytes);
type
PTBytes = ^TBytes;
PTIdBytes = ^TIdBytes;
var
LDataSize : Integer;
LProtocol : TProtocol;
begin
LProtocol := BytesToProtocol(PTBytes(#LBuffer)^);
case LProtocol.Command of
cmdLibera:
begin
// action
end;
end;
end;
and the others functions from TTHread structure:
constructor TReadingThread.Create(AClient: TIdTCPClient);
begin
inherited Create(True);
FClient := AClient;
end;
procedure TReadingThread.DataReceived;
begin
if Assigned(FOnData) then FOnData(FData);
end;
I know the code works because as i said i use it on a normal application (who isn't a service) and all works perfectly, but at service it don't work.

The answer is, just add a "packed" and solved the problem, Thanks Remy.

Related

Detecting changes in an editable TWebBrowser

I'm loading an HTML local file into TWebBrowser as follows:
procedure TForm1.FormCreate(Sender: TObject);
begin
WebBrowser1.Navigate('file:///C:\Tmp\input.html');
end;
In the TWebBrowser.OnDocumentComplete event handler I'm making it editable:
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; const URL: OleVariant);
begin
(WebBrowser1.Document as IHTMLDocument2).designMode := 'on';
end;
I need to be notified as soon as the user applies any changes through the TWebBrowser (i.e: he writes something...) but I can't see any OnChanged or similar event handler.
I've tried capturing WM_PASTE and WM_KEYDOWN but my code is never executed:
TMyWebBrowser = class(TWebBrowser)
public
procedure WM_Paste(var Message: TWMPaste); message WM_PASTE;
procedure WM_KeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
end;
...
procedure TMyWebBrowser.WM_Paste(var Message: TWMPaste);
begin
inherited;
ShowMessage('Paste');
end;
procedure TMyWebBrowser.WM_KEYDOWN(var Message: TWMKeyDown);
begin
inherited;
ShowMessage('KeyDown');
end;
I've also tried setting the WindowProc property but without any success.
To capture changes to the document in design mode you should use its IMarkupContainer2 interface to register an IHTMLChangeSink via RegisterForDirtyRange method. The process is pretty simple - implement IHTMLChangeSink, obtain IMarkupContainer2 from WebBrowser1.Document and call its RegisterForDirtyRange method, but there's a catch.
When you change the designMode of IHTMLDocument2, TWebBrowser control reloads the current document and it loses all registered change sinks. Therefore you should register it after putting the document in design mode. After that you receive change notifications via IHTMLChangeSink.Notify method.
But there's another catch. Since entering the design mode causes reloading of the document and that in turn causes changing the readyState property of the document to 'loading' and then consecutively to 'complete'. Your change sink will receive those readyState change notifications. Note that TWebBrowser.OnDocumentComplete is not invoked after entering design mode. That's why you should ignore any notifications until the document is fully reloaded in design mode.
Another minor complication is that RegisterForDirtyRange creates a cookie that you need to maintain in order to unregister the change sink. Since you need a class to implement IHTMLChangeSink anyway, it could also encapsulate the design mode state and change registration.
uses
System.SysUtils, SHDocVw, MSHTML;
const
DesignMode: array[Boolean] of string = ('off', 'on');
type
TWebBrowserDesign = class(TInterfacedObject, IHTMLChangeSink)
private
FDirtyRangeCookie: LongWord;
FDocumentComplete: Boolean;
FHTMLDocument2: IHTMLDocument2;
FMarkupContainer2: IMarkupContainer2;
FOnChange: TProc;
{ IHTMLChangeSink }
function Notify: HResult; stdcall;
public
constructor Create(WebBrowser: TWebBrowser; const AOnChange: TProc);
destructor Destroy; override;
end;
constructor TWebBrowserDesign.Create(WebBrowser: TWebBrowser; const AOnChange: TProc);
begin
inherited Create;
if not Assigned(WebBrowser) then
raise Exception.Create('Web browser control missing.');
if not Supports(WebBrowser.Document, IHTMLDocument2, FHTMLDocument2) then
raise Exception.Create('No HTML document loaded.');
FHTMLDocument2.designMode := DesignMode[True];
if Supports(WebBrowser.Document, IMarkupContainer2, FMarkupContainer2) then
begin
if FMarkupContainer2.RegisterForDirtyRange(Self, FDirtyRangeCookie) <> S_OK then
FDirtyRangeCookie := 0
else
_Release;
end;
FOnChange := AOnChange;
end;
destructor TWebBrowserDesign.Destroy;
begin
if Assigned(FMarkupContainer2) and (FDirtyRangeCookie <> 0) then
FMarkupContainer2.UnRegisterForDirtyRange(FDirtyRangeCookie);
if Assigned(FHTMLDocument2) then
FHTMLDocument2.designMode := DesignMode[False];
inherited;
end;
function TWebBrowserDesign.Notify: HResult;
begin
Result := S_OK;
if not FDocumentComplete then
FDocumentComplete := FHTMLDocument2.readyState = 'complete'
else if Assigned(FOnChange) then
FOnChange();
end;
Note the call to _Release after registering the change sink. This is to "prevent" markup container from holding strong reference to TWebBrowserDesign instance. That allows you to control design mode using the lifetime of TWebBrowserDesign instance:
type
TForm1 = class(TForm)
{ ... }
private
FWebBrowserDesign: IInterface;
{ ... }
end;
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; const URL: OleVariant);
begin
{ enter design mode }
FWebBrowserDesign := TWebBrowserDesign.Create(WebBrowser1, procedure
begin
ButtonSave.Enabled := True;
end);
end;
procedure TForm1.ButtonSave(Sender: TObject);
begin
{ exit design mode }
FWebBrowserDesign := nil;
ButtonSave.Enabled := False;
end;
Alternatively you can implement change sink as a component.
type
TWebBrowserDesign = class(TComponent, IHTMLChangeSink)
private
FDirtyRangeCookie: LongWord;
FDocumentComplete: Boolean;
FHTMLDocument2: IHTMLDocument2;
FMarkupContainer2: IMarkupContainer2;
FOnChange: TNotifyEvent;
FWebBrowser: TWebBrowser;
procedure EnterDesignMode;
procedure ExitDesignMode;
function GetActive: Boolean;
procedure SetActive(const Value: Boolean);
procedure SetWebBrowser(const Value: TWebBrowser);
{ IHTMLChangeSink }
function Notify: HResult; stdcall;
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
destructor Destroy; override;
published
property Active: Boolean read GetActive write SetActive;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property WebBrowser: TWebBrowser read FWebBrowser write SetWebBrowser;
end;
destructor TWebBrowserDesign.Destroy;
begin
ExitDesignMode;
inherited;
end;
procedure TWebBrowserDesign.EnterDesignMode;
begin
if not Assigned(FWebBrowser) then
raise Exception.Create('Web browser control missing.');
if not Supports(FWebBrowser.Document, IHTMLDocument2, FHTMLDocument2) then
raise Exception.Create('No HTML document loaded.');
try
FHTMLDocument2.designMode := DesignMode[True];
if Supports(FWebBrowser.Document, IMarkupContainer2, FMarkupContainer2) then
begin
if FMarkupContainer2.RegisterForDirtyRange(Self, FDirtyRangeCookie) <> S_OK then
FDirtyRangeCookie := 0;
end;
except
ExitDesignMode;
raise;
end;
end;
procedure TWebBrowserDesign.ExitDesignMode;
begin
if Assigned(FMarkupContainer2) then
begin
if FDirtyRangeCookie <> 0 then
begin
FMarkupContainer2.UnRegisterForDirtyRange(FDirtyRangeCookie);
FDirtyRangeCookie := 0;
end;
FMarkupContainer2 := nil;
end;
if Assigned(FHTMLDocument2) then
begin
FHTMLDocument2.designMode := DesignMode[False];
if not (csDestroying in ComponentState) then
FHTMLDocument2 := nil; { causes AV when its hosting TWebBrowser component is destroying; I didn't dig into details }
end;
FDocumentComplete := False;
end;
function TWebBrowserDesign.GetActive: Boolean;
begin
Result := Assigned(FHTMLDocument2);
end;
procedure TWebBrowserDesign.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent = FWebBrowser) then
WebBrowser := nil;
end;
function TWebBrowserDesign.Notify: HResult;
begin
Result := S_OK;
if not FDocumentComplete then
FDocumentComplete := FHTMLDocument2.readyState = 'complete'
else if Assigned(FOnChange) then
FOnChange(Self);
end;
procedure TWebBrowserDesign.SetActive(const Value: Boolean);
begin
if Active <> Value then
begin
if Value then
EnterDesignMode
else
ExitDesignMode;
end;
end;
procedure TWebBrowserDesign.SetWebBrowser(const Value: TWebBrowser);
begin
if Assigned(FWebBrowser) then
begin
ExitDesignMode;
FWebBrowser.RemoveFreeNotification(Self);
end;
FWebBrowser := Value;
if Assigned(FWebBrowser) then
FWebBrowser.FreeNotification(Self);
end;
If you put such a component in a design-time package and register it within the IDE, then you'll be able to link this component with TWebBrowser and assign OnChange event handler in the form designer. Use Active property in code to enter/exit the design mode.
type
TForm1 = class(TForm)
{ ... }
WebBrowserDesign1: TWebBrowserDesign;
{ ... }
end;
procedure WebBrowserDesign1Change(Sender: TObject);
begin
ButtonSave.Enabled := True;
end;
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; const URL: OleVariant);
begin
{ enter design mode }
WebBrowserDesign1.Active := True;
end;
procedure TForm1.ButtonSave(Sender: TObject);
begin
{ exit design mode }
WebBrowserDesign1.Active := False;
ButtonSave.Enabled := False;
end;
NB: Similar question has been asked regarding C#/WinForms - How do I detect when the content of a WebBrowser control has changed (in design mode)?
Final note: I'm not convinced that enabling save button after a change is the best UX design. If you think that the code above is worth to achieve your goal then go ahead. This is just a proof of concept and the code hasn't been thoroughly tested. Use it at your own risk.

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 handle Log in a Threaded manner

I have a form with a TMemo that I want to show what is going on in several services started by the application.
What I have running:
idHTTPServer running with idContext responding to requests
a Thread downloading updates from Dropbox
idUDPServer responding to UDP requests
another thread taking care of some database stuff.
the main application thread also needed to add log
Basically, I need to know how to create a standard, unified, thread safe way to channel the log messages to my TMemo and keep the user updated of what is going on.
Since you are already using Indy anyway, you can use Indy's TIdSync (synchronous) or TIdNotify (asynchronous) class to access the TMemo safely. For simple logging purposes, I would use TIdNotify, eg:
type
TLog = class(TIdNotify)
protected
FMsg: string;
procedure DoNotify; override;
public
class procedure LogMsg(const AMsg; string);
end;
procedure TLog.DoNotify;
begin
Form1.Memo1.Lines.Add(FMsg);
end;
class procedure TLog.LogMsg(const AMsg: string);
begin
with TLog.Create do
try
FMsg := AMsg;
Notify;
except
Free;
raise;
end;
end;
Then you can directly call it in any thread like this:
TLog.LogMsg('some text message here');
UPDATE: in Delphi 2009 and later, you can use anonymous procedures with the static versions of TThread.Synchronize() and TThread.Queue(), thus making Indy's TIdSync and TIdNotify classes obsolete, eg:
type
TLog = class
public
class procedure LogMsg(const AMsg; string);
end;
class procedure TLog.LogMsg(const AMsg: string);
begin
TThread.Queue(nil,
procedure
begin
Form1.Memo1.Lines.Add(AMsg);
end
);
end;
Basically, you can build a thread that receive all the message (here, it is a function AddEvent). Messages are queued (and timestamped) and written down to the memo when possible (if you're under heavy load...).
Don't forget to clean the memo if it exceeds a number of line, add exception handling etc...
I use something like this :
TThreadedMsgEvent = class( TThread )
private
FLock : TCriticalSection;
FStr : TQueue<String>;
FMemo : TMemo;
function GetEvent : String;
protected
procedure Execute; override;
public
procedure AddEvent( aMsg : String );
constructor Create( AMemo: TMemo );
destructor Destroy; override;
end;
implementation
{ TThreadedMsgEvent }
procedure TThreadedMsgEvent.AddEvent(aMsg: String);
begin
FLock.Acquire;
FStr.Enqueue( FormatDateTime('DD/MM/YY HH:NN:SS.ZZZ',Now)+ ' : '+ aMsg );
FLock.Release;
end;
constructor TThreadedMsgEvent.Create(aMemo: TMemo);
begin
inherited Create(True);
FreeOnTerminate := False;
FOnMessage := ACallBack;
FStr := TQueue<String>.Create();
FLock := TCriticalSection.Create;
FMemo := aMemo;
Resume;
end;
destructor TThreadedMsgEvent.Destroy; override;
begin
FreeAndNil( FStr );
FreeAndNil( FLock );
end;
procedure TThreadedMsgEvent.Execute;
begin
while not Terminated do
begin
try
if (FStr.Count > 0) then
begin
if Assigned( aMemo ) then
begin
TThread.synchronize( procedure
begin
FMemo.Lines.Add( GetEvent );
end; );
end;
end;
except
end;
TThread.Sleep(1);
end;
end;
function TThreadedMsgEvent.GetEvent: String;
begin
FLock.Acquire;
result := FStr.Dequeue;
FLock.Release;
end;
You can also notify this thread with Windows Messages. It might be easier as you won't need any reference to this thread in your classes.

IdHttpServer form caption not updating

I know i have posted a similar question before but i am not able to get it working I have this simple code :
procedure TfrmMain.srvrConnect(AContext: TIdContext); //idhttpserver on connect event
var
S,C : String;
begin
repeat
s := s + AContext.Connection.Socket.ReadChar;
until AContext.Connection.Socket.InputBufferIsEmpty = True;
frmMain.caption := S;
Memo1.Lines.Add(S);
end;
The strings displays ok in the memo but the caption doesn't get updated
TIdHTTPServer is a multi-threaded component. TIdContext runs in its own worker thread. You cannot safely update the Form's Caption (or do anything else with the UI) from outside of the main thread. You need to synchronize with the main thread, such as with the TIdSync or TIdNotify class.
On a side note, calling ReadChar() in a loop is very inefficient, not to mention error-prone if you are using Delphi 2009+ since it cannot return data for surrogate pairs.
Use something more like this instead;
type
TDataNotify = class(TIdNotify)
protected
Data: String;
procedure DoNotify; override;
public
constructor Create(const S: String);
class procedure DataAvailable(const S: String);
end;
constructor TDataNotify.Create(const S: String);
begin
inherited Create;
Data := S;
end;
procedure TDataNotify.DoNotify;
begin
frmMain.Caption := Data;
frmMain.Memo1.Lines.Add(Data);
end;
class procedure TDataNotify.DataAvailable(const S: String);
begin
Create(S).Notify;
end;
procedure TfrmMain.srvrConnect(AContext: TIdContext); //idhttpserver on connect event
var
S: String;
begin
AContext.Connection.IOHandler.CheckForDataOnSource(IdTimeoutDefault);
if not AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
S := AContext.Connection.IOHandler.InputBufferAsString;
TDataNotify.DataAvailable(S);
end;
end;
First, make sure you are writing to the right variable. Are you sure that frmMain is the form you want the caption do change?
Also, you could try:
procedure TfrmMain.srvrConnect(AContext: TIdContext); //idhttpserver on connect event
var
S,C : String;
begin
repeat
s := s + AContext.Connection.Socket.ReadChar;
until AContext.Connection.Socket.InputBufferIsEmpty = True;
oCaption := S;
TThread.Synchronize(nil, Self.ChangeCaption);
end;
procedure TfrmMain.ChangeCaption;
begin
Self.Caption := oCaption;
Memo1.Lines.Add(oCaption);
end;
And finally, make sure that the first line on S is not a blank line, because the form's caption will not show strings that contains a line feed.

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