Delphi: WebBrowser's OnDownloadComplete happens multiple times at once - delphi

For example in this code:
procedure TForm1.WebBrowser1DownloadComplete(Sender: TObject);
begin
ShowMessage('Download Completed');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
WebBrowser1.Navigate('http://www.google.com/');
end;
"WebBrowser1DownloadComplete" message appears several times on 1 Navigate.
This is annoying and makes this event almost useless.
Why is this happening? How to avoid this?
Thankyou

Perhaps the OnNavigationComplete2 event handler is more suitable for you application.
Occurs immediately after the Web browser successfully navigates to a
new location.
Write an OnNavigateComplete2 event handler to take specific action when the Web browser successfully navigates to a new resource. The event can occur before the document is fully downloaded, but when it occurs at least part of the document must be received and a viewer for the document created.

Related

TDialogService.ShowMessage not blocking on Win10

In my Delphi 10.4 FMX program, I am asking the user for a new file name using the code below
procedure TForm6.btnBlockingClick(Sender: TObject);
begin
//In Win10, this blocks form access when ShowMessage is called
NameCallBack(mrOk, ['name']);
end;
procedure TForm6.btnNonBlockingClick(Sender: TObject);
begin
//In Win10, this does not block form access when ShowMessage is called in the NameCallBack routine.
TDialogService.InputQuery('Enter name', ['Name'], [''], NameCallBack);
end;
procedure TForm6.NameCallBack(const AResult: TModalResult; const AValues: array of string);
begin
if aResult = mrOK then
TDialogService.ShowMessage('Ok pressed')
else
TDialogService.ShowMessage('Cancel pressed');
end;
Any idea why ShowMessage is not blocking when NameCallBack is used as the Callback event for InputQuery? In Win10, what is the best way to show a message to a user in this type of callback routine that keeps the user from accessing the underlying form until the dialog is closed in some way.
FYI: the same thing happens if you use MessageDialog, to allow user interaction, instead of ShowMessage in the callback routine.
Note: this logic works in OSX and IOS, with both dialogs blocking. On Android, neither dialog is blocking but is not a problem, as touching anywhere but the dialogs closes the dialog and requires a second touch to interact with the underlying form again. On Win10, I can doing anything I want with the underlying form while the ShowMessage dialog is visible when used in a callback event.
Thanks for any help with this.
This may not be the best way to work around the bug I found, but here is what I did in case someone else has this problem.
I added a timer to my form, set the interval to 200 and disabled it.
For any TDialogServices.MessageDialog and TDialogServices.InputQuery callback routine where the callback routine also called MessageDialog or ShowMessage, I moved the callback logic into new routines. I then changed the callback routines to set a form variable to indicate which callback routine was called, saved off the relevant info from the callback routine as needed, then enabled the timer.
In the timer event, I first disable the timer then call the new routines based on the form variable.
This now allows both the original dialog and the dialog needed in the callback routine to be blocking on Win10. In addition, Android, OSX and IOS appear to still work correctly as explained in my question.

OnShown event for TForm?

