Using Delphi 2010
Hi, I am looking for a way to break out of a loop using a key press (example 'x')
procedure TfrmMain.btnSpinClick(Sender: TObject);
function IsControlKeyPressed: Boolean;
begin
Result := GetKeyState(Ord('x')) < 0;
end;
var
ProductList: TStringList;
I, Integer;
begin
Screen.Cursor:= crHourGlass;
Spinning:= True;
UpdateAll;
Application.ProcessMessages;
//create a product list
ProductList:= TStringList.Create;
ProductList.LoadFromFile(edtProductsFile.Text);
Progressbar1.Min:= 1;
Progressbar1.Max:= ProductList.Count - 1;
//interate through the product list
//skip first line (its the field names) and start at the second line
for I:= 1 to ProductList.Count - 1 do
begin
//***************
//other code here
//***************
Progressbar1.Position:= Progressbar1.Position + 1;
***if IsControlKeyPressed then Break;
Application.ProcessMessages;***
end; //for I:= 1 to ProductList.Count - 1 do
ProductList.Clear;
ProductList.Free;
Thesaurus.Clear;
Thesaurus.Free;
Screen.Cursor:= crDefault;
Spinning:= False;
UpdateAll;
Application.ProcessMessages;
end;
Move your long-running code into a separate thread. In it, occasionally check whether a certain flag is set. When it's set, stop.
Then, write an OnKeyPress event handler for your form. When that event handler detects that the magic key combination has been pressed, set the flag. That will cause the thread to stop doing its work.
It could work something like this:
type
TProcessProductListThread = class(TThread)
private
FFileName: string;
FProgressBar: TProgressBar;
FMax: Integer;
procedure SetProgressBarRange;
procedure IncrementProgressBar;
procedure ProcessProduct(const AProduct: string);
protected
procedure Execute; override;
public
constructor Create(const AFileName: string; AProgressBar: TProgressBar;
OnThreadTerminate: TNotifyEvent);
end;
The constructor receives all the information it will need to do its work, but doesn't actually start doing any of it. That's reserved for the Execute method. We set FreeOnTerminate := False because the main thread will need to continue to have access to the thread object after it's begun running.
constructor TProcessProductListThread.Create(const AFileName: string;
AProgressBar: TProgressBar; OnThreadTerminate: TNotifyEvent);
begin
inherited Create(False);
FFileName := AFileName;
FProgressBar := AProgressBar;
OnTerminate := OnThreadTerminate;
FreeOnTerminate := False;
end;
Your code interacts with the GUI in a couple of places. That needs to happen from the GUI thread, so we'll extract that code into separate methods that can be passed to Synchronize:
procedure TProcessProductList.SetProgressBarRange);
begin
FProgressBar.Min := 1;
FProgressBar.Position := FProgressBar.Min;
FProgressBar.Max := FMax;
end;
procedure TProcessProduceList.IncrementProgressBar;
begin
FProgressBar.Position := FProgressBar.Position + 1;
end;
You'll notice that the Execute method looks similar to your original code. Notice how it uses the values previously saved from the constructor.
procedure TProcessProductList.Execute;
var
ProductList: TStringList;
I: Integer;
begin
ProductList := TStringList.Create;
try
ProductList.LoadFromFile(FFileName);
FMax := ProductList.Count - 1;
Synchronize(SetProgressBarRange);
// skip first line (it's the field names) and start at the second line
for I := 1 to ProductList.Count - 1 do begin
ProcessProduct(ProductList[I]);
Synchronize(IncrementProgressBar);
if Terminated then
exit;
end;
finally
ProductList.Free;
end;
end;
To start the thread, create it like this:
ProcessThread := TProcessProductList.Create(edtProductsFile.Text, Progressbar1,
OnProcessProductListTerminate);
Handle the termination with an event handler like below. It's mostly the stuff from the epilogue of your original code, but it also clears ProcessThread; that way, its value can indicate whether the thread is still running.
procedure TForm1.OnProcessProductListTerminate(Sender: TObject);
begin
Thesaurus.Clear;
Thesaurus.Free;
UpdateAll;
ProcessThread := nil;
end;
Remember that I said you should set a flag when the key is pressed? In the code above, the flag it checks is simply the thread's own Terminated property. To set it, call the thread's Terminate method.
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
if Char = 'X' then begin
ProcessThread.Terminate;
ProcessThread.Free;
Char := #0;
end;
end;
Related
I have a simple class that has the following interface implementation.
Note: TPolyBase is an abstract class and TPolyResult is an array of double; it's not important to see their code, it's not relevant here.
//INTERFACE
type
TPolynomialList = class
strict private
FPolynomialList: TObjectList<TPolyBase>;
FResult: TList<TPolyResult>;
FCanGet: boolean;
function GetResult: TList<TPolyResult>;
procedure DoSolve;
public
constructor Create(PolynomialList: TObjectList<TPolyBase>);
destructor Destroy; override;
procedure SolvePolynomials(CompletionHandler: TProc);
property Solutions: TList<TPolyResult> read GetResult;
end;
//IMPLEMENTATION
constructor TPolynomialList.Create(PolynomialList: TObjectList<TPolyBase>);
begin
FPolynomialList := PolynomialList;
FResult := TList<TPolyResult>.Create;
FCanGet := false;
end;
destructor TPolynomialList.Destroy;
begin
FResult.Free;
inherited;
end;
procedure TPolynomialList.DoSolve;
var
i: integer;
begin
for i := 0 to FPolynomialList.Count - 1 do
FResult.Add(FPolynomialList[i].GetSolutions);
FCanGet := true;
end;
function TPolynomialList.GetResult: TList<TPolyResult>;
begin
if FCanGet = false then
raise TEquationError.Create('You must solve the equation first!');
Result := FResult;
end;
procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
TTask.Run(procedure
var
ex: TObject;
begin
try
DoSolve;
TThread.Synchronize(nil, procedure
begin
CompletionHandler;
end);
except
on E: Exception do
begin
ex := AcquireExceptionObject;
TThread.Synchronize(nil, procedure
begin
Writeln( (ex as Exception).Message );
end);
end;
end;
end);
end;
This class takes a list of objects as input and it has an internal important field called FResult that gives the results to the user. It can be accessed from the getter only if the method SolvePolynomials has finished his work.
The problem is in the SolvePolynomials. The code I have shown uses a task because the size of the object list may be very big and I don't want to freeze the UI. Why do I always get an access violation in the task code?
Note that the following code works fine but this is not what I want because if I input 15000 the program freezes for a few seconds.
procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
DoSolve;
CompletionHandler;
end;
Could the FPolynomialList variable be a the problem? If you look at my class the only thing "taken from outside" is the TObjectList<TPolyBase> because in the constructor I simply assing the reference (I'd like to avoid the copy ok 15k items). All the other variables are not shared with anything.
I have seen in many books I have read like "Delphi High Performance" that is good to have a task that calls an inner "slow" method but in this case there could be those reference that are messing up something. Any idea?
This is the code that I am using as test:
var
a: TObjectList<TPolyBase>;
i, j: integer;
f: TPolynomialList;
s: string;
function GetRandom: integer;
begin
Result := (Random(10) + 1);
end;
begin
a := TObjectList<TPolyBase>.Create(true);
try
for i := 0 to 15000 do
begin
a.Add({*Descendant of TPolyBase*})
end;
f := TPolynomialList.Create(a);
try
f.SolvePolynomials(procedure
var
i, j: integer;
begin
for i := 0 to f.Solutions.Count - 1 do
begin
for j := Low(f.Solutions[i]) to High(f.Solutions[i]) do
Writeln({output the results...})
end;
end);
finally
f.Free;
end;
finally
a.Free;
end;
end.
Your SolvePolynomials method delegates solving to another thread and returns before that thread is finished with its task. While that task thread is running it is necessary that all data it operates on is still alive. But, in your code you are releasing necessary object instances right after SolvePolynomials exits - while your task is still running, hence the error.
You have to move releasing of those objects into completion handler.
Basically, your code simplified looks like:
type
TPolynomialList = class
public
destructor Destroy; override;
procedure DoSolve;
procedure SolvePolynomials(CompletionHandler: TProc);
end;
destructor TPolynomialList.Destroy;
begin
Writeln('Destroyed');
inherited;
end;
procedure TPolynomialList.DoSolve;
begin
Writeln('Solving');
end;
procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
TTask.Run(
procedure
begin
try
DoSolve;
TThread.Synchronize(nil,
procedure
begin
CompletionHandler;
end);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end);
end;
procedure Test;
var
f: TPolynomialList;
begin
f := TPolynomialList.Create;
try
f.SolvePolynomials(
procedure
begin
Writeln('Solved');
end);
finally
f.Free;
end;
end;
If you run it output will be:
Destroyed
Solving
Solved
However, if you move releasing of your variables into completion handler order of execution will be correct.
procedure Test;
var
f: TPolynomialList;
begin
f := TPolynomialList.Create;
f.SolvePolynomials(
procedure
begin
Writeln('Solved');
f.Free;
end);
end;
Solving
Solved
Destroyed
For your code, that means moving both a.Free and f.Free into completion handler.
This is my example code, I use Waitfor to wait for a thread finish
TCPThread = class(TThread)
protected
procedure Execute; override;
public
Source, Dest: String;
FHandle:THandle;
constructor Create(Source1, Dest1: string; TFHandle1: THandle);
end;
............
constructor TCPThread.Create(Source1, Dest1: string; TFHandle1: THandle);
begin
inherited Create(False);
Source:=Source1;
Dest:=Dest1;
FHandle:=TFHandle1;
end;
procedure TCPThread.Execute;
var
Cancel : PBool;
begin
Cancel := PBOOL(False);
CopyFileEx(PChar(Source), PChar(Dest), #CopyFileProgress, Pointer(FHandle), Cancel, 0);
end;
The progress bar is working well, but I can not click on any button and anywhere, e.g cancel button.
I need to wait for the files to be copied or can cancel it if necessary and cleanup
CPThread := TCPThread.Create('D:\test.iso', 'D:\test2.iso',FHandle);
CPThread.WaitFor;
CPThread.Destroy;
TThread.WaitFor() blocks the calling thread until the thread is terminated. When called in the context of the main UI thread, WaitFor() does not process pending window messages (but does process pending TThread.Synchronize() and TThread.Queue() requests). That is why you cannot click on anything.
For what you are attempting to do, don't wait on the thread at all. Let it run normally while you return control back to the main UI message loop, and let the thread tell you when it is finished with its work.
Also, you are misusing the pbCancel parameter of CopyFileEx().
Try something more like this:
type
TCPThread = class(TThread)
private
Cancel : BOOL;
Source, Dest: String;
FHandle: THandle;
protected
procedure Execute; override;
procedure TerminatedSet; override;
public
constructor Create(Source1, Dest1: string; TFHandle1: THandle);
end;
constructor TCPThread.Create(Source1, Dest1: string; TFHandle1: THandle);
begin
inherited Create(True);
FreeOnTerminate := True;
Source := Source1;
Dest := Dest1;
FHandle := TFHandle1;
end;
procedure TCPThread.Execute;
begin
if not CopyFileEx(PChar(Source), PChar(Dest), #CopyFileProgress, Pointer(FHandle), #Cancel, 0) then
ReturnValue := GetLastError;
end;
procedure TCPThread.TerminatedSet;
begin
Cancel := True;
end;
var
CPThread: TCPThread = nil;
procedure TMyForm.CopyButtonClick(Sender: TObject);
begin
CPThread := TCPThread.Create('D:\test.iso', 'D:\test2.iso', FHandle);
CPThread.OnTerminate := CopyFinished;
CPThread.Start;
CopyButton.Enabled := False;
CancelButton.Enabled := True;
end;
procedure TMyForm.CancelButtonClick(Sender: TObject);
begin
if CPThread <> nil then
CPThread.Terminate;
end;
procedure TMyForm.CopyFinished(Sender: TObject);
begin
CPThread := nil;
CancelButton.Enabled := False;
if TCPThread(Sender).FatalException <> nil then
begin
// thread terminated by uncaught exception, do something...
end
else if TCPThread(Sender).ReturnValue <> 0 then
begin
// CopyFileEx() failed, do something...
end
else
begin
// CopyFileEx() succeeded, do something...
end
CopyButton.Enabled := True;
end;
I wrote such module to store there last changes of picture in my paint application " in Delphi
unit HistoryQueue;
interface
uses
Graphics;
type
myHistory = class
constructor Create(Size:Integer);
public
procedure Push(Bmp:TBitmap);
function Pop():TBitmap;
procedure Clean();
procedure Offset();
function isEmpty():boolean;
function isFull():boolean;
function getLast():TBitmap;
protected
historyQueueArray: array of TBitmap;
historyIndex, hSize:Integer;
end;
implementation
procedure myHistory.Push(Bmp:TBitmap);
var tbmp:TBitmap;
begin
if(not isFull) then begin
Inc(historyIndex);
historyQueueArray[historyIndex]:=TBitmap.Create;
historyQueueArray[historyIndex].Assign(bmp);
end else begin
Offset();
historyQueueArray[historyIndex]:=TBitmap.Create;
historyQueueArray[historyIndex].Assign(bmp);
end;
end;
procedure myHistory.Clean;
var i:Integer;
begin
{ for i:=0 to hSize do begin
historyQueueArray[i].Free;
historyQueueArray[i].Destroy;
end; }
end;
constructor myHistory.Create(Size:Integer);
begin
hSize:=Size;
SetLength(historyQueueArray, hSize);
historyIndex:=-1;
end;
function myHistory.isEmpty: boolean;
begin
Result:=(historyIndex = -1);
end;
function myHistory.isFull: boolean;
begin
Result:=(historyIndex = hSize);
end;
procedure myHistory.Offset; {to handle overflow}
var i:integer;
begin
//historyQueueArray[0]:=nil;
for i:=0 to hSize-1 do begin
historyQueueArray[i]:=TBitmap.Create;
historyQueueArray[i].Assign(historyQueueArray[i+1]);
end;
end;
function myHistory.Pop: TBitmap;
var
popBmp:TBitmap;
begin
popBmp:= TBitmap.Create;
popBmp.Assign(historyQueueArray[historyIndex]);
Dec(historyIndex);
Result:=popBmp;
end;
function myHistory.getLast: TBitmap; {this function I use when I need refresh the cnvas when I draw ellipse or rect, to get rid of traces and safe previous changes of the picture}
var
tBmp:TBitmap;
begin
tBmp:= TBitmap.Create;
tBmp.Assign(historyQueueArray[historyIndex]);
Result:=tBmp;
end;
end.
And thats how I use it
procedure TMainForm.FormCreate(Sender: TObject);
var
cleanBmp:TBitmap;
begin
{...}
doneRedo:=false;
redomode:=false; undomode:=false;
//init arrays
picHistory:=myHistory.Create(10); //FOR UNDO
tempHistory:=myHistory.Create(10); //FOR REDO
cleanbmp:=TBitmap.Create;
cleanbmp.Assign(imgMain.Picture.Bitmap);
picHistory.Push(cleanbmp);
cleanbmp.Free;
{...}
end;
procedure TMainForm.btnUndoClick(Sender: TObject);
var redBmp:TBitmap;
begin
undoMode:=true;
//if there were some changes
if(not picHistory.isEmpty) then begin
redBmp:=TBitmap.Create;
redBmp.Assign(picHistory.getLast);
//clean canvas
imgMain.Picture.Bitmap:=nil;
//get what was there before
imgMain.Canvas.Draw(0,0, picHistory.Pop);
//and in case if we will not make any changes after UNDO(clicked one or more times)
//and call REDO then
tempHistory.Push(redBmp);//we save what were on canvas before UNDOand push it to redo history
redBmp.Free;
end;
end;
procedure TMainForm.btnRedoClick(Sender: TObject);
var undBmp:TBitmap;
begin
redoMode:=true;
if(not tempHistory.isEmpty) then begin
doneRedo:=True;
undBmp:=TBitmap.Create;
undBmp.Assign(tempHistory.getLast);
imgMain.Picture.Bitmap:=nil;
MainForm.imgMain.Canvas.Draw(0,0, tempHistory.Pop);
//same history (like with UNDO implementation) here but reverse
picHistory.Push(undBmp);
undBmp.Free;
end;
end;
{...}
procedure TMainForm.imgMainMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var bmp:TBitmap;
begin
//if mouse were down and then it's up this means we drew something
//and must save changes into history to be able to make UNDO
{...}
bmp:=TBitmap.Create;
try
bmp.Assign(imgMain.Picture.Bitmap);
picHistory.Push(bmp);
//if there are some changes added after redo then we clean redo history
if (doneRedo) then begin
tempHistory.Clean;
doneRedo:=false;
end;
finally
bmp.Free;
//sor of refresh
imgMain.Canvas.Draw(0,0, picHistory.getLast);
end;
{...}
But the problem is it works not way I expected. an example:
If I push undo button once - nothing happens. On twice - it does what it should at once.
And if I drew an ellipse, then click undo once and start draw new one - last drawn ellipse just dissaperas!
Here's the elipse draw method in case if it could be helpful to find out the problem
procedure TMainForm.ellipseDraw(X, Y: Integer);
begin
imgMain.Canvas.Pen.Color:=useColor;
imgMain.Canvas.Brush.Color:=scndColor;
imgMain.Canvas.Pen.Width:=size;
if(mouseIsDown) then begin
imgMain.Canvas.Draw(0,0, picHistory.getLast); //there gonna be no bizzare traces from figures
imgMain.Canvas.Ellipse(dX, dY, X,Y);
end;
end;
Answer
If I push undo button once - nothing happens. On twice - it does what it should at once.
That is indeed exactly what your code does:
In imgMainMouseUp you add the current picture to the undo list, and
In btnUndoClick you retrieve the last bitmap from the undo list, which is the same as currently seen on the Image.
The solution - to this specific question - is to add the previous bitmap to the undo list instead of the current one.
Bonus
And to address David's comment concerning the leaking, your implementation leaks Bitmaps because:
The routines Pop and getLast return a newly local created Bitmap. This places the responsibility for its destruction on the caller ot the routines. Your MainForm code does not destroy those Bitmaps, thus they are memory leaks. The solution is to simply return the item in the array, instead of creating a new Bitmap.
In the Offset routine, you again create new Bitmaps and leak all previous ones. Just assign Queue[I] to Queue[I + 1].
In the Push method, you forget to free the last item.
The class does not have a destructor, which again places the responsibility for the destruction of all Bitmaps on the user of the object with the need to call Clean, which it does not. The solution is to add a destructor to your object which calls Clean.
Besides these leaks, there are more problems with your code. Here some fixes and tips:
Since dynamic arrays are zero-based, your isFull routine does not return True when it should. It should be implemented as Result := historyIndex = hSize - 1;
Your array is not a queue (FIFO), but a stack (LIFO).
Your Pop routine does not check for an empty list.
Altogether, your history class could better look like:
uses
SysUtils, Graphics;
type
TBitmapHistory = class(TObject)
private
FIndex: Integer;
FStack: array of TBitmap;
procedure Offset;
public
procedure Clear;
function Count: Integer;
constructor Create(ACount: Integer);
destructor Destroy; override;
function Empty: Boolean;
function Full: Boolean;
function Last: TBitmap;
function Pop: TBitmap;
procedure Push(ABitmap: TBitmap);
end;
implementation
{ TBitmapHistory }
procedure TBitmapHistory.Clear;
var
I: Integer;
begin
for I := 0 to Count - 1 do
FreeAndNil(FStack[I]);
FIndex := -1;
end;
function TBitmapHistory.Count: Integer;
begin
Result := Length(FStack);
end;
constructor TBitmapHistory.Create(ACount: Integer);
begin
inherited Create;
SetLength(FStack, ACount);
FIndex := -1;
end;
destructor TBitmapHistory.Destroy;
begin
Clear;
inherited Destroy;
end;
function TBitmapHistory.Empty: Boolean;
begin
Result := FIndex = -1;
end;
function TBitmapHistory.Full: Boolean;
begin
Result := FIndex = Count - 1;
end;
function TBitmapHistory.Last: TBitmap;
begin
if Empty then
Result := nil
else
Result := FStack[FIndex];
end;
procedure TBitmapHistory.Offset;
begin
FStack[0].Free;
Move(FStack[1], FStack[0], (Count - 1) * SizeOf(TBitmap));
end;
function TBitmapHistory.Pop: TBitmap;
begin
if not Empty then
begin
Result := Last;
Dec(FIndex);
end;
end;
procedure TBitmapHistory.Push(ABitmap: TBitmap);
begin
if Full then
Offset
else
Inc(FIndex);
FStack[Findex].Free;
FStack[FIndex] := TBitmap.Create;
FStack[Findex].Assign(ABitmap);
end;
Remarks:
There also exists a specialized class TObjectStack for this in the Contnrs unit which you could override/exploit.
There are also concerns with your MainForm code, but I politely leave that up to you to fix.
Overview:
I have an application were we want to listen/test events, the ideal is
to pass an event name to a stored procedure and be able to report
back it event was successful and/or unsuccessful.
Issue:
Event is only getting executed the first time, subsequent event don’t
get executed. (see NotifyEventAlert)
Stored Procedure:
CREATE PROCEDURE TESTEVENT(EVENTNAME VARCHAR(65))
AS
BEGIN
POST_EVENT(:EVENTNAME);
END;
NotifyEventAlert:
//Note event is only getting excuted the first time
procedure TObjApplicationEvents.NotifyEventAlert(Sender: TObject;
EventName: string; EventCount: Integer; var CancelAlerts: Boolean);
begin
if Assigned(Self.OnPostedEvent) then
Self.OnPostedEvent(Self, Eventname);
end;
EventPosting :
procedure TFrmEventProcessingTestLkp.btnTestEventClick(Sender: TObject);
var
c : TCursor;
ae : TObjApplicationEvents;
cq : TCustomQuery;
begin
inherited;
c := Screen.Cursor;
Screen.Cursor := crHourglass;
ae := nil;
cq := nil;
try
try
ae := TObjApplicationEvents.Create;
ae.OnPostedEvent := Self.OnPostedEvent;
cq := TCustomQuery.Create(nil);
cq.StartTransaction;
cq.SetOperationType(cqotStoredProcedure);
cq.SetStoredProcName('TESTEVENT');
cq.Params.CreateParam(ftString, 'EVENTNAME', ptInput);
cq.ParamByName('EVENTNAME').Value := GetRecordColumnValue(cxColEvent.Index);
cq.ExecSQL;
cq.CommitTransaction;
Sleep(1000);
except on e: Exception do
begin
cq.RollBackTransaction;
raise Exception.Create(e.message);
end;
end;
finally
FreeAndNil(ae);
FreeAndNil(cq);
Screen.Cursor:= c;
end;
end;
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.