views:

825

answers:

1

Hi,

My goal: User should be able to get the html source of a page, when clicked on a button. This event opens a new form with a geckoWebBrowser component and navigates to a given url. Once this is done it fires a documentCompleted event. Then I can start loading the DOM.

The problem: While the loading of the page needs time, I have to wait untill in the second form class the DOM (or just the value of a div) is loaded. This is exactly the problem! Every wait or loop stucks the second form (geckoBrowser) so I can't get the value.

This is the code of the first form:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace mozillaFirefox2
{
public partial class Form1 : Form
{

    string source = "";

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        //BackgroundWorker bw = new BackgroundWorker();
        //bw.DoWork += new DoWorkEventHandler(bw_DoWork);

        Browser br = new Browser();
        //object parameters = br;
        //bw.RunWorkerAsync(parameters);
        br.navigate("http://www.google.com/#hl=en&q=superman&aq=f&aqi=&oq=&fp=1&cad=b");

        Thread th = new Thread(delegate() { this.dw(br); });            
        th.Start();
        th.Join(2000);

        richTextBox1.AppendText(br.GetSource + "\n");            
    }

    private void dw(Browser br)
    {
        while (br.workDone == false)
        {
            //donothing
        }
        source = br.GetSource;
    }

    //void bw_DoWork(object sender, DoWorkEventArgs e)
    //{
    //    Browser br = (Browser)e.Argument;
    //    while (br.workDone == false)
    //    {
    //        //donothing
    //    }
    //    richTextBox1.AppendText(br.GetSource + "\n");
    //}
}
}

This is the second:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace mozillaFirefox2
{
//Declare a delegate who points to a function / signature
public delegate void GotDataEventHandler(object sender, EventArgs e); 


class Browser : Form
{
    public event GotDataEventHandler GotData;

    private System.ComponentModel.IContainer components = null;

    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.geckoWebBrowser1 = new Skybound.Gecko.GeckoWebBrowser();
        this.SuspendLayout();

        // 
        // geckoWebBrowser1
        // 
        this.geckoWebBrowser1.Location = new System.Drawing.Point(14, 4);
        this.geckoWebBrowser1.Name = "geckoWebBrowser1";
        this.geckoWebBrowser1.Size = new System.Drawing.Size(261, 67);
        this.geckoWebBrowser1.TabIndex = 2;
        this.geckoWebBrowser1.DocumentCompleted += new EventHandler(geckoWebBrowser1_DocumentCompleted);
        //Never forget this. Otherwise this error is raised "Cannot call Navigate() before the window handle is created"
        this.geckoWebBrowser1.CreateControl();
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(788, 577);
        this.Controls.Add(this.geckoWebBrowser1);
        this.Name = "Form1";
        this.Text = "Form1";
        this.ResumeLayout(false);            

    }

    #endregion

    public Skybound.Gecko.GeckoWebBrowser geckoWebBrowser1;

    public string source = "";

    public bool workDone = false;

    public bool navCall = false;

    [STAThread]
    public Browser()
    {
        Skybound.Gecko.Xpcom.Initialize(@"C:\Program Files\Mozilla-Gecko ActiveX WebBrowserControl\xulrunner-1.9.1.2.en-US.win32\xulrunner");
        Skybound.Gecko.GeckoPreferences.User["general.useragent.override"] = "Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)";
        this.InitializeComponent();                        
        this.Show();
    }


    public delegate void NavigationHandler(string url);
    public void navigate(string url)
    {
        if (this.InvokeRequired)
        {
            object[] parameters = { url };
            this.geckoWebBrowser1.Invoke(new NavigationHandler(navigate), parameters);
        }
        else
        {
            navCall = true;
            this.geckoWebBrowser1.Navigate(url);
        }
    }

    public string GetSource
    {
        get { return source; }
    }

    public string getSource()
    {
        return source;
    }

    public void geckoWebBrowser1_DocumentCompleted(object sender, EventArgs e)
    {
        if(navCall == true){
            source = this.geckoWebBrowser1.Document.GetElementById("res").InnerHtml.ToString();
            workDone = true;
            navCall = false;

            //
            GotDataEventHandler gd;
            lock (this)
            {
                gd = GotData;
            }
            if (gd != null)gd(this, EventArgs.Empty);

        }
    }
}
}

Now I should be able to just wait till I get an answer within the button1_Click function or catch the event within this function or a function on the second form so I can return this (than the code will wait by itself). Don't know how to do this.

Thanks in advance.

+1  A: 

The basic idea would be to split the function into two parts, putting the code that should be run after the event occurs in an anonymous delegate:

Instead of

void doStuff() { 
    firstPart();
    waitEvent();
    secondPart();
}

you do

void doStuff() { 
    firstPart();
    some.Event += delegate {
        secondPart();
    }
}

The only alternative I know of is to put the whole processing into a second thread and use Monitor.Wait and Monitor.Pulse to tell the second thread when it may continue. This will create more overhead and will be generally slower, though. It might be a good idea, if you need to wait for 10 events and continue when the last one arrives (and you don't know the order), but not in that simple case you outline above.

mihi
Your first solution will not work, cause still the code continues even though I'am registered on an event. The first class needs an answer via a call to the second class and since I don't know when It's return value is ready, I can't return something except an event to the caller, that is ready (if I want to return a value at that time, I'm to late).The caller wants an answer, and does not care if it has to wait.
CappaMontana
If you really want to block in your function until you get an answer, you will have to make sure that all your callers will not call your function from an event dispatcher (like in your example button_click) directly. As long as a button handler is run, the central message queue of the process will block so no other UI events can be created. can't you provide an event to your caller as well?
mihi
Yes I thought about that, without really success. But once removing the code from the dispatcher in form1 and form2 (just made a while loop that checks on present of some data instead of the documentLoaded event) everything worked fine!Tnx!
CappaMontana