Delphi fmx blocked message Dialog box - dialog

While using android app developed using java or android studio I discovered that the message dialog prompt stay execution of the next line until the dialog prompt is answered. I have been trying to do this using TDialogService.MessageDialog(AMessage, ADialogType, AButtons, ADefaultButton, 0, procedurexyz). While the prompt is on display, the next line is executed making the prompt useless as the user was suppose to decide the next action. I need help from anyone to get an active block message dialog prompt.

Embarcadero documentation says, that on Android platform you can use only non-blocking calls for ShowMessage, MessageDialog, myForm.ShowModal and etc.
To get "blocking" mode you can use workaround, like this:
function myMessageDialog(const AMessage: string; const ADialogType: TMsgDlgType;
const AButtons: TMsgDlgButtons; const ADefaultButton: TMsgDlgBtn): Integer;
var
mr: TModalResult;
begin
mr:=mrNone;
// standart call with callback anonimous method
TDialogService.MessageDialog(AMessage, ADialogType, AButtons,
ADefaultButton, 0,
procedure (const AResult: TModalResult)
begin
mr:=AResult
end);
while mr = mrNone do // wait for modal result
Application.ProcessMessages;
Result:=mr;
end;

After your suggestion #Kami I came up with this and its working very well for me, though am open to suggestions or additions.
function MsgBox(const AMessage: string; const ADialogType: TMsgDlgType; const AButtons: TMsgDlgButtons;
const ADefaultButton: TMsgDlgBtn ): Integer;
var
myAns: Integer;
IsDisplayed: Boolean;
begin
myAns := -1;
IsDisplayed := False;
While myAns = -1 do
Begin
if IsDisplayed = False then
TDialogService.MessageDialog(AMessage, ADialogType, AButtons, ADefaultButton, 0,
procedure (const AResult: TModalResult)
begin
myAns := AResult;
IsDisplayed := True;
end);
IsDisplayed := True;
Application.ProcessMessages;
End;
Result := myAns;
end;

Related

How do I get a boolean result from a function using OmniThreadLibrary?

I have a Delphi (Windows) application created using Delphi 10 that has some blocking calls that I would like to convert to using threads. Unfortunately for me, these are not procedures, but functions. (And information on how to return function results from threads appears to be much more limited.) I am trying to familiarize myself with the OmniThreadLibrary, since it seems to be the most flexible and best supported threading library for Delphi, but I'm having trouble with understanding how to do this.
I have been able to get the various OmniThreadLibrary routines to work well with procedures, but when I try to set up a function, I get an error about capturing the result. When I use OmniThreadLibrary's Future example as a starting point, I can get the function to work, but I can't figure out how to connect to the event monitor, how to send messages from the task, etc. So, it seems as if I'm overlooking something no matter which way I try to solve this problem.
Currently, my program does something like this:
If myPing(IPAddress) then
begin
//Do other things hereā€¦
end;
Because myPing is blocking, and I actually need it to wait until myPing returns true before processing further, the application gets sluggish during this process. I'd like to put the myPing call in a thread, which would solve the sluggishness problem, but I can't figure out how to do that in the form of a function using OmniThreadLibrary. (Unless I use a future, in which case I can't figure out how to connect to the Event Monitor.)
Edit 1: Since my original post, I have made a little progress. I was able to connect the Event Monitor to the Future by adding Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1) to my code, right after the function. However, I still can't figure out how to send messages to that event monitor from within the Future function.
Edit 2: I now have some sample code. My first attempt was similar to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end
);
end;
The basic function worked, but did not allow me to send any messages to the TOmniEventMonitor. I was able to get that part working by changing the code to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Now, I can successfully monitor the OnTaskTerminated event, but I still can't send messages to the Event Monitor from the task. By changing my code once again, I can access the task itself and send messages using task.Comm.Send(), but the messages don't reach the EventMonitor:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function(const task: IOmniTask): boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
task.Comm.Send(0,'Test 1');
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Here's a simple example on how to retrieve the function result from the async call. It does not use an "OmniEventMonitor" but instead calls a function once the async call returns ("Ping" is defined in PingU.pas, but not of importance here):
unit MainFormU;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Menus;
type
TPingResultEvent = procedure (const bResult: Boolean) of object;
TOnTerminateTestForm = class(TForm)
LogMemo: TMemo;
MainMenu: TMainMenu;
PingMenu: TMenuItem;
procedure PingMenuClick(Sender: TObject);
private
procedure BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
procedure PingResult (const bResult: Boolean);
public
{ Public declarations }
end;
var
OnTerminateTestForm: TOnTerminateTestForm;
implementation
{$R *.dfm}
uses PingU, OtlParallel, OtlTaskControl;
procedure TOnTerminateTestForm.PingMenuClick (Sender: TObject);
var
sServer : String;
begin
if (InputQuery ('Ping computer', 'Computer name:', sServer)) then
if (sServer <> '') then
begin
PingMenu.Enabled := false;
LogMemo.Lines.Add (Format ('Pinging %s',[sServer]));
BackgroundPing (sServer, PingResult);
end; { if }
end; { TOnTerminateTestForm.PingMenuClick }
procedure TOnTerminateTestForm.BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
var
bResult : Boolean;
begin
Parallel.Async (
procedure
begin
bResult := Ping (sServer);
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
// executed in main thread after the async has finished
if Assigned (OnResult) then
OnResult (bResult);
end
)
);
end; { TOnTerminateTestForm.BackgroundPing }
procedure TOnTerminateTestForm.PingResult (const bResult: Boolean);
begin
PingMenu.Enabled := true;
LogMemo.Lines.Add ('Ping result = ' + BoolToStr (bResult, true));
end; { TOnTerminateTestForm.PingResult }
end.
Code source: Get a function result asynchronously in Delphi using Omni Thread Library

