我目前正在研究一個應用程序,它有一個用於搜索字符串的文本框。標籤控件中的多個Web瀏覽器
我有一個tabControl有四個tabPages。在每個標籤中都有一個WebBrowser。這些標籤永遠不會被修改,他們只是想要在那裏,永遠留下來。
WebBrowsers設置爲導航到,讓我們說:(「https://www.google.se/#q=」+ textBox.Text);當按下搜索按鈕時。
我想使Web瀏覽器運行在不同的線程上,但我不知道如何。
有什麼想法?
我目前正在研究一個應用程序,它有一個用於搜索字符串的文本框。標籤控件中的多個Web瀏覽器
我有一個tabControl有四個tabPages。在每個標籤中都有一個WebBrowser。這些標籤永遠不會被修改,他們只是想要在那裏,永遠留下來。
WebBrowsers設置爲導航到,讓我們說:(「https://www.google.se/#q=」+ textBox.Text);當按下搜索按鈕時。
我想使Web瀏覽器運行在不同的線程上,但我不知道如何。
有什麼想法?
我不認爲有一個簡單的方法讓WebBrowser控件在單獨的線程中運行,因爲它需要在創建控件的線程上工作。我也不認爲有一個簡單的方法來爲每個標籤創建一個新的線程。
我能找到的地方有人張貼解決方法最接近的是:
WPF的WebBrowser
異步導航。如果您使用Windows Forms,則可以使用ElementHost
類將其嵌入。 This tutorial解釋瞭如何在Windows窗體中承載WPF控件。
下面是我用於創建標籤瀏覽器的解決方案,其中每個瀏覽器都在單獨的線程中運行。我必須警告你,這不是一個簡單的解決方案,這種windows黑客會導致很多小的界面和線程問題。 (所有解決方法,但需要一些時間來測試您的應用程序)
該解決方案由容器面板(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,而不是調用的。如果使用阻止調用對WebBrowser控件的調用將導致帶有另一個阻止調用的回調事件,則會發生死鎖。在另一個窗口中單擊WebBrowser控件時,在主UI線程中創建的彈出菜單不會消失。 (通過捕捉WebBrowser控件和路線,這回主要形式的onclick事件解決此問題)
到底爲什麼你需要一個單獨的線程運行web瀏覽器的每個實例? – Noseratio
Sjaak的解決方案有效,但我遇到了這種方法的問題。似乎有一些網站無法使用webBrowser從不同於主UI線程的線程正確加載,例如某些Flash應用程序。我正在考慮爲每個選項卡使用完整的新流程。我也想知道,如果這就是Google Chrome使用進程而不是線程的原因。 – xamid