2

Well, i have a form which contains 5 WebBrowser controls each opens a page and handle DocumentComplete event, The problem is sometimes events don't fire. After reading i came across an answer saying that if the thread is busy the events like Navigated, DocumentComplete won't fire so i tried to create the WebBrowser in a separate thread then add it to the form which results in a cross thread exception

Code

        new Thread(() =>
        {
            WebBrowser1 = new WebBrowser
            {
                Location = new Point(15, 14),
                MinimumSize = new Size(20, 20),
                Name = "WebBrowser1",
                ScriptErrorsSuppressed = true,
                Size = new Size(250, 370),
                TabIndex = 0,
                Url = new Uri("", UriKind.Relative)
            };

            ((WebBrowser_V1) WebBrowser1.ActiveXInstance).NewWindow += (string u, int f, string n, ref object d, string h, ref bool p) =>
            {
                p = true;
                WebBrowser1.Navigate(u);
            };

            WebBrowser1.DocumentCompleted += async (sender, args) =>
            {
                // Code...
            };

            WebBrowser1.Navigated += (sender, args) =>
            {
                // Code...
            };

            WebBrowser1.Navigate(Service.LinkTL.Find(_ => _.Valid)?.Use()?.Address);

            //Cross-Thread Exception
            base.Invoke(new MethodInvoker(() =>
            {
                Controls.Add(WebBrowser1);
            }));

            Application.Run();
        }) {ApartmentState = ApartmentState.STA}.Start();

I need the WebBrowser control to be visible in the form yet be able to intercept all triggered events.

Edit: if i invoke WebBrowser i get Controls created on one thread cannot be parented to a control on a different thread exception.

Erric J Manderin
  • 1,096
  • 3
  • 10
  • 23
  • this also might help [WebBrowser Control in a new thread ](https://stackoverflow.com/questions/4269800/webbrowser-control-in-a-new-thread) – Lei Yang Jul 06 '17 at 02:31

1 Answers1

0

You need to check InvokeRequired on Cross Thread operations (see: https://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired(v=vs.110).aspx).

If it is, you need to invoke the delegate from your code.

 myFormControl1.Invoke(myFormControl1.myDelegate);

A typical pattern is:

private void DoFoo() {
    if (myFormControl1.InvokeRequired) {
        myFormControl1.Invoke(new MethodInvoker(() => { DoFoo(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

This thread discusses ways to implement this cleanly without dirtying your code: Automating the InvokeRequired code pattern

A useful answer in the thread, shows a static method to handle invocation:

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

And then defining the delegates like:

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

Using this to accomplish your specific question:

public static class myExtension
{
    public static void InvokeIfRequired(this Control control, MethodInvoker action)
    {
        // See Update 2 for edits Mike de Klerk suggests to insert here.

        if (control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }
}

// ...

protected WebBrowser WebBrowser1;
public void loadBrowser()
{
    new Thread(() =>
        {

            var WebBrowser1 = new WebBrowser
            {
                Location = new Point(15, 14),
                MinimumSize = new Size(20, 20),
                Name = "WebBrowser1",
                ScriptErrorsSuppressed = true,
                Size = new Size(250, 370),
                TabIndex = 0,


            };
            var form = new Form()
            {
                Name = "Other Form",
                Text = "Thread Start Browser",
                AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F),
                AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font,
                ClientSize = new System.Drawing.Size(284, 261),
            };
            form.Controls.Add(WebBrowser1);
            form.Show();
            //((WebBrowser_V1)WebBrowser1.ActiveXInstance).NewWindow += (string u, int f, string n, ref object d, string h, ref bool p) =>
            //{
            //    p = true;
            //    WebBrowser1.Navigate(u);
            //};

            //WebBrowser1.DocumentCompleted += async (sender, args) =>
            //{
            //    // Code...
            //};

            WebBrowser1.Navigated += (sender, args) =>
            {
                // Code...
            };

            //WebBrowser1.Navigate(Service.LinkTL.Find(_ => _.Valid)?.Use()?.Address);

             WebBrowser1.Navigated += (sender, args) =>
            {
                // Code...
            };

            //WebBrowser1.Navigate(Service.LinkTL.Find(_ => _.Valid)?.Use()?.Address);

            this.InvokeIfRequired(() =>
            {
                this.WebBrowser1 = WebBrowser1;
                this.CreatedBrowser(); 

            });

            Application.Run();
        })

        { ApartmentState = ApartmentState.STA }.Start();
    }
private void Form1_Load(object sender, EventArgs e)
{
    loadBrowser();
}
public void CreatedBrowser()
{
    Console.WriteLine("Created browser");
}
private void btnGo_Click(object sender, EventArgs e)
{
    string url = txtUrl.Text;
    WebBrowser1.InvokeIfRequired(() => WebBrowser1.Navigate(url));
}

Here's a screen shot:

WebBrowser created on a separate thread

Alexander Higgins
  • 6,765
  • 1
  • 23
  • 41
  • I think this is not an answer to my specific question because even through i am invoking in the code you see i still get cross threading exception which isn't supposed to be – Erric J Manderin Jul 06 '17 at 01:43
  • But you are calling this method from a different thread. Your base control needs to be the one to check for and invoke the method in the caller thread. – Alexander Higgins Jul 06 '17 at 01:46
  • I updated my answer, I do not get a cross thread exception using the method I described. – Alexander Higgins Jul 06 '17 at 02:05
  • Two things. First I didn't realize the the loading thread was hung. The invoke never completed. So then I set I had Control.CheckForIllegalCrossThreadCalls = false; and now get I 'Controls created on one thread cannot be parented to a control on a different thread.' So I guess you can't do what you are trying to accomplish. – Alexander Higgins Jul 06 '17 at 02:40
  • Okay, got around the last error by creating another form in the separate thread to parent the browser. See screenshot and updated code. – Alexander Higgins Jul 06 '17 at 03:09