現代瀏覽器(Chrome、Firefox、Edge)皆採用分頁式介面。本文示範如何在 WinForms 中使用 WebBrowser 控件(基於 MSHTML/IE 引擎)實現多分頁瀏覽、新視窗攔截、列印預覽、原始碼檢視、尋找對話框、網際網路選項等功能,並解析關鍵事件 NewWindow2 的運作機制。
using System;
using System.Windows.Forms;
using AxSHDocVw; // 需參考 Microsoft Web Browser (COM)
public partial class MainForm : Form
{
private int _tabIndex = 0;
public MainForm()
{
InitializeComponent();
// 初始建立第一個分頁
CreateNewTab("about:blank");
}
private AxWebBrowser CreateNewWebBrowser()
{
var wb = new AxWebBrowser();
wb.Tag = new BrowserTag { TabIndex = _tabIndex++ };
wb.Dock = DockStyle.Fill;
// 訂閱核心事件
wb.BeforeNavigate2 += Wb_BeforeNavigate2;
wb.DocumentComplete += Wb_DocumentComplete;
wb.NavigateComplete2 += Wb_NavigateComplete2;
wb.NavigateError += Wb_NavigateError;
wb.NewWindow2 += Wb_NewWindow2; // 關鍵:攔截新視窗
wb.ProgressChange += Wb_ProgressChange;
wb.StatusTextChange += Wb_StatusTextChange;
wb.TitleChange += Wb_TitleChange;
wb.CommandStateChange += Wb_CommandStateChange;
return wb;
}
private void CreateNewTab(string url)
{
var wb = CreateNewWebBrowser();
var tab = new TabPage($"分頁 {wb.Tag as BrowserTag}?.TabIndex}");
tab.Controls.Add(wb);
tabControl.TabPages.Add(tab);
tabControl.SelectedTab = tab;
wb.Navigate(url);
}
// 攔截 target="_blank"、window.open() 等新視窗請求
private void Wb_NewWindow2(object sender, AxSHDocVw.DWebBrowserEvents2_NewWindow2Event e)
{
var newWb = CreateNewWebBrowser();
e.ppDisp = newWb.Application; // 將新視窗導向我們的控件
newWb.RegisterAsBrowser = true; // 參與視窗名稱衝突檢測
}
// 標籤類別:保存分頁專屬狀態
private class BrowserTag
{
public int TabIndex { get; set; }
public bool CanGoBack { get; set; }
public bool CanGoForward { get; set; }
}
// 其他事件處理器略...
}
當網頁嘗試開啟新視窗(<a target="_blank">、window.open())時觸發。
e.ppDisp 指定接收新視窗的 IDispatch 介面,將其設為新建立的 AxWebBrowser.Application 即可在同一應用程式內開啟分頁。
RegisterAsBrowser = true 讓該控件參與「已命名視窗」衝突檢測,避免重複開啟同名視窗。
WebBrowser 底層的 MSHTML 文件物件實作 IOleCommandTarget,可執行「檢視原始碼」、「尋找」、「網際網路選項」等編輯器命令。
using System;
using System.Runtime.InteropServices;
using mshtml; // 參考 Microsoft HTML Object Library
// 介面定義
[ComImport, Guid("B722BCCB-4E68-101B-A2BC-00AA00404770"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IOleCommandTarget
{
void QueryStatus(ref Guid pguidCmdGroup, uint cCmds,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] OLECMD[] prgCmds,
ref OLECMDTEXT pCmdText);
void Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdExecOpt,
ref object pvaIn, ref object pvaOut);
}
[StructLayout(LayoutKind.Sequential)]
struct OLECMD { public uint cmdID; public uint cmdf; }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct OLECMDTEXT
{
public uint cmdtextf; public uint cwActual; public uint cwBuf;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public char rgwz;
}
// 命令群組 GUID (CGID_IWebBrowser)
private static readonly Guid CMD_GUID = new Guid("ED016940-BD5B-11CF-BA4E-00C04FD70816");
private enum BrowserCommand : uint
{
Find = 1,
ViewSource = 2,
Options = 3
}
// 取得目前分頁的 HTMLDocument
private HTMLDocument GetDocument()
{
var wb = tabControl.SelectedTab?.Controls[0] as AxWebBrowser;
return wb?.Document as HTMLDocument;
}
// 執行命令
private void ExecCommand(BrowserCommand cmd)
{
var doc = GetDocument();
if (doc == null) return;
var target = (IOleCommandTarget)doc;
object inObj = null, outObj = null;
target.Exec(ref CMD_GUID, (uint)cmd, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT,
ref inObj, ref outObj);
}
// 功能按鈕
private void btnViewSource_Click(object sender, EventArgs e) => ExecCommand(BrowserCommand.ViewSource);
private void btnFind_Click(object sender, EventArgs e) => ExecCommand(BrowserCommand.Find);
private void btnOptions_Click(object sender, EventArgs e)
{
try { ExecCommand(BrowserCommand.Options); }
catch { /* 選項對話框關閉時會拋 COM 例外,可忽略 */ }
}
private void Print()
{
var wb = GetCurrentWebBrowser();
wb?.Print(); // 直接列印
}
private void PrintPreview()
{
var wb = GetCurrentWebBrowser();
// WebBrowser 無內建預覽,需自行實作或呼叫 ExecWB
object missing = Type.Missing;
wb?.ExecWB(OLECMDID.OLECMDID_PRINTPREVIEW, OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT,
ref missing, ref missing);
}
private void ShowPageProperties()
{
var wb = GetCurrentWebBrowser();
object missing = Type.Missing;
wb?.ExecWB(OLECMDID.OLECMDID_PROPERTIES, OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT,
ref missing, ref missing);
}
| 功能 | 實作方式 |
|---|---|
| 多分頁瀏覽 | TabControl + 動態建立 AxWebBrowser |
| 新視窗攔截 | NewWindow2 事件 + ppDisp 重導向 |
| 前進/後退/重新整理/停止 | GoBack/GoForward/Refresh/Stop 方法 |
| 網址列同步 | NavigateComplete2 事件更新 TextBox |
| 進度列 | ProgressChange 事件更新 ProgressBar |
| 狀態列文字 | StatusTextChange 事件 |
| 標題更新分頁文字 | TitleChange 事件 |
| 列印/列印預覽 | Print() / ExecWB(PRINTPREVIEW) |
| 頁面屬性 | ExecWB(PROPERTIES) |
| 尋找對話框 | IOleCommandTarget.Exec(FIND) |
| 檢視原始碼 | IOleCommandTarget.Exec(VIEWSOURCE) |
| 網際網路選項 | IOleCommandTarget.Exec(OPTIONS) |
WebBrowser 封裝 IE/MSHTML (Trident),不支援現代 HTML5/CSS3/ES6。FEATURE_BROWSER_EMULATION 強制指定文件模式 (IE11 Edge 模式)。
// 1. NuGet 安裝 Microsoft.Web.WebView2
// 2. 設計介面拖放 WebView2 控件
// 3. 初始化
await webView2.EnsureCoreWebView2Async();
// 4. 導航
webView2.CoreWebView2.Navigate("https://example.com");
// 5. 執行腳本
await webView2.CoreWebView2.ExecuteScriptAsync("document.title");
// 6. 攔截新視窗
webView2.CoreWebView2.NewWindowRequested += (s, e) =>
{
e.NewWindow = newWebView2.CoreWebView2; // 導向新分頁
};
雖然 WebBrowser 屬於上一代技術,但理解其事件模型(NewWindow2)、COM 命令介面(IOleCommandTarget)、分頁狀態管理等核心概念,對於維護舊專案或快速原型開發仍有價值。新專案強烈建議直接採用 WebView2,獲得現代 Web 標準支援與長期維護保障。