I am trying to make my WebBrowser wait until the page fully loads, then proceed to the next step. I've research how to do this, yet, my code keeps running before the page loads.
private void AdobeConnect_Load(object sender, EventArgs e)
{
for (int x = 1; x <= 3; x++)
{
while (acBrwsr.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
adobeStepper(x);
}
}
The problem is you are relying on ReadyState and you shouldn't be. In WebBrowser.DocumentComplete you need to check e.Url == WebBrowser.Url then check for the ready state. DocumentComplete fires multiple times with forms when you have frames and that messes with ReadyState.
What I do with my bots that use WebBrowser is I activate a timer when I have a document complete state for the actual page then grab my next item in the queue to process for that page like 1 second after the completion. (Of course always turn off the timer in the OnTick event.)
I wrote a queue that groups a set of tasks together where I can prioritize and remove any items like a list so I don't repeat tasks but only perform when DocumentCompleted e.url == webBrowser.url and my ReadyState is Complete.
Related
I have a Button on Page 1 and on Button_click Doing some operations with a background worker and showing the progress and once the operation is finished , application
loads a new page like this
Uri ViewPageUri = new System.Uri("View.xaml", UriKind.Relative);
private void bookOpeningWorker_WorkFinished(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error == null)
{
NavigationService.Navigate(ViewPageUri);
}
}
Inside View Page Loaded event doing some other time consuming operations in a Background worker and showing the waiitng progress.
The second page contains few user controls and some other browser controls , but all the operations are performing on Load event of the page
All things are working fine , but the only issue is application is taking some time to finish execution between the lines NavigationService.Navigate and Loaded event of the Page
So i am getting a delay of 3-4 seconds betweeen this time and no progress or waiting messages are visible to end user and also i can see the pointer in a waiting mode .
Is there any specific reason for this effect?or any better way to improve the code?
I am currently trying to create a program that will show a slideshow of pictures from an ImageList with variable show times from a ListView, which is acessed through a numericUpDown, but when I click the button to start, nothing happens, until the time of the slideshow ends, where the last slide is shown, then disappears straight away (if i leave out "pictureBox1.Visible = false" at the end, it stays).
current code:
private void buttonSlideshow_Click(object sender, EventArgs e)
{
pictureBox1.Visible = true;
for (int s = 0; s < listView1.Items.Count; s++)
{
listView1.Items[s].Selected = true;
pictureBox1.Image = imageList1.Images[s];
DateTime later = DateTime.Now.AddMilliseconds((double)numericUpDown1);
while (DateTime.Now < later)
{
}
}
pictureBox1.Visible = false;
}
I have tried many versions of this (including using timers and switching code around) have no idea what is going wrong
numericUpDown1 is the duration of the slide, have been using integers for testing, still doesn't work
Doing this:
while (DateTime.Now < later)
Is blocking your UI thread. It can't redraw the screen while you are keeping it busy spinning it's wheels. Use a timer with a call back instead.
You'll need to rework your code slightly. Rather than use a for loop, define a variable for your current slide, initialize it to zero in your click handler, make your picture box visible, load the first picture and then start your timer.
In your timer handler, you'll increment the current index, check it against the length of you list of slides and, if you still have slides to display, load the next one and then start the timer again (or you can use a timer that fires repeatedly, although you might want to be careful that it doesn't fire again before you're done handling that last one). If there are no more slides left, just do you clean up and make your picture box invisible again.
One final note, be careful with which thread the timer is going to fire on. In win forms you can use System.Windows.Forms.Timer which fires on the UI thread and you won't have any problems. In WPF you can use DispatcherTimer which also fires on the UI thread. Other timers generally run in their own thread and you'll have to handle pushing anything that's supposed to update the UI back to the UI thread.
My app needs to click a button on a page x number of times (user defined).
The page works like this: After you click a button, the page reloads with the same yes/no buttons. Using my code, the app goes crazy and starts clicking way too fast and then crashes.
What can I do so it waits until the page reloads?
int toGet = Convert.ToInt32(numberOfTimes.Text);
int got = 0;
while (got < toGet)
{
while (webBrowser1.ReadyState != WebBrowserReadyState.Complete) Application.DoEvents();
webBrowser1.Document.GetElementById("votea").InvokeMember("click");
got++;
}
Add a delay, e.g. 500ms, between the clicks inside your while loop by using setTimeout() function
I've been researching this stuff and everyone seems to agree that the solution is to check the ReadyState of the Web Browser until is set to Complete.
But actually the event is sometimes fired with the ReadyState set to Complete several times.
I don't think there is a solution with that crappy .NET WebBrowser, but there might be one if I use the underlying DOM component.
Only problem is, I have no idea how do access the DOM component behind the WebBrowser that fires the DocumentCompleted event.
DocumentCompleted will fire for each frame in the web page. The hard way is to count off the frames, shows you how to access the DOM:
private int mFrameCount;
private void startNavigate(string url) {
mFrameCount = 0;
webBrowser1.Navigate(url);
}
private void DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
mFrameCount += 1;
bool done = true;
if (webBrowser1.Document != null) {
HtmlWindow win = webBrowser1.Document.Window;
if (win.Frames.Count > mFrameCount && win.Frames.Count > 0) done = false;
}
if (done) {
Console.WriteLine("Now it is really done");
}
}
The easy way is to check the URL that completed loading:
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (e.Url.Equals(webBrowser1.Url)) {
Console.WriteLine("Now it is really done");
}
}
This would probably happen if the page uses Javascript or <meta refresh> to redirect to another page.
If so, there's no good workaround.
I can't find anything that will give 100% certainty.
Mentioned example (e.Url.Equals(webBrowser1.Url)) may work for a simple WebBrowser.Navigate(url), however, in my case I click nodes in code to open new frames in existing frames. Mostly the number of times "Navigating" and "DocumentCompleted" fire will be the same, but again NOT always. "isBusy = false" and "ReadyState = Complete" will always be the case when it's finished (at least so far) but it will also a few times have this state when it's still loading. Counting frames also seems useless for me, in one case DocumentCompleted is fired 23 times, however, all frames and sub(-sub-sub and so on) frames are 14 in total.
The only thing that seems to work is wait a short period (1 or 2 seconds?) to see if anything happens (any events fired, any state changes).
Hmm, I found another solution for me. Often we're not interested in the whole page being loaded, often we want certain elements to exists. So after each DocumentCompleted and when "isBusy = false" and "ReadyState = Complete" we can search the DOM if this element exists.
In my experience it's impossible to tell when a web page has finished loading until DocumentCompleted hasn't fired for a while. So I refresh a timer for around 1000ms every time the DocumentCompleted event triggers. Then when the timer times out I process the web page.
How can I detect when a System.Windows.Forms.WebBrowser control has completed loading?
I tried to use the Navigate and DocumentCompleted events but both of them were raised a few times during document loading!
I think the DocumentCompleted event will get fired for all child documents that are loaded as well (like JS and CSS, for example). You could look at the WebBrowserDocumentCompletedEventArgs in DocumentCompleted and check the Url property and compare that to the Url of the main page.
I did the following:
void BrowserDocumentCompleted(object sender,
WebBrowserDocumentCompletedEventArgs e)
{
if (e.Url.AbsolutePath != (sender as WebBrowser).Url.AbsolutePath)
return;
//The page is finished loading
}
The last page loaded tends to be the one navigated to, so this should work.
From here.
The following should work.
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
//Check if page is fully loaded or not
if (this.webBrowser1.ReadyState != WebBrowserReadyState.Complete)
return;
else
//Action to be taken on page loading completion
}
Note the url in DocumentCompleted can be different than navigating url due to server transfer or url normalization (e.g. you navigate to www.microsoft.com and got http://www.microsoft.com in documentcomplete)
In pages with no frames, this event fires one time after loading is complete. In pages with multiple frames, this event fires for each navigating frame (note navigation is supported inside a frame, for instance clicking a link in a frame could navigate the frame to another page). The highest level navigating frame, which may or may not be the top level browser, fires the final DocumentComplete event.
In native code you would compare the sender of the DocumentComplete event to determine if the event is the final event in the navigation or not. However in Windows Forms the sender parameter is not wrapped by WebBrowserDocumentCompletedEventArgs. You can either sink the native event to get the parameter's value, or check the readystate property of the browser or frame documents in the DocumentCompleted event handler to see if all frames are in the ready state.
There is a prolblem with the readystate method as if a download manager is present and the navigation is to a downloadable file, the navigation could be cancelled by the download manager and the readystate won't become complete.
I had the same issue of multiple DocumentCompleted fired events and tried out all the suggestions above. Finally, seems that in my case neither IsBusy property works right nor Url property, but the ReadyState seems to be what I needed, because it has the status 'Interactive' while loading the multiple frames and it gets the status 'Complete' only after loading the last one. Thus, I know when the page is fully loaded with all its components.
I hope this may help others too :)
It doesn't seem to trigger DocumentCompleted/Navigated events for external Javascript or CSS files, but it will for iframes. As PK says, compare the WebBrowserDocumentCompletedEventArgs.Url property (I don't have the karma to make a comment yet).
If you're using WPF there is a LoadCompleted event.
If it's Windows.Forms, the DocumentCompleted event should be the correct one. If the page you're loading has frames, your WebBrowser control will fire the DocumentCompleted event for each frame (see here for more details). I would suggest checking the IsBusy property each time the event is fired and if it is false then your page is fully done loading.
Using the DocumentCompleted event with a page with multiple nested frames didn't work for me.
I used the Interop.SHDocVW library to cast the WebBrowser control like this:
public class webControlWrapper
{
private bool _complete;
private WebBrowser _webBrowserControl;
public webControlWrapper(WebBrowser webBrowserControl)
{
_webBrowserControl = webBrowserControl;
}
public void NavigateAndWaitForComplete(string url)
{
_complete = false;
_webBrowserControl.Navigate(url);
var webBrowser = (SHDocVw.WebBrowser) _webBrowserControl.ActiveXInstance;
if (webBrowser != null)
webBrowser.DocumentComplete += WebControl_DocumentComplete;
//Wait until page is complete
while (!_complete)
{
Application.DoEvents();
}
}
private void WebControl_DocumentComplete(object pDisp, ref object URL)
{
// Test if it's the main frame who called the event.
if (pDisp == _webBrowserControl.ActiveXInstance)
_complete = true;
}
This code works for me when navigating to a defined URL using the webBrowserControl.Navigate(url) method, but I don't know how to control page complete when a html button is clicked using the htmlElement.InvokeMember("click").
You can use the event ProgressChanged ; the last time it is raised will indicate that the document is fully rendered:
this.webBrowser.ProgressChanged += new
WebBrowserProgressChangedEventHandler(webBrowser_ProgressChanged);