Delphi: Stop a download using IdHttp and Thread

I have a thread defined THttpThread for downloading of a file. I would like to stop the download if modal form is closed or if Cancel button is pressed.
In the example from bellow I got Access Violation probably because of the way how I reuse the thread.
procedure Tform_update.button_downloadClick(Sender: TObject);
var
HttpThread: THttpThread;
begin
//download
if button_download.Tag = 0 then
begin
HttpThread:= THttpThread.Create(True);
//...
HttpThread.Start;
end
//cancel download
else
begin
HttpThread.StopDownload:= True;
end;
end;
I sow the answers from How stop (cancel) a download using TIdHTTP and some many others but I still don't get it how to update a property of a running thread.
I will give the answer found, using also the hints from users comments.
The access violation comes from the fact HttpThread was not assigned during cancellation. Reason why HttpThread: THttpThread must be defined under his form like:
Tform_update = class(TForm)
//...
private
HttpThread: THttpThread;
and then the code should be:
procedure Tform_update.button_downloadClick(Sender: TObject);
begin
//download
if button_download.Tag = 0 then
begin
HttpThread:= THttpThread.Create(True);
//...
HttpThread.Start
end
//cancel download
else
begin
if Assigned(HttpThread) then HttpThread.StopDownload:= True;
end;
end;
Same for form close
procedure Tform_update.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Assigned(HttpThread) then HttpThread.StopDownload:= True;
end;
As some users ask for in some comments, there is no need for the code of thread.

OverbyteICS HTTP timeout when used in different threads

