选项卡控件中的多个Web浏览器

时间:2014-03-03 23:52:23

标签: c# multithreading winforms webbrowser-control tabcontrol

我目前正在开发一个应用程序,它有一个用于搜索字符串的textBox。

我有一个带四个tabPages的tabControl。在每个标签中都有一个WebBrowser。这些标签永远不会被修改,它们只是被用来存在并永远存在。

WebBrowsers设置为导航到,让我们说:(" https://www.google.se/#q=" + textBox.Text);按下搜索按钮时。

我想让网络浏览器在不同的线程上运行,但我不确定如何。

有什么想法吗?

3 个答案:

答案 0 :(得分:1)

以下是我用于创建选项卡式浏览器的解决方案,其中每个浏览器都在单独的线程中运行。我必须警告你,这不是一个简单的解决方案,这种窗口黑客攻击会导致很多次要的界面和线程问题。 (所有可解决的,但需要一些时间来测试您的应用程序)

该解决方案由容器面板(BrowserPanel)和嵌入式Web浏览器面板(BrowserForm)组成。创建BrowserPanel将在单独的线程中启动Web浏览器。

这远不是一个完整的解决方案,但希望它能帮助你开始。

1)为user32.dll方法创建一个单独的类文件(您可能已经有了这些)

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)创建容器控件

创建一个新的类文件并将其命名为BrowserPanel。 此控件将存在于主UI线程中,并充当下面Web浏览器表单的占位符。

public class BrowserPanel : Panel
{
}

3)创建Web浏览器表单

创建一个新表单并将webbrowser控件放在其上。将表单命名为BrowserForm。 这个表单将有自己独立的线程。 表单必须具有指向BrowserPanel的链接。 (参见下面的源代码)

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

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

3)添加创建代码

将以下代码添加到BrowserPanel类。

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)向BrowserPanel类添加更多逻辑

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

我们还没完成。不!

从主UI线程调用浏览器控件方法可能会导致线程异常。对于许多WebBrowser方法,您必须在BrowserForm中创建一个包装器,如下所示。一些WebBrowser方法可以从另一个线程调用而没有问题。通过反复试验找出答案。

    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();
    }

调用主UI线程的WebBrowser事件也是如此。例如:

    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);
    }

你需要注意一些特别的事情:

  • 尽可能使用BeginInvoke而不是Invoke。如果使用阻塞Invoke调用WebBrowser控件将导致带有另一个阻塞调用的回调事件

  • ,则会发生死锁
  • 在另一个窗口中单击WebBrowser控件时,在主UI线程中创建的弹出菜单不会消失。 (通过捕获WebBrowser控件的onclick事件并将其路由回主窗体来解决此问题)

答案 1 :(得分:0)

我不认为让WebBrowser控件在不同的线程中运行是一种简单的方法,因为它需要在创建控件的线程上工作。我也不认为这是为每个标签创建新主题的简便方法。

我能找到最接近某人发布变通方法的地方是:

WebBrowser Control in a new thread

答案 2 :(得分:0)

WPF的WebBrowser以异步方式导航。如果您使用的是Windows窗体,则可以使用ElementHost类嵌入它。 This tutorial解释了如何在Windows窗体中托管WPF控件。