Question

I'm currently working on an application which has one textBox for searching a string.

I have one tabControl with four tabPages. In each tab there's a WebBrowser. These tabs are never to be modified, they're just suppposed to be there and stay forever.

The WebBrowsers are set to navigate to, lets say: ("https://www.google.se/#q=" + textBox.Text); when pressing the search button.

I want to make the web browsers run on separate threads, but I'm not sure how.

Any thoughts?

No correct solution

OTHER TIPS

Below is a solution i have used for creating a tabbed browser where every browser was running in a separate thread. I have to warn you that it is not an easy solution and that this kind of windows hacking will cause a lot of minor interface and threading problems. (All solvable, but take some time for testing your application)

The solution consists of a container panel (BrowserPanel) and an embedded web browser panel (BrowserForm). Creating a BrowserPanel will start a web browser inside itself in a separate thread.

This is far from a complete solution but hopefully it will help you start.

1) Create a separate class file for the user32.dll methods (you might already have these)

public static class User32
{
    [DllImport("user32.dll")]
    public static extern IntPtr SetParent(IntPtr wnd, IntPtr parent);

    [DllImport("user32.dll")]
    public static extern bool SetWindowPos(IntPtr wnd, IntPtr parent, int x, int y, int w, int h, uint flags);

    [DllImport("user32.dll")]
    public static extern IntPtr SetFocus(IntPtr wnd);
}

2) Create the container control

Create a new class file and name it BrowserPanel. This control will live on the main UI thread and acts as a placeholder for the web browser form below.

public class BrowserPanel : Panel
{
}

3) Create the web browser form

Create a new form and place the webbrowser control on it. Name the form BrowserForm. This form will have its own seperate thread. The form must have a link to the BrowserPanel. (see source code below)

public partial class BrowserForm : Form
{
    public BrowserPanel Panel { get; private set; }

    public BrowserForm(BrowserPanel panel)
    {
        Panel = panel;
        InitializeComponent();
        FormBorderStyle = FormBorderStyle.None;
        TopLevel = false;
    }
}

3) Add creation code

Add the following code to the BrowserPanel class.

public class BrowserPanel : Panel
{               
    public BrowserForm Browser { get; private set; }

    private IntPtr _threadownerhandle;
    private IntPtr _threadformhandle;
    private Thread _thread;
    private AutoResetEvent _threadlock;

    public BrowserPanel(): Panel
    {
        Resize += OnResize;
        ThreadCreate();            
    }   

    public void ThreadCreate()
    {
        // The following line creates a window handle to the BrowserPanel
        // This has to be done in the UI thread, but the handle can be used in an other thread
        _threadownerhandle = Handle;

        // A waiting lock
        _threadlock = new AutoResetEvent(false);

        // Create the thread for the BrowserForm
        _thread = new Thread(ThreadStart);
        _thread.SetApartmentState(ApartmentState.STA);
        _thread.Start();

        // Let's wait until the Browser object has been created
        _threadlock.WaitOne();
    }


    private void ThreadStart()
    {   
        // This a NOT the UI thread
        try
        {
            // Create the BrowserForm in a new thread
            Browser = new BrowserForm(this);

            // Get the handle of the form. This has to be done in the creator thread
            _threadformhandle = Browser.Handle;

            // The magic. The BrowserForm is added to the BrowserPanel
            User32.SetParent(_threadformhandle, _threadownerhandle);

            // Make the BrowserForm the same size as the BrowserPanel
            ThreadWindowUpdate();
        }
        finally
        {
            // Notify the BrowserPanel we are finished with the creation of the Browser
            _threadlock.Set();
        }

        try
        {
            // With the next line a (blocking) message loop is started
            Application.Run(Browser); 
        }
        finally
        {
            Browser.Dispose();
        }
    }

    private void OnResize(object sender, EventArgs e)
    {
        // Resizing the BrowserPanel will resize the BrowserForm too
        if (Browser != null) ThreadWindowUpdate();
    }               

    public void ThreadWindowUpdate()
    {
        if (Browser == null) return;
        User32.SetWindowPos(_threadformhandle, IntPtr.Zero, 0, 0, Width, Height, 0);
    }
}

4) Add some more logic to the BrowserPanel class

    public void Focus()
    {
        // normal focus will not work
        User32.SetFocus(_threadformhandle);
    }

Are we done yet. No!

Calling the browser control methods from the main UI thread can cause thread exceptions. For many WebBrowser methods you have to make a wrapper in the BrowserForm like the one below. Some WebBrowser methods can be called from another thread without problems. Find out by trial and error.

    public void BrowserPrint()
    { 
        if (InvokeRequired)
            BeginInvoke(new MethodInvoker(() => { webBrowser1.Print(); }));
        else
            webBrowser1.Print();
    }

    public void BrowserClose()
    {
        Browser.DialogResult = DialogResult.Cancel; // or whatever
        if (InvokeRequired)
            BeginInvoke(new MethodInvoker(() => { this.Close(); }));
        else
            this.Close();
    }

The same for WebBrowser events calling the main UI thread. For example:

    In BrowserForm:

    private void webBrowser1_StatusTextChange(object sender, StatusTextChangeEventArgs e)
    {
        Panel.EventStatusTextChange(e.text);
    }

    In BrowserPanel:

    public void EventStatusTextChange(string text)
    {
        if (_statustext == text) return;
        _statustext s = text;
        if (InvokeRequired)
            BeginInvoke(new MethodInvoker(() => { Owner.StateChanged(this); }));
        else
            Owner.StateChanged(this);
    }

Some special thing you have to take care of:

  • Use BeginInvoke instead of Invoke when possible. Deadlock will occur if a call to the WebBrowser control with a blocking Invoke will cause a callback event with another blocking Invoke

  • A popup menu created in the main UI thread wil not disappear when clicking on a WebBrowser control in another window. (Solve this by catching the onclick event of the WebBrowser control and route this back to the main form)

I don't think there's an easy way to have the WebBrowser control run in separate threads, since it needs to work on the thread that created the control. I also don't think that there's an easy way to create a new thread for each tab.

The closest I could find where someone posted a workaround is:

WebBrowser Control in a new thread

WPF's WebBrowser navigates asynchronously. If you're using Windows Forms, you can embed it using the ElementHost class. This tutorial explains how to host a WPF control in Windows Forms.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top