From what I have understood reading Nick Hodges, this code should be fine:
TTask.Run(
procedure
var
resp, tmp: string;
req: boolean;
bwriter: TBinaryWriter;
myfile: TFileStream;
begin
//tell the user to wait
TThread.Queue(TThread.CurrentThread,
procedure
begin
LoginButton.Text := 'Please wait...';
end
);
//some checks
try
resp := GetURL('... here I get a result from the server...');
if (resp = fOKstatus) then
begin
req := true;
myfile := TFileStream.Create(TPath.Combine(TPath.GetHomePath, 'docs.mkb'), fmCreate);
try
bwriter := TBinaryWriter.Create(myfile, TEncoding.Unicode, false);
try
bwriter.Write(UsernameEdit.Text);
bwriter.Write(AppIDEdit.Text);
bwriter.Close;
finally
bwriter.Free;
end;
finally
myfile.Free;
end;
end
else
begin
req := false;
end;
except
req := false;
end;
//final
TThread.Queue(TThread.CurrentThread,
procedure
begin
if (req = true) then
begin
LoginButton.Text := 'Success!';
ShowMessage('Close the app to complete the registration.');
end
else
begin
LoginButton.Text := 'Login failed.';
end;
end
);
end
);
This runs in a separated thread, and it is linked to the main thread with the calls to Queue(). In fact, at the beginning I am updating the Text of a Button using this method.
QUESTION. Look at these 2 lines:
bwriter.Write(UsernameEdit.Text);
bwriter.Write(AppIDEdit.Text);
I need to retrieve the username and AppID (which is a random code) from two Edit controls that are in the main thread UI. Is this correct?
I guess that I should call Queue(), but so far the program is working well.
Can I take the values in this way safely? I am not updating anything, and I just need to grab the data, but I am not sure if mixing contents from 2 different tasks can be dangerous/bad practice.
The 2 lines of code you are concerned about are NOT thread-safe. You must synchronize with the main thread for all UI access, both reading and writing. TThread.Queue() is asynchronous, so it is not suitable for the purpose of retrieving values from the UI. Use TThread.Synchronize() instead, which is synchronous:
TTask.Run(
procedure
var
resp, tmp, username, appid: string;
req: boolean;
bwriter: TBinaryWriter;
myfile: TFileStream;
begin
//tell the user to wait
TThread.Queue(nil,
procedure
begin
LoginButton.Text := 'Please wait...';
end
);
//some checks
try
resp := GetURL('... here I get a result from the server...');
if resp = fOKstatus then
begin
req := true;
TThread.Synchronize(nil,
procedure
begin
username := UsernameEdit.Text;
appid := AppIDEdit.Text;
end
);
myfile := TFileStream.Create(TPath.Combine(TPath.GetHomePath, 'docs.mkb'), fmCreate);
try
bwriter := TBinaryWriter.Create(myfile, TEncoding.Unicode, false);
try
bwriter.Write(username);
bwriter.Write(appid);
bwriter.Close;
finally
bwriter.Free;
end;
finally
myfile.Free;
end;
end
else
begin
req := false;
end;
except
req := false;
end;
//final
TThread.Queue(nil,
procedure
begin
if req then
begin
LoginButton.Text := 'Success!';
ShowMessage('Close the app to complete the registration.');
end
else
begin
LoginButton.Text := 'Login failed.';
end;
end
);
end
);
Alternatively, assuming the main UI thread is the one starting the TTask, you can read the 2 values before starting the TTask and let the anonymous procedure capture them:
var
username, appid: string;
begin
username := UsernameEdit.Text;
appid := AppIDEdit.Text;
TTask.Run(
procedure
var
resp, tmp: string;
req: boolean;
bwriter: TBinaryWriter;
myfile: TFileStream;
begin
//tell the user to wait
TThread.Queue(nil,
procedure
begin
LoginButton.Text := 'Please wait...';
end
);
//some checks
try
resp := GetURL('... here I get a result from the server...');
if resp = fOKstatus then
begin
req := true;
myfile := TFileStream.Create(TPath.Combine(TPath.GetHomePath, 'docs.mkb'), fmCreate);
try
bwriter := TBinaryWriter.Create(myfile, TEncoding.Unicode, false);
try
bwriter.Write(username);
bwriter.Write(appid);
bwriter.Close;
finally
bwriter.Free;
end;
finally
myfile.Free;
end;
end
else
begin
req := false;
end;
except
req := false;
end;
//final
TThread.Queue(nil,
procedure
begin
if req then
begin
LoginButton.Text := 'Success!';
ShowMessage('Close the app to complete the registration.');
end
else
begin
LoginButton.Text := 'Login failed.';
end;
end
);
end
);
end;
Related
Function
function DownloadString(AUrl: string): string;
var
LHttp: TIdHttp;
begin
LHttp := TIdHTTP.Create;
try
LHttp.HandleRedirects := true;
result := LHttp.Get('http://127.0.0.1/a.php?n='+AUrl);
finally
LHttp.Free;
end;
end;
Boot
procedure TForm1.Button1Click(Sender: TObject);
var
LUrlArray: TArray<String>;
begin
LUrlArray := form1.listbox1.Items.ToStringArray;
TThread.CreateAnonymousThread(
procedure
var
LResult: string;
LUrl: string;
begin
for LUrl in LUrlArray do
begin
LResult := DownloadString(LUrl);
TThread.Synchronize(nil,
procedure
begin
if Pos('DENEGADA',LResult)>0 then
begin
Memo1.Lines.Add(LResult);
end
else
begin
Memo1.Lines.Add(LResult + 'DIE');
end;
end
);
end;
end
).Start;
end;
Listbox Lines
http://127.0.0.1/a.php?n=4984
http://127.0.0.1/a.php?n=4986
http://127.0.0.1/a.php?n=4989
in this case only one thread will download all URL's content but I would like to make it creates a thread for each item...
example:
thread1 - check item1 listbox - http://127.0.0.1/a.php?n=4984
thread2 - check next item 4986
thread3 - check next item 4989
how make this? Is there any way to do this ?, I believe that this method will be more effective.
In order to create separate threads, you have to bind the url variable value like this:
procedure TForm1.Button1Click(Sender: TObject);
var
LUrlArray: TArray<String>;
LUrl: String;
function CaptureThreadTask(const s: String) : TProc;
begin
Result :=
procedure
var
LResult : String;
begin
LResult := DownloadString(s);
TThread.Synchronize(nil,
procedure
begin
if Pos('DENEGADA',LResult)>0 then
begin
Memo1.Lines.Add(LResult);
end
else
begin
Memo1.Lines.Add(LResult + 'DIE');
end;
end
);
end;
end;
begin
LUrlArray := form1.listbox1.Items.ToStringArray;
for LUrl in LUrlArray do
// Bind variable LUrl value like this
TThread.CreateAnonymousThread( CaptureThreadTask(LUrl)
).Start;
end;
See Anonymous Methods Variable Binding
You can try using ForEach pattern of omnithreadlibrary :
http://otl.17slon.com/book/chap04.html#highlevel-foreach
http://otl.17slon.com/book/chap04.html#leanpub-auto-iomniblockingcollection
Draft is like that:
TMyForm = class(TForm)
private
DownloadedStrings: iOmniBlockingCollection;
published
DownloadingProgress: TTimer;
MemoSourceURLs: TMemo;
MemoResults: TMemo;
...
published
procedure DownloadingProgressOnTimer( Sender: TObject );
procedure StartButtonClick ( Sender: TObject );
.....
private
property InDownloadProcess: boolean write SetInDownloadProcess;
procedure FlushCollectedData;
end;
procedure TMyForm.StartButtonClick ( Sender: TObject );
begin
DownloadedStrings := TOmniBlockingCollection.Create;
Parallel.ForEach<string>(MemoSourceURLs.Lines)
.NumTasks(10) // we do not want to overload computer by millions of threads when given a long list. We are not "fork bomb"
// .PreserveOrder - usually not a needed option
.Into(DownloadedStrings) // - or you would have to manually seal the container by calling .CompleteAdding AFTER the loop is over in .OnStop option
.NoWait
.Execute(
procedure (const URL: string; var res: TOmniValue)
var Data: string; Success: Boolean;
begin
if my_IsValidUrl(URL) then begin
Success := my_DownloadString( URL, Data);
if Success and my_IsValidData(Data) then begin
if ContainsText(Data, 'denegada') then
Data := Data + ' DIE';
res := Data;
end;
end
);
InDownloadProcess := true;
end;
procedure TMyForm.SetInDownloadProcess(const process: Boolean);
begin
if process then begin
StartButton.Hide;
Prohibit-Form-Closing := true;
MemoSourceURLs.ReadOnly := true;
MemoResults.Clear;
with DownloadingProgress do begin
Interval := 333; // update data in form 3 times per second - often enough
OnTimer := DownloadingProgressOnTimer;
Enabled := True;
end;
end else begin
DownloadingProgress.Enabled := false;
if nil <> DownloadedStrings then
FlushCollectedData; // one last time
Prohibit-Form-Closing := false;
MemoSourceURLs.ReadOnly := false;
StartButton.Show;
end;
end;
procedure TMyForm.FlushCollectedData;
var s: string; value: TOmniValue;
begin
while DownloadedStrings.TryTake(value) do begin
s := value;
MemoResults.Lines.Add(s);
end;
PostMessage( MemoResults.Handle, .... ); // not SendMessage, not Perform
// I do not remember, there was something very easy to make the memo auto-scroll to the last line added
end;
procedure TMyForm.DownloadingProgressOnTimer( Sender: TObject );
begin
if nil = DownloadedStrings then begin
InDownloadProcess := false;
exit;
end;
FlushCollectedData;
if DownloadedStrings.IsCompleted then begin
InDownloadProcess := false; // The ForEach loop is over, everything was downloaded
DownloadedStrings := nil; // free memory
end;
end;
http://docwiki.embarcadero.com/Libraries/XE4/en/System.StrUtils.ContainsText
http://docwiki.embarcadero.com/Libraries/Seattle/en/Vcl.ExtCtrls.TTimer_Properties
PS. note that the online version of the book is old, you perhaps would have to update it to features in the current version of the omnithreadlibrarysources.
PPS: your code has a subtle error:
for LUrl in LUrlArray do
begin
LResult := DownloadString(LUrl);
Given your implementation of DownloadString that means in the case of HTTP error your function would re-return the previous value of LResult again and again and again and.... until the no-error downloading happened.
That is why I changed your function definition to be clear when error happens and no output data is given.
UPDATE Problem Still Exists.
Is it possible to run code in already running thread? for example:
thread1 is running some code & i want to run code from thread2 in thread1.
I want to run code in idTCPServer thread to send some data to client
Edit:
After research seems that my problem is that when Client data is received or is receiving same time another thread is trying to write to that socket.
Edit:
procedure TMainFrm.UserSRVExecute(AContext: TIdContext);
var
Command : String;
msSize : Int64;
ms : TMemoryStream;
decompressedMS : TMemoryStream;
H : TIdNotify;
begin
// Application.ProcessMessages;
Command := AContext.Connection.Socket.ReadLn;
// messagebox(0,'snd','',$40);
if logb then mainfrm.mconnections.Lines.Add(command + ' - BEGIN');
if Command <> '' then // keepalive
begin
//Application.ProcessMessages;
msSize := AContext.Connection.Socket.ReadInt64;
ms := TMemoryStream.Create;
decompressedMS := TMemoryStream.Create;
try
AContext.Connection.Socket.ReadStream(ms, msSize);
ms.Position := 0;
DecompressStream(MS,decompressedMS);
decompressedMS.Position := 0;
Client_ProcessData(AContext,Command,decompressedMS);
finally
ms.Free;
decompressedMS.Free;
if logb then mainfrm.mconnections.Lines.Add(command + ' - END');
end;
end;
end;
procedure Client_ProcessData(AContext: TIdContext; cmd : String; data : TMemoryStream);
var
Hnd : THandle;
clData : TStringArray;
TmpStr1 : String;
Tmp : String;
TN : TIdNotify;
Sync : TMySync;
I,I2 : Integer;
begin
Hnd := AContext.Connection.Socket.Binding.Handle;
if cmd = 'scr' then // RECEIVE COMMAND TO SEND TO CLIENT TO RECEIVE DATA FROM CLIENT
begin
Tmp := StreamToString(data);
{Sync := TMySync2.Create(True);
try
Sync.cmd := cmd;
Sync.hnd := Hnd;
Sync.tmp := TmpStr1;
Sync.Resume;
finally
//Sync.Free;
end; }
log('>>> CLFROMAS: '+IntToStr(HND)+':::'+cmd+':::');
// SendCMDToSocket(MainFrm.UserSRV,StrToInt(Trim(Tmp)),'scr'+IntToStr(Hnd));
I2 := StrToInt(Trim(Tmp));
for I := 0 to 100 do
if USRVData[i].hnd = I2 then
begin
// cs.Acquire;
USRVData[i].CTX.Connection.Socket.WriteLn('scr'+IntToStr(Hnd)); // PLACED ALL CONTEXTs IN GLOBAL VARIABLE + ALL SOCKET HANDLES. <--- HERE IS THE PROBLEM
// cs.Release;
Break;
end;
// log('>>> CLFROMAS: '+IntToStr(HND)+':::'+cmd+':::'+streamtostring(data));
Exit;
end;
if Copy(cmd,1,Length('scr4u')) = 'scr4u' then // RECEIVE DATA FROM CLIENT TO SEND IT TO ADMIN CLIENT REQUEST ABOVE
begin
if Length(cmd) > Length('scr4u') then
begin
Delete(cmd,1,Length('scr4u'));
Data.Position := 0;
{ Sync := TMySync.Create;
try
Sync.cmd := cmd;
Sync.hnd := Hnd;
Sync.data := TMemoryStream.Create;
Sync.data.CopyFrom(data,data.Size);
Sync.data.Position := 0;
Sync.DoNotify;
finally
Sync.data.Free;
Sync.Free;
end; }
SendStreamToSocket(MainFrm.UserSRV,strtoint(cmd),'scr4u',Data);
log('>>>>> ADMIN: '+IntToStr(HND)+':::'+cmd+':::'{+streamtostring(data)});
end else TmpStr1 := '';
Exit;
end;
...
UPDATE
procedure TMainFrm.UserSRVExecute(AContext: TIdContext);
var
Command : String;
msSize : Int64;
ms : TMemoryStream;
decompressedMS : TMemoryStream;
H : TIdNotify;
I : Integer;
List, Messages : TStringList;
begin
Messages := nil;
try
List := TMyContext(AContext).OutgoingMessages.Lock;
try
if List.Count > 0 then
begin
Messages := TStringList.Create;
Messages.Assign(List);
List.Clear;
end;
finally
TMyContext(AContext).OutgoingMessages.Unlock;
end;
if Messages <> nil then
begin
for I := 0 to Messages.Count-1 do
begin
AContext.Connection.IOHandler.WriteLn(Messages.Strings[I]);
end;
end;
finally
Messages.Free;
end;
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
AContext.Connection.IOHandler.CheckForDataOnSource(100);
AContext.Connection.IOHandler.CheckForDisconnect;
if AContext.Connection.IOHandler.InputBufferIsEmpty then
Exit;
end;
Command := AContext.Connection.Socket.ReadLn;
if logb then mainfrm.mconnections.Lines.Add(command + ' - BEGIN');
if Command <> '' then
begin
msSize := AContext.Connection.Socket.ReadInt64;
ms := TMemoryStream.Create;
decompressedMS := TMemoryStream.Create;
try
AContext.Connection.Socket.ReadStream(ms, msSize);
ms.Position := 0;
DecompressStream(MS,decompressedMS);
decompressedMS.Position := 0;
Client_ProcessData(AContext,Command,decompressedMS);
finally
ms.Free;
decompressedMS.Free;
if logb then mainfrm.mconnections.Lines.Add(command + ' - END');
end;
end;
end;
Is it possible to run code in already running thread? for example: thread1 is running some code & i want to run code from thread2 in thread1.
No. Thread1 needs to be explicitly coded to stop what it is currently doing, do something else, and then go back to what it was previous doing. All Thread2 can do is signal Thread1 to perform that stop+continue at its earliest convenience.
I want to run code in idTCPServer thread to send some data to client
Your TIdTCPServer.OnExecute event handler needs to check for that data periodically and send it when it is available.
You can use the TIdContext.Data property, or derive a custom class from TIdServerContext and assign it to the TIdTCPServer.ContextClass property, to provide a per-client thread-safe buffer for your outbound data. Your OnExecute handler can then access that buffer when needed.
For example:
type
TMyContext = class(TIdServerContext)
public
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
OutgoingMessages: TIdThreadSafeStringList;
end;
constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited;
OutgoingMessages := TIdThreadSafeStringList.Create;
end;
destructor TMyContext.Destroy;
begin
OutgoingMessages.Free;
inherited;
end;
procedure TMyForm.FormCreate(Sender: TObject);
begin
// this must be set before activating the server...
IdTCPServer1.ContextClass := TMyContext;
end;
procedure TMyForm.IdTCPServer1Execute(AContext: TIdContext);
var
List, Messages: TStringList;
begin
// check for outgoing data...
Messages := nil;
try
List := TMyContext(AContext).OutgoingMessages.LockList;
try
if List.Count > 0 then
begin
Messages := TStringList.Create;
Messages.Assign(List);
List.Clear;
end;
finally
TMyContext(AContext).OutgoingMessages.UnlockList;
end;
if Messages <> nil then
begin
// send Messages using AContext.Connection.IOHandler as needed...
end;
finally
Messages.Free;
end;
// check for incoming data...
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
AContext.Connection.IOHandler.CheckForDataOnSource(100);
AContext.Connection.IOHandler.CheckForDisconnect;
if AContext.Connection.IOHandler.InputBufferIsEmpty then
Exit;
end;
// process incoming data as needed...
end;
procedure TForm1.SomeProcedure;
var
List: TIdContextList;
Context: TMyContext;
begin
List := IdTCPServer1.Contexts.LockList;
try
Context := TMyContext(List[SomeIndex]);
Context.OutgoingMessages.Add('something');
finally
IdTCPServer1.Contexts.UnlockList;
end;
end;
i am facing the following problem.
Me and a friend of mine, have set up a wireless network using uhf data modem.
When i am trying to send a file (e.g. photo) and the connection is ok there is no problem. But when i am trying to send a file and for some reason there is no connection for a while, the form freezes until there is a reestablishment. Can anyone help me please? Here is the code i use from both server and client side (Delphi 2010).
Client Side (Transmits file [this form freezes if connection is lost for a while or permanently]):
procedure TForm17.BtnSendFile(Sender: TObject);
var
FS: TFileStream;
filename: string;
begin
filetotx := 'temp.jpg';
FS := TFileStream.Create(filetotx, fmOpenRead, fmShareDenyWrite);
FS.Position := 0;
try
Form1.IdTCPClient1.Socket.LargeStream := true;
Form1.IdTCPClient1.Socket.WriteLn('PIC');
Form1.IdTCPClient1.Socket.Write(FS, 0, true);
finally
FS.Free;
end;
end;
Server Side (receives file)
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
s, filename:string;
FS: TFileStream;
Jpg: TJpegImage;
begin
S := AContext.Connection.Socket.ReadLn;
if S = 'PIC' then
begin
filename := 'PIC_' + datetostr(date) + ' ' + timetostr(time) + '.jpg';
filename := StringReplace(filename, '/', '-', [rfReplaceAll]);
filename := StringReplace(filename, ':', '_', [rfReplaceAll]);
filename := extractfilepath(Application.exename) + 'PIC\' + filename;
FS := TFileStream.Create(filename, fmCreate);
FS.Position := 0;
AContext.Connection.Socket.LargeStream := true;
AContext.Connection.Socket.ReadStream(FS);
Jpg := TJpegImage.Create;
FS.Position := 0;
Jpg.LoadFromStream(FS);
form26.image1.Picture.Assign(Jpg);
try
Jpg.Free;
FS.Free;
finally
//send feedback file received
AContext.Connection.Socket.WriteLn('PICOK');
TIdNotify.NotifyMethod(form26.Show);
end;
end;
Client Side (receives feedback 'PICOK')
type
TReadingThread = class(TThread)
protected
FConn: TIdTCPConnection;
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(AConn: TIdTCPConnection); reintroduce;
end;
constructor TReadingThread.Create(AConn: TIdTCPConnection);
begin
TLog.AddMsg('Client Thread Created');
FConn := AConn;
inherited Create(False);
end;
procedure TReadingThread.Execute;
begin
while not Terminated do
begin
if S='MSGOK' then
.
.
else if S = 'PICOK' then
begin
Do Something
end
end;
end;
procedure TReadingThread.DoTerminate;
begin
TLog.AddMsg('Disconnected');
inherited;
end;
Your client code is sending the file in the context of the main UI thread. That is why the UI freezes - there are no messages being processed while the send is busy. Either move that code into a worker thread (preferred), or else drop a TIdAntiFreeze component onto your Form.
Your server code is fine as far as the actual file transfer is concerned, however your try/finally block is wrong, and you are directly accessing a TImage without synchronizing with the main UI thread. You are already synchronizing when calling form26.Show, you just need to synchronize when calling form26.image1.Picture.Assign(Jpg) as well. Try this instead:
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
S, Filename: string;
FS: TFileStream;
Jpg: TJpegImage;
begin
S := AContext.Connection.Socket.ReadLn;
if S = 'PIC' then
begin
Filename := ExtractFilePath(Application.ExeName) + 'PIC\' + FormatDateTime('"PIC_"mm"-"dd"-"yyyy" "hh"_"nn"_"ss".jpg"', Now);
FS := TFileStream.Create(Filename, fmCreate);
try
AContext.Connection.Socket.LargeStream := true;
AContext.Connection.Socket.ReadStream(FS);
FS.Position := 0;
Jpg := TJpegImage.Create;
try
Jpg.LoadFromStream(FS);
TThread.Synchronize(nil,
procedure
begin
Form26.Image1.Picture.Assign(Jpg);
Form26.Show;
end;
);
finally
Jpg.Free;
end;
finally
FS.Free;
end;
//send feedback file received
AContext.Connection.Socket.WriteLn('PICOK');
end;
end;
Or this:
type
TMyNotify = class(TIdNotify)
protected
procedure DoNotify; override;
public
Jpg: TJpegImage;
constructor Create;
destructor Destroy; override;
end;
constructor TMyNotify.Create(Stream: TStream);
begin
inherited;
Jpg := TJpegImage.Create;
Jpg.LoadFromStream(Stream);
end;
destructor TMyNotify.Destroy;
begin
Jpg.Free;
inherited;
end;
procedure TMyNotify.DoNotify;
begin
Form26.Image1.Picture.Assign(Jpg);
Form26.Show;
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
S, Filename: string;
FS: TFileStream;
begin
S := AContext.Connection.Socket.ReadLn;
if S = 'PIC' then
begin
Filename := ExtractFilePath(Application.ExeName) + 'PIC\' + FormatDateTime('"PIC_"mm"-"dd"-"yyyy" "hh"_"nn"_"ss".jpg"', Now);
FS := TFileStream.Create(Filename, fmCreate);
try
AContext.Connection.Socket.LargeStream := true;
AContext.Connection.Socket.ReadStream(FS);
FS.Position := 0;
TMyNotify.Create(FS).Notify;
finally
FS.Free;
end;
//send feedback file received
AContext.Connection.Socket.WriteLn('PICOK');
end;
end;
I am using the IdThreadComponent to perform a simple ftp upload to a server.
The code to the ftp upload is as follows :
procedure TfrmNoticeWindow.IdThreadComponent1Run(
Sender: TIdCustomThreadComponent);
begin
IdFtp1.Host := 'ip';
IdFtp1.Username := 'user';
IdFtp1.Password := 'pass';
try
IdFtp1.Connect;
except
begin
msgDlgBox.MessageDlg('Could not connect!', mtError, [mbOk], 0);
publishing := false;
end;
end;
IdFtp1.Put(txtPath.text, file_name);
IdFtp1.Quit;
IdFtp1.Disconnect;
End;
The FtpWorkEnd is as follows :
procedure TfrmNoticeWindow.IdFTP1WorkEnd(Sender: TObject;
AWorkMode: TWorkMode);
var
Params : TStringList;
Resp : String;
begin
IdThreadComponent1.Active := false;
Params := TStringList.Create;
Params.Add('enotice_publish='+packet);
if (aborted = true) then
begin
IdFtp1.Quit;
idFtp1.Disconnect;
aborted := false;
uploadGauge.Value := 0;
uploadGauge.Visible := false;
frmNoticeWindow.Height := 512;
btnUpload.Caption := 'Publish';
exit;
end;
Resp := doPost('url', params);
if (Resp = 'Notice published successfully!') then
msgDlgBox.MessageDlg(Resp, mtInformation, [mbOk], 0)
else
msgDlgBox.MessageDlg(Resp, mtError, [mbOk], 0);
frmNoticeWindow.Refresh;
uploadGauge.Value := 0;
uploadGauge.Visible := false;
frmNoticeWindow.Height := 512;
btnUpload.Caption := 'Publish';
publishing := false;
txtPath.Text := '';
txtNoticeHeader.Text := '';
end;
When the upload is completed, the http post is made and in response I get a string [Success/Failure] from the server.
The problem is, after this MessageDlg, the components of my app, turns in to white blocks and the app's controls can no longer be used.
I did try an update() on the form, but that didn't help.
I'm using Business Skin Forms to skin my app, and the thread component is messing the form after closing.
The TIdThreadComponent.OnRun event handler runs in the context of a worker thread, not in the main UI thread. All of your TIdFTP operations are running in the context of the worker thread, which is fine. However, your TIdFTP.OnWorkEnd event handler is trying to make UI updates, but it is running in the worker thread as well, not in the main UI thread. That is not safe. You MUST synchronize with the main UI thread in order to access the UI safely. That includes calls to MessageDlg(), which is not a thread-safe function.
Try something more like this:
procedure TfrmNoticeWindow.IdThreadComponent1Run(
Sender: TIdCustomThreadComponent);
begin
IdFtp1.Host := 'ip';
IdFtp1.Username := 'user';
IdFtp1.Password := 'pass';
if aborted then Exit;
try
IdFtp1.Connect;
except
TThread.Queue(nil,
procedure
begin
msgDlgBox.MessageDlg('Could not connect!', mtError, [mbOk], 0);
end
);
Exit;
end;
try
if not aborted then
IdFtp1.Put(txtPath.text, file_name);
finally
IdFtp1.Disconnect;
end;
end;
procedure TfrmNoticeWindow.IdThreadComponent1AfterRun(
Sender: TIdCustomThreadComponent);
begin
publishing := false;
TThread.Queue(nil,
procedure
begin
uploadGauge.Value := 0;
uploadGauge.Visible := false;
frmNoticeWindow.Height := 512;
btnUpload.Caption := 'Publish';
if not aborted then
begin
txtPath.Text := '';
txtNoticeHeader.Text := '';
end;
end
);
end;
procedure TfrmNoticeWindow.IdFTP1Work(Sender: TObject;
AWorkMode: TWorkMode; AWorkCount: Int64);
begin
if aborted then
IdFtp1.Abort;
end;
procedure TfrmNoticeWindow.IdFTP1WorkEnd(Sender: TObject;
AWorkMode: TWorkMode);
var
Params : TStringList;
Resp : String;
begin
if aborted then Exit;
Params := TStringList.Create;
try
Params.Add('enotice_publish='+packet);
Resp := doPost('url', params);
finally
Params.Free;
end;
TThread.Queue(nil,
procedure
begin
if (Resp = 'Notice published successfully!') then
msgDlgBox.MessageDlg(Resp, mtInformation, [mbOk], 0)
else
msgDlgBox.MessageDlg(Resp, mtError, [mbOk], 0);
end
);
end;
If you are using a version of Delphi that does not support anonymous procedures, you can replace TThread.Queue() with TIdNotify instead:
uses
..., IdSync;
procedure TfrmNoticeWindow.MsgBoxCouldNotConnect;
begin
msgDlgBox.MessageDlg('Could not connect!', mtError, [mbOk], 0);
end;
procedure TfrmNoticeWindow.MsgBoxPostOk;
begin
msgDlgBox.MessageDlg('Notice published successfully!', mtInformation, [mbOk], 0)
end;
procedure TfrmNoticeWindow.MsgBoxPostFail;
begin
msgDlgBox.MessageDlg('Notice failed to publish!', mtError, [mbOk], 0);
end;
procedure TfrmNoticeWindow.ResetUiOk;
begin
uploadGauge.Value := 0;
uploadGauge.Visible := false;
frmNoticeWindow.Height := 512;
btnUpload.Caption := 'Publish';
txtPath.Text := '';
txtNoticeHeader.Text := '';
end;
procedure TfrmNoticeWindow.ResetUiAborted;
begin
uploadGauge.Value := 0;
uploadGauge.Visible := false;
frmNoticeWindow.Height := 512;
btnUpload.Caption := 'Publish';
end;
procedure TfrmNoticeWindow.IdThreadComponent1Run(
Sender: TIdCustomThreadComponent);
begin
IdFtp1.Host := 'ip';
IdFtp1.Username := 'user';
IdFtp1.Password := 'pass';
if aborted then Exit;
try
IdFtp1.Connect;
except
TIdNotify.NotifyMethod(MsgBoxCouldNotConnect);
Exit;
end;
try
if not aborted then
IdFtp1.Put(txtPath.text, file_name);
finally
IdFtp1.Disconnect;
end;
end;
procedure TfrmNoticeWindow.IdThreadComponent1AfterRun(
Sender: TIdCustomThreadComponent);
begin
publishing := false;
if aborted then
TIdNotify.NotifyMethod(ResetUiAborted)
else
TIdNotify.NotifyMethod(ResetUiOk);
end;
procedure TfrmNoticeWindow.IdFTP1Work(Sender: TObject;
AWorkMode: TWorkMode; AWorkCount: Int64);
begin
if aborted then
IdFtp1.Abort;
end;
procedure TfrmNoticeWindow.IdFTP1WorkEnd(Sender: TObject;
AWorkMode: TWorkMode);
var
Params : TStringList;
Resp : String;
begin
if aborted then Exit;
Params := TStringList.Create;
try
Params.Add('enotice_publish='+packet);
Resp := doPost('url', params);
if (Resp = 'Notice published successfully!') then
TIdNotify.NotifyMethod(MsgBoxPostOk)
else
TIdNotify.NotifyMethod(MsgBoxPostFail);
finally
Params.Free;
end;
end;
I am new to INNO and getting better every but I am having a problem with the CreateInputQueryPage that is running after the install is complete. It is returning the initial values that I put in as the defaults, not the edited values the user has edited.
Here is the code that creates the page. This seems to be fine.
procedure InitializeWizard();
var
ret : boolean;
begin
SvrSetup := CreateInputQueryPage(wpInfoAfter,
'i2x Server setup', '',
'Please specify your Company name, Listener host name and Port. then click Next.');
SvrSetup.Add('Company name:', False);
SvrSetup.Add('Listener host name:', False);
SvrSetup.Add('Port:', False);
SvrSetup.Values[1] := 'computer name'
SvrSetup.Values[2] := '5551';
end;
I run this code on the NEXTBUTTONCLICK event to validate my input
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := true;
{ Validate certain pages before allowing the user to proceed }
if CurPageID = SvrSetup.ID then
begin
if SvrSetup.Values[0] = '' then begin
MsgBox('You must enter company name.', mbError, MB_OK);
Result := False;
end;
if SvrSetup.Values[1] = '' then begin
MsgBox('You must enter listner host.', mbError, MB_OK);
Result := False;
end;
if SvrSetup.Values[2] = '' then begin
MsgBox('You must enter listner port.', mbError, MB_OK);
Result := False;
end;
end;
end;
Now when the install is over but before the final page I do this.
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
WriteXML;
end;
end;
And WriteXML is this.
procedure WriteXML;
var
FileData: String;
begin
FileData := ''
LoadStringFromFile(ExpandConstant('{app}\EMailer\EMailer.exe.config'), FileData);
StringChange(FileData, '!OVERRIDE_COMP!',ExpandConstant('{code:GetEmailerCompany}'));
SaveStringToFile(ExpandConstant('{app}\EMailer\EMailer.exe.config'), FileData, False);
end;
With the value I need to insert into the XML as code:GetEmailerCompanyas
function GetEmailerCompany(Param: String): String;
begin
// Return the company for email
Result := SvrSetup.Values[0];
end;
Aagain, It is returning the initial values that I put in as the defaults, not the edited values the user has edited. What am I missing?
Thanks