views:

203

answers:

6

I have a win forms application that contains a Web Browser control. I need to be able to add delay between operations on the web-browser due to the asynchronous nature of navigating.

The Document_Complete event is worthless as it does not take into account that a page may contain multiple AJAX requests. The event ofter fires many times.

UPDATE

The AJAX Requests are made when the page is loaded. So the page loads and content in some DIV is fetched via an HTTP request. So the the Document_Complete event is raises when the document first loads and then when each (AJAX) HTTP request returns. No Bueno.

UPDATE2

My application attempts to read HtmlElements from the Webbrowser.Document object. Because the code executes faster than the HTTP Requests return... the document object does not contain all of the html elements.

What I need is some way to delay the call of methods in the main thread. I have tried using a timer:

private void startTimer()
        {
            timer.Interval = 2000;
            timer.Start();
            while (!BrowserIsReady)
            {
                //Wait for timer
            }
        }

This locks up the thread and the tick event never fires. This loops never ends.

I want to run a series of methods like this:

Navagate("http://someurl.com");
//delay
ClickALink();
//delay
Navagate("Http://somewhere.com");
//delay

Can I solve this problem with a timer and the BackgroundWorker? Can someone suggest a possible solution?

A: 

Unfortunately you have to call Application.DoEvents() in order to keep the browser functional. Beyond that, I also pay attention to the ReadyState property.

  while (BusyNavigating || (wbBrowser.ReadyState != WebBrowserReadyState.Complete))
  {
    Application.DoEvents();
  } 
chilltemp
These states will change multiple times per request in the case of AJAX. How can this be implemented to pause code execution unitl the browser has finished all HTTP requests?
Nick
@nick: What is causing the AJAX events to fire? I haven't had problems using this technique to sleep after page loads or when I've invoked click methods on buttons, etc. You've got a bigger challenge if the AJAX events are fired from timers. You may have to add checking for a page element to your loop. Sorry, but web scraping is a messy task.
chilltemp
The Ajax requests are called by a variety of events. I do not control the web page design. I am interested in getting DOM elements from the page once it has loaded. i.e a user clicks a link -> the ReadyState might change 4-5 times before the page is "complete". It seems that the Document_Complete event does not take into account active http requests.
Nick
@Nick how would your application deal with things like StackOverflow which has AJAX calls on a timer. Halting problem: how do you determine whether or not it will ever stop?
@devinb I am certain that the pages in question will be complete in several seconds. I cannot find a more elegant way to ensure page completion. I am not certain how make this delay possible.
Nick
@Nick are you targeting *particular* pages? As in, this does not target "the web" in general? If you have a specific set of targeted pages, then you might be able to cue off particular variables *on* the page.
@Nick: There is no silver bullet here. You are going to have to use page content to determine when each page is 'ready' when dealing with AJAX. Some sites like StackOverflow and Facebook are never done because they have timer loops that poll indefinitely.
chilltemp
A: 

Document_Complete does tell you when the page has finished loading.

AJAX simply enables it to then load something else. Certain WebApplications (like StackOveflow) have AJAX calls on a timer which means that your application would never load fully because there is always an AJAX call waiting to be made/return.

This is a problem because the callback for an AJAX function could simply kick off another AJAX function. Without parsing their entire page (creating an execution tree) there really isn't a way for you to determine when the page is truly and completely loaded.

Perhaps you could clarify explicitly what it is that you application does that needs to wait until all the AJAX calls have completed and why waiting for Document_Complete is not good enough.

Question Updated.
Nick
@Nick Do you know how many times the Document_Complete event will fire? or is it variable as well. Basically, the best thing to do is still cue off that event. Basically, is there any way to determine *from the page **content** itself* whether or not all the elements have been loaded?
You may be right.. I will look to see if counting the event occurrences will work.
Nick
A: 

I think one of the problems you might be having (can't tell because the code is not complete) is that you're trying to do stuff on your control when invoke is required.

I coded up a little form that changes the url every five seconds. Remember that interrogating the web browser's state on a background thread also isn't allowed.

  public partial class Form1 : Form
{
    System.Timers.Timer t = new System.Timers.Timer();
    Stack<string> urls = new Stack<string>();

    public Form1()
    {
        InitializeComponent();
        urls.Push("http://stackoverflow.com/");
        urls.Push("http://msdn.microsoft.com/");
        urls.Push("http://maps.google.com");


        t.AutoReset = false;
        t.Interval = 5000;
        t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);

    }



    void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {

         if (urls.Count > 0 )
        {
             string url = urls.Pop();
             SetUrl(url);

         }



         t.Interval = 5000;
        t.Start();

    }

    static void SynchronizedInvoke(ISynchronizeInvoke sync, Action action)
    {
        // If the invoke is not required, then invoke here and get out.
        if (!sync.InvokeRequired)
        {
            // Execute action.
            action();

            // Get out.
            return;
        }

        // Marshal to the required thread.
        sync.Invoke(action, new object[] { });
    }

    private void SetUrl(string url)
    {
        SynchronizedInvoke(webBrowser1, () => webBrowser1.Navigate(url));
    }



    private void button1_Click(object sender, EventArgs e)
    {
        t.Interval = 1;
        t.Start();
    }




}
Conrad Frix
A: 

Thanks for all the suggestions. This is the "Delay" solution I came up with last night. It feels like using a steam roller to crack a peanut but I couldn't find a more 'elegant' answer to this particular problem.

I am spinning off a new worker thread that invokes a method called "AddDelay". This method will put the worker thread to sleep for some interval of time. My main (UI) thread loops on the Thread.IsAlive condition while allowing the application to receive OS messages if the thread has not completed.

This seems to do the trick.

 private void Delay()
        {
            DelayTimer dt = new DelayTimer(1);
            Thread thread = new Thread(new ThreadStart(dt.AddDelay));
            thread.Start();
            while (thread.IsAlive)
            {
                Application.DoEvents();
            }

        }




public class DelayTimer
    {
        private int _seconds;
        public DelayTimer(int time)
        {
            _seconds = time;
        }
        public void AddDelay()
        {
           Thread.Sleep(_seconds*1000);
        }
    }
Nick
How are you detecting when it is done? This is just a glorified sleep statement.
chilltemp
A: 

You might consider going with the Monitor class in the Threading namespace. Example below.

using System;
using System.Threading;

namespace MonitorWait
{
    class Program
    {
        private static readonly object somelock = new object();

        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(SyncCall));

            Thread.Sleep(5000);  //Give the SyncCall a chance to run...
            //Thread.Sleep(6000);  //Uncomment to see it timeout.

            lock (somelock)
            {
                Monitor.Pulse(somelock);  //Tell the SyncCall it can wake up...
            }

            Thread.Sleep(1000);  //Pause the main thread so the other thread 
                                 //prints before this one :)

            Console.WriteLine("Press the any key...");
            Console.ReadKey();
        }

        private static void SyncCall(object o)
        {
            lock (somelock)
            {
                Console.WriteLine("Waiting with a 10 second timeout...");
                bool ret = Monitor.Wait(somelock, 10000);
                if (ret)
                {
                    Console.WriteLine("Pulsed...");
                }
                else
                {
                    Console.WriteLine("Timed out...");
                }
            }
        }
    }
}
Nate
A: 

Can't you simply use:

   System.Threading.Thread.Sleep(1000);

This should have the same effect surely?

Geoff
No it does not. This would put the main thread to sleep. The browser is running on the main thread. The delay must be moved off of the main thread if the browser is to continue "working"
Nick