在 WinForms 包裝 B/S 系統時,原生 WebBrowser 控制項缺乏分頁功能,若不處理 Window.Open 或 target="_blank",連結會彈出外部 IE 視窗,破壞應用程式整體性。本文實作一個可複用的多頁籤瀏覽器使用者控制項,核心需求:
target="_blank"、window.open、JavaScript 導航)一律在新分頁開啟。<title>,過長自動截斷。CreateNewTab(url) 公開方法供外部呼叫。原生 WebBrowser 未暴露 NewWindow2/3 事件,需繼承並覆寫 CreateSink/DetachSink 掛接 DWebBrowserEvents2 COM 介面:
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
public class ExtendedWebBrowser : WebBrowser
{
AxHost.ConnectionPointCookie _cookie;
WebBrowser2EventHelper _helper;
public event EventHandler<WebBrowserExtendedNavigatingEventArgs> BeforeNewWindow;
protected override void CreateSink()
{
base.CreateSink();
_helper = new WebBrowser2EventHelper(this);
_cookie = new AxHost.ConnectionPointCookie(
ActiveXInstance, _helper, typeof(DWebBrowserEvents2));
}
protected override void DetachSink()
{
if (_cookie != null) { _cookie.Disconnect(); _cookie = null; }
base.DetachSink();
}
// 內部類別轉發 COM 事件...
}
關鍵事件引數: WebBrowserExtendedNavigatingEventArgs 包含 Url、Cancel 屬性,攔截時設 Cancel = true 並自行導航新分頁。
設定 DrawMode = OwnerDrawFixed,在 DrawItem 繪製標題與關閉圖示:
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
var tabRect = tabControl1.GetTabRect(e.Index);
var g = e.Graphics;
// 1. 繪製標題文字
g.DrawString(tabControl1.TabPages[e.Index].Text, Font, SystemBrushes.ControlText, tabRect.X + 3, tabRect.Y + 3);
// 2. 僅多於 1 個分頁時繪製關閉按鈕
if (tabControl1.TabCount > 1)
{
const int CloseSize = 14;
var closeRect = new Rectangle(tabRect.Right - CloseSize - 4, tabRect.Y + 2, CloseSize, CloseSize);
var img = (e.State & DrawItemState.Selected) == DrawItemState.Selected
? imageList1.Images["closeSelected"] : imageList1.Images["close"];
g.DrawImage(img, closeRect);
// 將 closeRect 存入 Tag 供 MouseDown 判斷
tabControl1.TabPages[e.Index].Tag = closeRect;
}
}
CreateNewTabPage(string url)private readonly List<ExtendedWebBrowser> _browsers = new List<ExtendedWebBrowser>();
public void CreateNewTabPage(string url)
{
var web = new ExtendedWebBrowser
{
Name = $"WebBrowser{_browsers.Count}",
Dock = DockStyle.Fill,
Margin = Padding.Empty
};
web.DocumentCompleted += Web_DocumentCompleted;
web.BeforeNewWindow += Web_BeforeNewWindow;
web.Navigate(url);
_browsers.Add(web);
var tabPage = new TabPage("載入中...")
{
Name = $"TabPage{tabControl1.TabCount}",
Padding = new Padding(0, 3, 0, 0),
ImageIndex = 0
};
tabPage.Controls.Add(web);
tabControl1.TabPages.Add(tabPage);
tabControl1.SelectedTab = tabPage;
}
private void Web_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var web = (ExtendedWebBrowser)sender;
string title = web.Document.Title.Trim();
if (title.Length > 8) title = title.Substring(0, 8) + "...";
var tabPage = (TabPage)web.Parent;
tabPage.Text = title;
if (tabControl1.SelectedTab == tabPage)
this.Text = web.Document.Title; // 同步表單標題
}
private void Web_BeforeNewWindow(object sender, WebBrowserExtendedNavigatingEventArgs e)
{
CreateNewTabPage(e.Url);
e.Cancel = true; // 取消預設彈出視窗行為
}
private void tabControl1_MouseDown(object sender, MouseEventArgs e)
{
for (int i = 0; i < tabControl1.TabCount; i++)
{
if (tabControl1.TabPages[i].Tag is Rectangle closeRect && closeRect.Contains(e.Location))
{
var web = _browsers[i];
web.DocumentCompleted -= Web_DocumentCompleted;
web.BeforeNewWindow -= Web_BeforeNewWindow;
web.Dispose();
_browsers.RemoveAt(i);
tabControl1.TabPages.RemoveAt(i);
break;
}
}
}
Navigating 事件,將當前分頁 URL 顯示在工具列文字方塊。FileDownload 事件,整合自訂下載對話框。Microsoft.Web.WebView2,支援現代網標、Chromium 內核、更佳效能與安全性。透過 ExtendedWebBrowser 攔截 COM 事件 與 TabControl OwnerDrawFixed 自繪 的結合,我們實現了功能完整、體驗流暢的多頁籤瀏覽器控制項。此架構清晰分離「瀏覽器核心」與「分頁管理 UI」,易於維護與擴充,可直接嵌入任何 WinForms 專案作為內嵌瀏覽器外殼。