I want to prevent iframe elements from triggering the OnDocumentComplete event every time. For example, a page has 4 iframes, and when I load this page, my OnDocumentComplete event runs 4 times. I want to run OnDocumentComplete just once for every page. How can I do that?
Maybe I could remove or block iframes in TWebBrowser control.
The event OnDocumentComplete is fired for each FRAME/IFRAME in the main document.
If you want to ignore them try this:
procedure TForm1.WebBrowser1DocumentComplete(Sender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
begin
// check that the event is raised for the top-level browser (not frames or iframes)
if pDisp = TWebBrowser(Sender).ControlInterface then
begin
// do something nice...
end;
end;
From Delphi Docs:
Write an OnDocumentComplete event handler to take specific action when a frame or document is fully loaded into the Web browser. For a document without frames, this event occurs once when the document finishes loading. On a document containing multiple frames, this event occurs once for each frame. When the multiple-frame document finishes loading, the Web browser fires the event one final time.
Sender is the Web browser that is loading the document.
pDisp is the Automation interface of the top-level frame or browser.
When loading a document without frames, pDisp is the interface of the
Web browser. When loading a document with multiple frames, this is the
interface of the containing frame, except for the very last time the
event occurs, when it is the interface of the Web browser.
Related
I am using TWebBrowser in my Delphi firemonkey application and would like to disable right click on the page.
Is there any way for this.
The default TWebBrowser for Firemonkey doesn't do this natively as of 10.3 Rio. Outside of a different browser component, your best bet is to use Javascript. If you are controlling the content that is served, that's pretty easy. See How do I disable right click on my web page?
If you are dealing with another website whose content you don't have control over, you can try to inject the Javascript using TWebBrowser.EvaluateJavaScript()
procedure TForm1.DisableRC;
var
strJS: string;
begin
strJS := 'document.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);';
webbrowser1.EvaluateJavaScript(strJS);
end;
The code works if you call DisableRC; from say, a button click. But if the URL reloads or content changes, you will need to call it again.
I tried placing a call to DisableRC() in the TWebBrowser.OnDidFinishLoad event, to execute it after page navigation completes, but the event ended up firing thousands of times in an infinite loop. Using TThread.Queue made no difference. Possibly this is because the evaluation of Javascript makes the event fire again.
What ended up working was placing a TTimer on the form, disabled by default, with the following code in OnTimer:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
DisableRC;
Timer1.Enabled := false;
end;
And then enabling the Timer in the TWebBrowser.OnDidFinishLoad event.
It's somewhat of a hack, but hopefully it is helpful to get you started in your implementation.
I believe you may be able to modify this code and trap the message WM_RBUTTONDOWN. Replace the WM_LBUTTONDOWN in the referenced code with WM_RBUTTONDOWN. I'd try it but i only have C++ Builder installed.
How to know if the TWebBrowser already finished to download the page?
My problem is: I can't know when my page was completely downloaded so it can be shown.
I request one page to my webbrowser and I want to show the response only when the page was completely downloaded.
You could try handling the OnDocumentComplete event.
If the site uses scripting to trigger downloading of additional data, you may have to employ more sophisticated methods since the event will fire before the page finishes running all its scripts. In general, the task begins to look like the halting problem. You might wish to refine your definition of "completely downloaded" to exclude certain difficult-to-detect cases.
source: http://www.delphifaq.com/faq/delphi/network/f264.shtml
Indeed, in case of multiple frames, OnDocumentComplete gets fired multiple times. Not every frame fires this event, but each frame that fires a DownloadBegin event will fire a corresponding DocumentComplete event.
How can the 'real completion' be recognized?
The OnDocumentComplete event sends parameter pDisp: IDispatch, which is the IDispatch of the frame (shdocvw) for which DocumentComplete is fired. The top-level frame fires the DocumentComplete in the end.
So, to check if a page is done downloading, you need to check if pDisp is same as the IDispatch of the WebBrowser control.
That's what the code below demonstrates:
procedure IForm1.WebBrowser1Documentccmplete(Sender: Iobject:
const pDisp: Inispatch; var URL: OLEvariant):
var
Curwebrowser : IWebBrowser:
IopWebBrowser: IWebBrowser:
Document : OLEvariant;
WindowName : string:
begin { TForm1.WebBrowser1DocumentComplete }
Curwebrowser := pDisp as IWebBrowser:
TopWebBrowser := (Sender as IWebBrowser).DefaultInterface;
if CurWebrowser=TopWebBrowser then
begin
ShowMessage('Document is complete.')
end
else
begin
Document := CurWebrowser.Document;
WindowName := Document.ParentWindow.Name:
ShowMessage('Frame ' + WindowName + ' is loaded.')
end:
end;
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.
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.
I am using a TWebBrowser component which I use to load XML documents into which are linked to a XSL file.
I have a default page I display when no XML document is loaded. However, if the user deletes the XML file whilst it is open in the browser and then refreshes I get the standard resource could not be found error. What I would like to do instead is if the page cannot be loaded, check that the file exists, and if it doesn't just load the default page again.
I have tried using the OnNavigateError and OnBeforeNavigate2 events however they does not seem to trigger on a refresh.
Any idea's?
There is an onRefresh event which is exposed by the TWebBrowser replacement TEmbeddedWB. This version also exposes many other features which are otherwise hidden by the TWebBrowser component.
This is a bit of a cludge but it works in my tests, using the standard TWebBrowser component.
What I did was override the F5 key in the form's OnKeyUp event. By setting the form's KeyPreview property to True, you can call your own refresh. Seeing as the TWebBrowser.Refresh method doesn't appear to call any of the navigation events (as you said in your question), I call the TWebBrowser.Navigate event myself, which does fire the events.
You need to store the URL, which I imagine you're already doing. But if not, then in the BeforeNavigate2 event, you are given the URL as a parameter. So store this in a variable or on-screen control. Then, when F5 is pressed (or the Refresh button if you have put one on screen), simpy navigate to that URL again. The OnBeforeNavigate2, OnNavigateComplete2 and OnDocumentComplete events are all fired again, allowing you the opportunity to perform your test and put your placeholder page up instead of IEs default error page.
procedure TForm1.WebBrowser1BeforeNavigate2(Sender: TObject;
const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
Headers: OleVariant; var Cancel: WordBool);
begin
Edit1.Text := URL;
end;
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_F5) then
begin
WebBrowser1.Navigate(Edit1.Text);
end;
end;