At program start, in the OnActivate event handler, I need to do something which blocks the program for a few seconds. During this time the form's client area is still not completely painted, which looks ugly for the user. (During this blocked time I don't need the program to respond to clicks or other user actions, so there is no need to put the blocking operation into a thread - I just need the form to be completely painted). So I use TForm.Update and Application-ProcessMessages to update the form before the blocking operation which works very well:
procedure TForm1.FormActivate(Sender: TObject);
begin
Form1.Update;
Application.ProcessMessages;
Sleep(7000);
end;
However, I wonder whether there is not another more elegant solution for this problem. This could be for example a OnShown event implemented in a descendant of TForm which will be fired AFTER the form has been completely painted. How could such an event be implemented?
Your real problem is that you are blocking the UI thread. Simply put, you must never do that. Move the long running task onto a different thread and thus allow the UI to remain responsive.
If you are looking for event which is fired when application finishes loading/repainting you should use TApplication.OnIdle event
http://docwiki.embarcadero.com/Libraries/XE3/en/Vcl.Forms.TApplication.OnIdle
This event is fired once application is read to recieve users input. NOTE this event will be fired every time application becomes idle so you need to implement some controll variable which will tel you when OnIdle even was fired for the first time.
But as David already pointed out it is not good to block your UI (main thread). Why? When you block your main thread the application can't normally process its messages. This could lead to OS recognizing your application as being "Hanged". And aou definitly wanna avoid this becouse it could cause the users to go and forcefully kill your application whihc would probably lead to data loss. Also if you ever wanna design your application for any other platforms than Windows your application might fail the certification proces becouse of that.
In the past a simple PostMessage did the trick.
Essentially you fire it during DoShow of the base form:
procedure TBaseForm.DoShow;
begin
inherited;
PostMessage(Handle, APP_AFTERSHOW, 0, 0);
end;
then catch the msg and create an AfterShow event for all forms inherited from this base form.
But that no longer works, well not if you are skinning and have a good number of VCL controls.
My next trick was to spawn a simple thread in DoShow and check for IsWindowVisible(Handle) and IsWindowEnabled(Handle). That really sped things up it cut 250ms from load time since db opening and other stuff was already in the AfterShow event.
Then finally I thought of madHooks, easy enough to hook the API ShowWindow for my application and fire APP_AFTERSHOW from that.
function ShowWindowCB(hWnd: HWND; nCmdShow: Integer): BOOL; stdcall;
begin
Result := ShowWindowNext(hWnd, nCmdShow);
PostMessage(hWnd, APP_AFTERSHOW, 0, 0);
end;
procedure TBaseForm.Loaded;
begin
inherited;
if not Assigned(Application.MainForm) then // Must be Mainform it gets assigned after creation completes
HookAPI(user32, 'ShowWindow', #ShowWindowCB, #ShowWindowNext);
end;
To get the whole thing to completely paint before AfterShow it still needed a ProcessPaintMessages call
procedure TBaseForm.APPAFTERSHOW(var AMessage: TMessage);
begin
ProcessPaintMessages;
AfterShow;
end;
procedure ProcessPaintMessages; // << not tested, pulled out of code
var
msg: TMsg;
begin
while PeekMessage(msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) do
DispatchMessage(msg);
end;
My final test was to add a Sleep to the AfterShow event and see the Form fully painted with empty db containers since the AfterShow events had not yet completed.
procedure TMainForm.AfterShow;
begin
inherited;
Sleep(8*1000);
......

Knowing Onclick Event fired

I use a server client component, and when a file is being received in the TransferFile event of this component, I use an alert message component. So I want, if the user clicks on the alert message, the program to continue executing code in the TransferFile event to accept the file transfer in case the button is clicked, or to exit the procedure when not.
pls see bellow code:
procedure TfrmReadFile.ServerReceiveEvent(Sender: TObject;
Client: TSimpleTCPClient; Event: TTOOCSEvent);
begin
if (Event is TTOOCSEventFileTransfert) then
begin
Alert.Show;
if Alert.OnAlertClick then
begin
with (Event as TTOOCSEventFileTransfert) do
if (dlgSaveFile.Execute) then
with TMemoryStream.Create do
try
Write(Content[1], Length(Content));
SaveToFile(dlgSaveFile.FileName);
finally
Free;
end;
end;
end;
end;
but "if Alert.OnAlertClick then" is wrong
procedure TfrmReadFile.AlertAlertClick(Sender: TObject);
begin
end;
Please help me for these codes.
the AlertMessage is one of the TMS component and it hasn't ShowModal but it has Alert.Show Procedure that I use. and I want to pause the executing code untill the alert show time is finish and if user not click on alert executing the code aborted and no file saved.
From your code it's obvious that by the time you get to show the Alert, the file transfer already occurred: it's only a matter of "Do I save to file" or "Do I discard the content I already received". I'm inferring this information from your use of the TMemoryStream.Write() - that function takes a buffer as a parameter, so I assume the Content[1] gives your the buffer. This also means the Content is allready populated with the data you need. Too late to NOT transfer it, it's already in memory, all you can do is save it to disk or discard it.
I also have no idea how TMS's Alert works, but I'm going to assume only one Alert can be shown at any given time, and I'm going to assume you dropped your Alert on a component (ie: There's only one Alert in the whole program).
You should first alter the code for your "received event" to immediately move the content to a TMemoryStream. Also make sure you don't get into trouble with recursive re-entrance. Add a private field to your form, call it FLastContentReceived: TMemoryStream; Now alter your code to look like this:
procedure TfrmReadFile.ServerReceiveEvent(Sender: TObject;
Client: TSimpleTCPClient; Event: TTOOCSEvent);
begin
if (Event is TTOOCSEventFileTransfert) then
begin
// Re-entry before we managed to handle the previous received file?
if Assigned(FLastContentReceived) then
raise Exception.Create('Recursive re-entry not supported.');
// No re-entry, let's save the content we received so we can save it to file
// if the user clicks the Alert button.
FLastContentReceived := TMemoryStream.Create;
// I don't know what Content is, but you've got that in your code so I
// assume this will work:
FLastContentReceived.Write(Content[1], Length(Content);
// Show the alert; If the OnAlertClick event fires we'll have the received file content
// in the FLastContentRecevied and we'll use that to save the file.
Alert.Show;
end;
end;
You're trying to do an if on Alert.OnAlertClick - so I assume there's an event in your Alert component that's called OnAlertClick. Write this in it's event handler:
procedure TfrmReadFile.AlertAlertClick(Sender: TObject);
begin
if not Assigned(FLastContentReceived) then raise Exception.Create('Bug');
try
if dlgSaveFile.Execute then
FLastContentReceived.SaveToFile(dlgSaveFile.FileName);
finally FreeAndNil(FLastContentReceived);
end;
end;
You'd also need a way to discard the FLastContentReceived if the form is closed before the Alert button gets clicked OR there's a time-out (the Alert goes away without the user clicking it). The first job (getting rid of FLastContentReceived) when the form is closed is simle: add this to your form's OnDestroy:
FLastContentRecevid;Free;
Handling the timeout might a bit more difficult. If the Alert has an event that's called when the alert times out and the Balloon goes away without being clicked then use that event handler to do this:
FreeAndNil(FLastContentRecevid);
If it offers no such thing you could set up a TTimer for an interval equal to the timeout of the alert (or slightly longer to be safe), enable it before showing the alert and do this from it's OnTimer:
procedure TFrmReadFile.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
FreeAndNil(FLastContentRecevid);
end;

Which is the best place to initialize code? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Splash Screen Programatically
Show a splash screen while a database connection (that might take a long time) runs
Which is the best place to initialize code such as loading INI file? I want first to show the form on screen so the user know that the app is loading and ONLY after that I want to call lengthy functions such as LoadIniFile or IsConnectedToInternet (the last one is REALLY slow).
The OnCreate is not good because the form is not yet ready and it will not show up on screen.
I do this I DPR but not working always:
program Test;
begin
Application.Initialize;
Application.Title := 'Test app';
Application.CreateForm(TfrmTest, frmTest);
frmTest.Show; <---------------------- won't show
LateInitialize;
Application.Run;
end.
The form will not show until LateInitialize (4-5 seconds) is executed.
procedure LateInitialize;
begin
CursorBussy;
TRY
// all this won't work also. the form won't show
frmTest.Visible:= TRUE;
Application.ProcessMessages;
frmTest.Show;
Application.ProcessMessages;
frmTest.BringToFront;
frmTest.Update;
Application.ProcessMessages;
DoSomethingLengthy; {4-5 seconds}
FINALLY
CursorNotBussy;
END;
end; <--------- Now the form shows.
And yes, frmTest it is my only form (the main form).
After calling frmTest.Show, you can call frmTest.Update to let it render onscreen, before then calling LateInitialize. But until Application.Run is called, the main message loop will not be running, so the form will not be able to do anything else until then.
Another option is to use the form's OnShow event to post a custom window message back to the form via PostMessage(), then have the form call LateInitialize when it receives that message at a later time. That will allow the form to process painting messages normally until LateInitialize is called.
Anything that blocks the main thread for more than a few milliseconds/seconds really should be moved into a separate worker thread instead (especially things like IsConnectedToInternet). The main thread should be used for running the UI.
An easy way to do this, is to send a message to yourself.
I do this all the time
const
MSG_AFTERCREATE = WM_APP + 4711;
...
procedure OnCreate(Sender: TObject);
procedure AfterCreate(var message: TMessage); message MSG_AFTERCREATE;
...
Implementation
procedure OnCreate(Sender: TObject);
begin
PostMessage(Self.Handle, MSG_AFTERCREATE, 0, 0);
end;
procedure AfterCreate(var message: TMessage);
begin
//Do initializing here... the form is done creating, and are actually visible now...
end;
Variant 1: Use TTimer with a 1 second delay, run it from main form's OnShow
In TTimer do the initialisation
This will give time for most components to initialize and draw themselves
Variant 1.1: use message method in function and call Win API PostMessage (but not SendMessage aka Perform) from OnShow. This is seemilar but more cheap and fast. However that message "do init now" sometimes may be received before some complex component on the form would fully draw itself.
Variant 2: use threads (OmniThreadsLib or even plain TThread)
Launch it from MainForm OnCreate and let it prepare all data in background, then enable all needed buttons, menus, etc
That is truly the best way if you have long and blocking functions, liek you described IsConnectedToInternet.
Variant 3: use SplashScreen before showing main form.
That is good because users see that application not read yet.
That is bad for that very reason - people start feeling your program is slow. Google Chrome was told to draw their main form as picture in 1st moments just to make look "we are already started" even the actual control would be ready a bit later.
A long time ago in another forum far far away, someone posted the following to document the life cycle of a form. I have found it useful, so am sharing it here.
Create OnCreate
Show OnShow
Paint OnPaint
Activate OnActivate
ReSize OnResize
Paint OnPaint
Close query OnCloseQuery
Close OnClose
Deactivate OnDeactivate
Hide OnHide
Destroy OnDestroy
Try the OnActivate event.

How Do I Show A Final Form On Exiting In Delphi?

This should be a simple one for someone. I just can't figure out how to do it.
Upon exiting of my program, I want to hide the main form and make a final "Thank You" form appear on its own, like this:
procedure TMainForm.ExitExecute(Sender: TObject);
begin
MainForm.Visible := false;
ThankYouForm.Show;
MainForm.Close;
end;
But when I do that, I get the Exception:
EInvalid Operation: Cannot change Visible in OnShow or OnHide
So how do I show a final form, while hiding the main form when exiting a program in Delphi?
Conclusion: Mghie confirmed that what I was trying was correct and should have worked. That indicated that I had a bug somewhere in my procedures of exiting and closing from my forms that was bringing up this exception.
Now that I know that, it won't take me long to find and fix the problem.
Found the problem: I was closing my main form from within the ThankYouForm, and that somehow looped back through into ExitExecute and, well, it got all bunged up.
But all's well again. The MainForm.Hide before the ThankYouForm.ShowModal works perfectly.
Thanks again, guys.
Instead of trying to shoehorn something into the main form, go to the place where you know everything else is finished running: the point where Application.Run returns. Create a new procedure that creates, shows, and destroys your farewell form, and then call it in your DPR file like this:
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
TThankYouForm.Execute;
end.
The display function can be along the lines of what Mghie's answer demonstrated:
class procedure TThankYouForm.Execute;
begin
with Create(nil) do try
ShowModal;
finally
Free;
end;
end;
You could do that in the OnClose handler of the main form. Be sure to ShowModal the other form, because otherwise it will be closed immediately when the closing of the main form terminates the application:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Hide;
with TThankYouForm.Create(nil) do try
ShowModal;
finally
Free;
end;
Action := caFree;
end;
or even
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Hide;
with TThankYouForm.Create(Application) do
ShowModal;
Action := caFree;
end;
And be sure to make the behaviour optional - when the user closes the app they are finished with it, and not everybody is pleased with programs that are so reluctant to go away.
Edit:
OK, showing such a form at the end of the trial period does indeed make sense. And while I can't really say why your code raises the exception - you should be able to find out by compiling with debug DCUs, setting a breakpoint on the line that raises the exception, and examine the stack trace. I assume some combination of the form properties and your code leads to another change of the Visible property higher up the stack, and you need to find out what it is and correct that. The code above should really work.
I would put (try) any of the code supplied above in the main form's OnCloseQuery event. Ensure that can close := false until you are ready to close the main form.
This may be caused by difference between order of method calls with order of message handler processing. After your method has completed there are still messages in operating system queue and they are being dispatched and handled by VCL.

Resources