I've been trying to figure this error our for about 4 days now. I'm using Delphi XE and have created a little tool for translators to use. I got the idea of using the Microsoft Translation API to help make things easier and a bit less tedious.
I created a class that accesses the Microsoft translator API, but I wanted to make it Thread Safe so the requests could be made in the background. I have no problem sending a request to get an Access Token, however, I run that request in a separate thread. When the user clicks a button, I spawn a new thread and run the http request to translate the term from in there. However, it times out every single time. If I run it from the same thread there's no problem.
Here is the method I use for sending the http requests (the THttpCli object that is passed is shared among threads)
function sendHTTPRequest(APost: Boolean; AURI: UTF8string;
AContentType: UTF8string; APostData: UTF8String; AHttpCli: TSSLHttpCli): UTF8string;
var
DataOut: TMemoryStream;
DataIn: TMemoryStream;
lHTMLStream: TStringStream;
lencoding: TUTF8Encoding;
lownClient: boolean;
begin
lownClient := false;
if AHttpCli = nil then
begin
AHttpCli := TSSLHttpCli.Create(nil);
AHttpCli.SslContext := TSSLContext.Create(nil);
with AHttpCli.SslContext do
begin
SSLCipherList := 'ALL:!ADH:RC4+RSA:+SSLv2:#STRENGTH';
SSLVersionMethod := sslV23_CLIENT;
SSLVerifyPeerModes := [SslVerifyMode_PEER]
end;
AHttpCli.MultiThreaded := true;
lownClient := true;
end;
AHttpCli.Accept := 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
if APost then
begin
DataOut := TMemoryStream.Create;
DataOut.Write(APostData[1], Length(APostData));
DataOut.Seek(0, soFromBeginning);
end;
AHttpCli.URL := AURI;
AHttpCli.ContentTypePost := AContentType;
DataIn := TMemoryStream.Create;
if APost then AHttpCli.SendStream := DataOut;
AHttpCli.RcvdStream := DataIn;
try
if apost then
AHttpCli.Post
else
AHttpCli.Get;
lHTMLStream := TStringStream.Create('', TEncoding.UTF8);
lHtmlStream.LoadFromStream(AHttpCli.RcvdStream);
result := lHtmlStream.DataString;
lHtmlStream.Free;
finally
AHttpCli.Close;
AHttpCli.RcvdStream := nil;
AHttpCli.SendStream := nil;
DataIn.Free;
if APost then
DataOut.Free;
if lownClient then
AHttpCli.free;
end;
end;
I suppose the obvious solution is to just have one thread that waits for a signal to execute, but I was hoping to get an explanation as to why the timeout happens. I have no way to explain why the second thread times out and the first does not.
The HTTP component seems to get stuck on the dnslookup. OverbyteICS uses the Windows function WSAAsyncGetHostByName to lookup the name.
Any help is much appreciated
UPDATE May 13, 2013:
So, as it turns out, sharing the THttpCli object among threads seems to be what causes the timeout. The Solution is simply to pass nil into the AHttpCli parameter in my function above.
I'll still accept an answer as to WHY this causes a timeout. As far as I could tell the WSAAsyncGetHostByName method doesn't use any synchronous objects and the other thread was not running at the same time so there shouldn't be anything blocking the threads.
On Windows, OverbyteICS uses WSAAsyncSelect (here) and MsgWaitForMultipleObjects (here) to allow asynchronous notification of the socket events (FD_READ, FD_WRITE, FD_CLOSE and FD_CONNECT). Part of the design of WSAAsyncSelect requires a window that will receive the event messages, and to that end, a control class is registered using RegisterClass here, and an instance created using CreateWindowEx here, both in the call to THttpCli.Create.
This is where the issue arises; as alluded to in the documentation for GetMessage, PeekMessage and PostMessage, the message queue itself is per thread.
I've tested various permutations of each discrete step of the process (listed below) shared between 2 threads, and the only combinations that fail are when the call to CreateWindowEx and MsgWaitForMultipleObjects are performed on different threads, which reinforces the idea that a given message queue can only be accessed on the same thread.
Seemingly, without a rewrite of the OverbyteICS library itself, the only way to use it in a threaded environment is to create the THttpCli instance in the same thread as the subsequent request calls (THttpCli.Get, THttpCli.Post etc).
Appendix
Call to RegisterClass
procedure Up0(S: PState);
var
WndClass: TWndClass;
begin
FillChar(WndClass, SizeOf(TWndClass), 0);
WndClass.lpfnWndProc := #DefWindowProc;
WndClass.hInstance := hInstance;
WndClass.lpszClassName := 'test';
if RegisterClass(WndClass) = 0 then
ExitProcess(GetLastError);
end;
Call to CreateWindowEx
procedure Up1(S: PState);
begin
S.Window := CreateWindowEx(WS_EX_TOOLWINDOW, 'test', '', WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
if S.Window = 0 then
ExitProcess(GetLastError);
end;
Call to Ics_socket
procedure Up2(S: PState);
begin
S.Socket := Ics_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if S.Socket = INVALID_SOCKET then
ExitProcess(Ics_WSAGetLastError);
end;
Call to Ics_WSAAsyncSelect
procedure Up3(S: PState);
begin
if Ics_WSAAsyncSelect(S.Socket, S.Window, WM_USER, FD_CONNECT) = SOCKET_ERROR then
ExitProcess(Ics_WSAGetLastError);
end;
Call to Ics_connect
procedure Up4(S: PState);
var
Error: Integer;
Sin: TSockAddrIn;
begin
FillChar(Sin, SizeOf(TSockAddrIn), 0);
Sin.sin_family := AF_INET;
Sin.sin_port := Ics_htons(42);
if Ics_connect(S.Socket, PSockAddr(#Sin)^, SizeOf(TSockAddrIn)) = SOCKET_ERROR then
begin
Error := Ics_WSAGetLastError;
if Error <> WSAEWOULDBLOCK then
ExitProcess(Error);
end;
end;
Call to MsgWaitForMultipleObjects
procedure Up5(S: PState);
var
Msg: TMsg;
WaitResult: Cardinal;
begin
WaitResult := MsgWaitForMultipleObjects(0, Pointer(nil)^, False, 1000, QS_ALLINPUT);
if WaitResult = WAIT_TIMEOUT then
begin
S.Result := 0;
Exit;
end;
while PeekMessage(Msg, S.Window, WM_USER, WM_USER, PM_REMOVE) do
if LOWORD(Msg.lParam) = FD_CONNECT then
begin
S.Result := 1;
Exit;
end;
end;

How to send a string (serial port) , wait (timeout) and catch the reply string?

After reading this very interesting topic on stackoverflow --> How to wait for COM port receive event before sending more data in a loop
I've ran into many problems and i've tried many solutions ... nothing work well unfortunately !
Many serial port libraries are event-driven and i'm having a hard time understanding them.
I've tried with Asyncpro, Synaser and TComport.
Is it possible to have a function like this:
SerialSendandReply(tx string here,timeout) return rx string and if timeout send a error string
Response from the device are withing milliseconds a blocking way to do it would be better?
Like this:
Dosomething here
showmessage(SerialSendandReply('test',100 )); //100 ms timeout
dosomething else
With this code
TForm1 = class(TForm)
...
private
IOEvent : THandle; // used for IO events
IORx : string;
Comport : TapdComport;
...
procedure TForm1.ComportTriggerAvail(CP: TObject; Count: Word);
var i : integer;
begin
for i:=1 to Count do
IORx:=IORx+Comport.GetChar;
SetEvent(IOEvent);
end;
function TForm1.SerialSAWR(tx : string; TimeOut : integer) : boolean;
begin
Result := False;
try
IORx := ''; // your global var
ResetEvent(IOEvent);
Comport.PutString(tx);
Result := WaitForSingleObject(IOEvent, TimeOut) = WAIT_OBJECT_0;
except
on E : Exception do
// dosomething with exception
end;
end;
// constructor part
IOEvent := CreateEvent(nil, True, False, nil);
// destructor part
if IOEvent <> 0 then
CloseHandle(IOEvent);
Then i've tried to call this function :
if SerialSAWR('test'; 5000) then showmessage(IORx);
Sending is working great but doesn't return anything in the string.
Any advices?
Thank you very much!
Regards,
Laurent
I use TComPort and have created the following routine to do what you ask. TComPort monitors received characters in its monitoring thread and my routine waits for characters without calling Application.ProcessMessages. It may not be the most elegant code but it works fine.
function TArtTComPort.SerialPort_AwaitChars(AMinLength: integer;
ATerminator: char; AQtyAfterTerm: integer; ARaise: boolean): string;
var
fDueBy : TDateTime;
function IsEndOfReplyOrTimeout( var AStr : string ) : boolean;
var
I : integer;
begin
Result := False;
If ATerminator <> #0 then
begin
I := Length( AStr ) - AQtyAfterTerm;
If I > 0 then
Result := AStr[I] = ATerminator;
end;
If not Result then
Result := Length(AStr) >= AMinLength;
// Un-comment this next line to disable the timeout.
//Exit;
If not Result then
begin
Result := Now > fDueBy;
If Result then
If ARaise then
raise EArtTComPort.Create( 'Serial port reply timeout' )
else
AStr := '';
end;
end;
var
Events : TComEvents;
iCount : integer;
S : string;
begin
Assert( AMinLength > 0, 'Invalid minimum length' );
If not FComPort.Connected then
begin
Result := '';
Exit;
end;
fDueBy := Now + (FTimeoutMS * TDMSec );
Result := '';
Repeat
// Setup events to wait for:
Events := [evRxChar, evTxEmpty, evRxFlag, evRing, evBreak,
evCTS, evDSR, evError, evRLSD, evRx80Full];
// Wait until at least one event happens.
FComPort.WaitForEvent(
Events,
FStopEvent.Handle,
FTimeOutMS);
If Events = [] then // timeout
begin
If ARaise then
raise EArtTComPort.Create( 'Serial port reply timeout' )
end
else
begin
If evRxChar in Events then
begin
iCount := FComport.InputCount;
FComPort.ReadStr( S, iCount );
Result := Result + S;
end;
end;
until IsEndOfReplyOrTimeout( Result );
end;
I switched for nrComm Lib (v9.31)... very simple of use and well supported.
The only drawback is that isn't free and open source ... but it's worth it !
It's also thread-safe which is good too :).
Thank you very much everyone for the replies!
You are trying to do async I/O from the main thread. This will never play well with the GUI.
Doing complex async I/O is better suited in a separate thread. I have a blocking serial communication package (I think Synaser also has a blocking mode) and a function like this:
function TransmitReceive(const msg: AnsiString; var reply: AnsiString;
timeOut: Integer): Boolean;
Put the complex code logic inside a thread.execute and trig the start of the logic with an event signal.
Communicate results etc to the main thread through PostMessage calls for example.

Create event and share variables

I am using Delphi 2007 and threads.
My problem (sorry, i'll try to explain better):
1) I created a file "utilities.pas" where i have the function i use more.
2) I created a new program, in this program i have one thread
3) in the execute method of the thread i call one function in my file "utilities.pas".
this function connects to an ftp using clever components (tclftp). This components logs the server responce in a dedicated event. What i would like to do is to save the log in a stringlist and then send the stringlist back to the calling thread.
This is part of the file "utilities.pas":
// I created TEventHandlers because it's the only way to assign the event runtime
// without having a class
type
TEventHandlers = class
procedure clFtp1SendCommand(Sender: TObject; const AText: string);
end;
var EvHandler: TEventHandlers;
// this is the porcedure called from the thread. i want to send the stringlist
// back to it containing the ftp log
procedure Test(VAR slMain: tStringlist);
var cFTP: TclFtp;
begin
cFTP := TclFtp.Create(nil);
cFTP.Server := 'XXX';
cFTP.UserName := 'XXX';
cFTP.Password := 'XXX';
cFTP.OnSendCommand := EvHandler.clFtp1SendCommand;
// i connect to the ftp
cFTP.Open;
FreeAndNil(cFTP);
end;
procedure TEventHandlers.clFtp1SendCommand(Sender: TObject; const AText: string);
begin
// here the component (cftp) sends me back the answer from the server.
// i am logging it
// HERE IT'S THE PROBLEM:
// I can't reach slMain from here.....
slmain.add(Atext);
end;
this is the calling thread:
procedure TCalcThread.Execute;
var slMain: tstringlist;
begin
inherited;
slmain := tstringlist.create(nil);
Test(slmain);
if slMain.count > 0 then
slMain.savetofile('c:\a.txt');
// i won't free the list box now, but in the thread terminated.
end;
this is the main program:
procedure TfMain.ThreadTerminated(Sender: TObject);
Var ExThread: TCalcThread;
begin
ExThread := (Sender as TCalcThread);
if ExThread.slMain.Count > 0 then
ExThread.slMain.SaveToFile('LOG\Errori.log');
freeandnil(slMain);
end;
Please can anybody help me in solving this? I really don't know what to do.
I hope now it more clear.
p.s. thanks for all the answer..
Another approach would be to have your thread object have its own instance of the stringlist and its own cFTP. If you need to have one "master thread" that everything writes to (perhaps for a summary of what each thread accomplished), use this class:
TThreadStringList by Tilo Eckert
http://www.swissdelphicenter.ch/torry/showcode.php?id=2167
I think one (BAD) approach would be to create a pool of components in the main thread or at design time, and assign one to each thread. i.e. 5 instances of cFTP, 5 stringlists, 5 threads.
Update: Martin James points out why this is a terrible idea, and I agree. So don't do this. Post stays as a deterrent.
Intercept the event within the thread class, and fire an own typed event from within that handler. Synchronize this call! And try to prevent the global variable. All this as follows:
type
TFtpSendCommandEvent = procedure(Mail: TStrings; const AText: String) of object;
TMyThread = class(TThread)
private
FclFtp: TclFtp;
FslMail: TStrings;
FOnFtpSendCommand: TFtpSendCommandEvent;
FText: String;
procedure clFtpSendCommand(Sender: TObject; const AText: String);
procedure DoFtpSendCommand;
protected
procedure Execute; override;
public
// You could add this property as parameter to the constructor to prevent the
// need to assign it separately
property OnFtpSendCommand: TFtpSendCommandEvent read FOnFtpSendCommand
write FOnFtpSendCommand;
end;
// If you dont want to make this a property or private field of the thread class:
var
EvHandler: TFtpSendCommandEvent;
{ TMyThread }
procedure TMyThread.clFtpSendCommand(Sender: TObject; const AText: string);
begin
// Store the AText parameter temporarily in a private field: Synchronize only
// takes a parameterless method
FText := AText;
Synchronize(DoFtpSendCommand);
end;
procedure TMyThread.DoFtpSendCommand;
begin
if Assigned(FOnFtpSendCommand) then
FOnFtpSendCommand(FslMail, FText);
// Or, if you really like to use that global variable:
if Assigned(EvHandler) then
EvHandler(FslMail, FText);
end;
procedure TMyThread.Execute;
begin
...
FclFtp := TclFtp.Create(nil);
FslMail := TStringList.Create(nil);
try
FclFtp.Server := 'XXX';
FclFtp.UserName := 'XXX';
FclFtp.Password := 'XXX';
FclFtp.OnSendCommand := clFtpSendCommand;
FclFtp.Open;
finally
FreeAndNil(FclFtp);
FreeAndNil(FslMail);
end;
...
end;

Resources