從 Delphi 3 開始引入 TWebBrowser 元件,早期以 ActiveX 控制項形式出現,需手動匯入;到了 Delphi 4.0 和 5.0,它已封裝完 shdocvw.dll,直接出現在「Internet」元件面板中,使用更為便捷。然而,許多開發者吐槽 Delphi 的幫助文件不夠完善,加上 TWebBrowser 本質上是 Microsoft 的技術,MSDN 雖資料龐大卻缺乏明確入口,查找起來往往讓人感到「非常複雜、複雜非常」。本文整理了實戰經驗與網路收集的範例,旨在為有意深入 TWebBrowser 開發的朋友提供實用參考。
在呼叫 ExecWB 等方法時,常遇到「嘗試啟動未註冊的丟失目標」或「OLE 物件未註冊」錯誤,或是操作無效果(如無法複製網頁內容到剪貼簿)。這通常是因為 TWebBrowser 是嵌入式 OLE 物件,而非純 VCL 控制項,必須正確初始化與終止化 OLE 環境。
解決方法:在專案檔(.dpr)的 initialization 與 finalization 區塊加入以下程式碼:
initialization
OleInitialize(nil);
finalization
try
OleUninitialize;
except
end;
放置於主視窗所有程式碼之後、end. 之前。這能有效避免 OLE 相關錯誤,確保元件正常運作。
Delphi 5 中 Navigate 方法多重載,後幾個參數皆為 var OleVariant,傳統寫法需宣告暫存變數:
var t: OleVariant;
begin
WebBrowser1.Navigate(Edit1.Text, t, t, t, t);
end;
實際上可直接使用系統內建的 EmptyParam(一個公用的 Variant 空變數,勿賦值):
WebBrowser1.Navigate(Edit1.Text, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
雖程式碼稍長,但免去每次宣告變數的麻煩。若僅需基本導航,直接呼叫 WebBrowser1.Navigate(Edit1.Text) 即可。
ExecWB 同樣多重載,透過 OLECMDID 執行各種瀏覽器命令:
ExecWB(OLECMDID_OPEN, OLECMDEXECOPT_DODEFAULT, EmptyParam, EmptyParam);(IE5.0 不支援)ExecWB(OLECMDID_SAVEAS, OLECMDEXECOPT_DODEFAULT, EmptyParam, EmptyParam);ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DODEFAULT, EmptyParam, EmptyParam);
if QueryStatusWB(OLECMDID_PRINTPREVIEW) = 3 then
ExecWB(OLECMDID_PRINTPREVIEW, OLECMDEXECOPT_DODEFAULT, EmptyParam, EmptyParam);
ExecWB(OLECMDID_PAGESETUP, OLECMDEXECOPT_DODEFAULT, EmptyParam, EmptyParam);
// 方式 A:使用 TWebBrowser 的 QueryStatusWB
if (QueryStatusWB(OLECMDID_COPY) = OLECMDF_ENABLED) or (OLECMDF_SUPPORTED) then
ExecWB(OLECMDID_COPY, OLECMDEXECOPT_DODEFAULT, EmptyParam, EmptyParam);
// 方式 B:使用 IHTMLDocument2 的 QueryCommandEnabled
var Doc: IHTMLDocument2;
begin
Doc := WebBrowser1.Document as IHTMLDocument2;
if Doc.QueryCommandEnabled('Copy') then
Doc.ExecCommand('Copy', False, EmptyParam);
end;
對應 IE「檢視」→「文字大小」的五個等級(0~4 對應最小~最大):
讀取目前縮放等級:
var t: OleVariant;
begin
WebBrowser1.ExecWB(OLECMDID_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, EmptyParam, t);
case t of
4: Largest.Checked := True;
3: Larger.Checked := True;
2: Middle.Checked := True;
1: Small.Checked := True;
0: Smallest.Checked := True;
end;
end;
設定縮放等級:
Largest.Checked := False; Larger.Checked := False; Middle.Checked := False;
Small.Checked := False; Smallest.Checked := False;
TMenuItem(Sender).Checked := True;
t := TMenuItem(Sender).Tag; // 0~4
WebBrowser1.ExecWB(OLECMDID_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, t, t);
使用 IShellUIHelper 介面新增收藏夾簡單但非模態視窗;若需模態效果,可直接呼叫 shdocvw.dll 的 DoAddToFavDlg。以下提供完整實作:
procedure TForm1.AddFavorite(IE: TEmbeddedWB);
procedure CreateUrl(AUrlPath, AUrl: PChar);
var URLfile: TIniFile;
begin
URLfile := TIniFile.Create(String(AUrlPath));
URLfile.WriteString('InternetShortcut', 'URL', String(AUrl));
URLfile.Free;
end;
var
AddFav: function(Handle: THandle; UrlPath: PChar; UrlPathSize: Cardinal;
Title: PChar; TitleSize: Cardinal; FavIDLIST: PItemIDList): Bool; stdcall;
FDoc: IHTMLDocument2;
UrlPath, url, title: array[0..MAX_PATH] of Char;
H: HWnd; pidl: PItemIDList; FRetOK: Bool;
begin
FDoc := IHTMLDocument2(IE.Document);
if FDoc = nil then Exit;
StrPCopy(Title, FDoc.Get_title);
StrPCopy(url, FDoc.Get_url);
if url <> '' then begin
H := LoadLibrary('shdocvw.dll');
if H <> 0 then begin
SHGetSpecialFolderLocation(0, CSIDL_FAVORITES, pidl);
AddFav := GetProcAddress(H, 'DoAddToFavDlg');
if Assigned(AddFav) then
FRetOK := AddFav(Handle, UrlPath, SizeOf(UrlPath), Title, SizeOf(Title), pidl);
end;
FreeLibrary(H);
if FRetOK then CreateUrl(UrlPath, url);
end;
end;
整理收藏夾則直接使用 DoOrganizeFavDlg,可指定父視窗句柄實現模態。
TWebBrowser 繼承自 TWinControl 的 SetFocus 無法使內含文件取得焦點,導致快捷鍵失效。解決方法:
procedure TForm1.SetFocusToDoc;
begin
if WebBrowser1.Document <> nil then
with WebBrowser1.Application as IOleObject do
DoVerb(OLEIVERB_UIACTIVATE, nil, WebBrowser1, 0, Handle, GetClientRect);
end;
更簡單的方式:
if WebBrowser1.Document <> nil then
IHTMLWindow2(IHTMLDocument2(WebBrowser1.Document).ParentWindow).focus;
// 或最簡單:
if WebBrowser1.Document <> nil then
IHTMLWindow4(WebBrowser1.Document).focus;
判斷文件是否擁有焦點:if IHTMLWindow4(WebBrowser1.Document).hasfocus then ...
TWebBrowser 不響應 Enter 鍵觸發 Submit 按鈕,且表單 KeyPreview 也捕捉不到按鍵。解決方案:
Info\Extras\SendKeys\SndKey32.pas 提供 SendKeys 函數,在文件取得焦點時呼叫 SendKeys('~', True); 即可模擬 Enter。EmbeddedWB.pas 實作,攔截 WM_KEYDOWN 等訊息並透過 IOleInPlaceActiveObject.TranslateAccelerator 轉發給瀏覽器。無需複雜的 IPersistStreamInit 流操作,直接使用 DOM 介面:
// 取得完整 HTML 原始碼(含標籤)
Memo1.Lines.Add(IHTMLDocument2(WebBrowser1.Document).Body.OuterHtml);
// 取得純文字內容(已解析標籤)
Memo1.Lines.Add(IHTMLDocument2(WebBrowser1.Document).Body.OuterText);
程式化調用查找對話框:IOleCommandTarget.Exec(OLECMDID_FIND)。若要自行設計查找介面,可操作 IHTMLTxtRange:
var Doc: IHTMLDocument2; TxtRange: IHTMLTxtRange;
begin
Doc := WebBrowser1.Document as IHTMLDocument2;
Doc.SelectAll; // 先建立選取範圍
TxtRange := Doc.Selection.CreateRange as IHTMLTxtRange;
TxtRange.FindText('搜尋文字', 0, 0);
TxtRange.Select;
end;
可透過 TxtRange.Get_text 取得目前選取文字。
var doc: IHTMLDocument2; all: IHTMLElementCollection; len, i: Integer; item: OleVariant;
begin
doc := WebBrowser1.Document as IHTMLDocument2;
all := doc.Get_links; // 或 doc.Links
len := all.Length;
for i := 0 to len - 1 do begin
item := all.Item(i, EmptyParam);
Memo1.Lines.Add(item.href);
end;
end;
極簡實作:
procedure SetCharSet(AWebBrowser: TWebBrowser; ACharSet: String);
var RefreshLevel: OleVariant;
begin
IHTMLDocument2(AWebBrowser.Document).Set_CharSet(ACharSet);
RefreshLevel := 7; // 註冊表值,說明文件有誤
AWebBrowser.Refresh2(RefreshLevel);
end;