WTL 浏览器






4.68/5 (21投票s)
2004年5月24日
6分钟阅读

171552

5875
如何使用 IE 引擎和 WTL 创建一个简单的浏览器。
引言
本教程将帮助您使用 IE 引擎构建一个迷你浏览器。它基于 WTL,并使用我编写的一个包装器类来处理 IWebBrowser2
接口。因为我知道阅读代码可能很困难,所以本教程将帮助您一步一步地开发一个迷你浏览器。
背景
我的大多数项目都与互联网浏览有关。我经常使用 SDI 和 HTML 视图。有时,我需要使用真正的浏览器功能,因此我为 IWebBrowser2
编写了一个包装器。这个包装器类可以处理嵌入在 IE 中的窗口。它还可以非常容易地处理事件接收(例如:OnDocumentComplete
)。
创建新项目
我们首先创建一个新的 WTL 项目。假设您已安装 WTL 文件(如果没有,请在此处查看)。在第一个向导屏幕上,选择一个 SDI 应用程序并勾选生成 .CPP 文件。
在第二个屏幕上,将默认视图更改为 HTML 视图。
第一步是编辑 stdafx.h。请包含 atlmisc.h(我们会不时使用 CString
)和 atlctrlx.h(用于 CMultiPaneStatusBarCtrl
)。我们还需要注释掉 _ATL_DLL
定义(我们不希望可执行文件依赖 atl.dll),并将 IE 版本更改为 5。
// Change these values to use different versions #define WINVER 0x0400 //#define _WIN32_WINNT 0x0400 #define _WIN32_IE 0x0500 #define _RICHEDIT_VER 0x0100 // This is required for hosting browser in ATL7 //#define _ATL_DLL #include <atlbase.h> #include <atlapp.h> extern CAppModule _Module; #include <atlcom.h> #include <atlhost.h> #include <atlwin.h> #include <atlctl.h> #include <atlmisc.h> #include <atlframe.h> #include <atlctrls.h> #include <atldlgs.h> #include <atlctrlw.h> #include <atlctrlx.h> //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional // declarations immediately before the previous line.
更新视图
在我们的视图类中,我们需要包含 browser.h 并从此类继承视图类。我们还需要将其链接到消息映射,以便该类可以处理多个消息(WM_CREATE
、WM_DESTROY
)。
#include "browser.h" class CWTLBrowserView : public CWindowImpl<CWTLBrowserView, CAxWindow>, public CWebBrowser2<CWTLBrowserView> { public: DECLARE_WND_SUPERCLASS(NULL, CAxWindow::GetWndClassName()) BOOL PreTranslateMessage(MSG* pMsg); BEGIN_MSG_MAP(CWTLBrowserView) CHAIN_MSG_MAP(CWebBrowser2<CWTLBrowserView>) END_MSG_MAP() // Handler prototypes (uncomment arguments if needed): // LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, // LPARAM /*lParam*/, BOOL& /*bHandled*/) // LRESULT CommandHandler(WORD /*wNotifyCode*/, // WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) // LRESULT NotifyHandler(int /*idCtrl*/, // LPNMHDR /*pnmh*/, BOOL& /*bHandled*/) };
创建菜单
我们需要在菜单中添加一些新项目。典型的浏览器会处理后退、前进、主页、停止和刷新。我们将这些命令添加到菜单和工具栏。
由于某些项目有时不允许使用,我们需要处理它们的 UI(我们不能始终使用后退和前进)。首先,我们需要将它们添加到 UI 更新映射中(在 mainfrm.h 中)。
UPDATE_ELEMENT(ID_VIEW_GOTO_BACK, UPDUI_MENUPOPUP|UPDUI_TOOLBAR) UPDATE_ELEMENT(ID_VIEW_GOTO_FORWARD, UPDUI_MENUPOPUP|UPDUI_TOOLBAR)
我们通过 OnIdle
函数更新它们。
UIEnable(ID_VIEW_GOTO_BACK,m_view.CanBack()); UIEnable(ID_VIEW_GOTO_FORWARD,m_view.CanForward());
CWebBrowser2
提供了两个函数(CanBack
和 CanForward
),可以确定后退和前进操作的状态。
由于默认向导启动于 microsoft.com,我们需要将代码更改为 about:blank,然后启动到正常的家庭主页。我们需要在 CMainFrame::OnCreate
中更改代码。
m_hWndClient = m_view.Create(m_hWnd, rcDefault, _T("about:blank"), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL, WS_EX_CLIENTEDGE); . . . m_view.GoHome(); return 0;
创建地址栏
现在我们有了一个工作的框架,但仍然无法在那里输入任何 URL!为了解决这个问题,我们将创建一个地址栏,以便用户可以输入 URL 进行导航。我们将向我们的主框架类添加一个新的成员变量(CEdit
),名为 m_URL
。我们将在 CMainFrame::OnCreate
中创建并初始化它。由于我们也希望为地址栏提供自动完成功能,因此我们将对编辑控件使用 SHAutoComplete
函数。
CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE); AddSimpleReBarBand(hWndCmdBar); AddSimpleReBarBand(hWndToolBar, NULL, TRUE); CString szAddress; szAddress.LoadString(IDS_ADDRESS); m_URL.Create(m_hWnd,CRect(0,0,0,18),NULL,WS_CHILD|WS_VISIBLE,WS_EX_STATICEDGE); AddSimpleReBarBand(m_URL,szAddress.GetBuffer(0),TRUE); m_URL.SetFont(AtlGetDefaultGuiFont()); SHAutoComplete(m_URL,SHACF_URLALL); CreateSimpleStatusBar();
如果我们尝试编译项目,将在链接文件时收到链接错误。此错误发生是因为 SHAutoComplete
从 shlwapi.dll 导出。要解决此问题,我们需要将库(shlwapi.lib)添加到我们的项目中。
编译项目后,我们将看到我们的新栏正在等待我们输入。但是!如果我们尝试按 Enter 键,我们的浏览器就会一直处于休眠状态!让我们修复它!
导航
由于 HTML 框架会将所有按键转发给 HTML 文档,因此我们无法简单地等待 WM_CHAR
消息。我们需要在 PreTranslateMessage
函数中添加一些代码。我们需要从地址栏捕获 WM_CHAR
消息,并处理 VK_RETURN
字符。
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) { if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg)) return TRUE; if (pMsg->message==WM_CHAR && m_URL==pMsg->hwnd) { switch (pMsg->wParam) { case VK_RETURN: { CString szURL; int nLength=m_URL.GetWindowTextLength(); m_URL.GetWindowText(szURL.GetBuffer(nLength),nLength+1); szURL.ReleaseBuffer(); m_view.Navigate(szURL); return TRUE; } } return FALSE; } return m_view.PreTranslateMessage(pMsg); }
Commands
现在我们可以输入 URL 并观看我们的浏览器导航了。但仍然缺少一些东西。为什么我们不能使用后退、前进和我们新命令的其他功能?我们需要将它们的函数添加到我们的主框架类(因为那里没有什么特别的,只需检查源代码即可了解如何实现)。
UI 调整
让我们测试我们的浏览器。尝试访问 https://codeproject.org.cn/ 并点击 WTL 部分。为什么我们的地址栏中看不到新位置?要修复它,我们需要在我们的位置每次更改时更新地址栏。一个好的地方是在我们的视图类中处理 OnNavigateComplete2
。由于我们需要更新位于主框架类中的 m_URL
,因此我们将在视图类中创建一个对它的新引用,并在构造函数中传递变量。现在我们可以处理我们的消息并更新地址栏了。
void CWTLBrowserView::OnNavigateComplete2(IDispatch* pDisp, const String& szURL) { m_URL.SetWindowText(GetLocationURL()); }
另一个有用的调整包括进度通知、安全图标和状态栏文本。它们都位于状态栏中。现在是时候拿出我们的杀手锏了——CMultiPaneStatusBarCtrl
!我们将在主框架类中创建一个新变量,并在我们的视图类中创建一个引用。状态栏应包含三个部分:默认文本、安全图标和进度通知。由于默认文本具有唯一的 ID(ID_DEFAULT_PANE
),因此我们只需要创建另外两个标识符。从“视图 -> 资源符号”菜单,我们需要创建我们的新符号:IDR_LOCK
和 IDR_PROGRESS
。创建它们后,我们可以在 CMainFrame::OnCreate
函数中初始化我们的新状态栏。
CreateSimpleStatusBar(); m_StatusBar.SubclassWindow(m_hWndStatusBar); int nPanes[]={ID_DEFAULT_PANE,IDR_LOCK,IDR_PROGRESS}; m_StatusBar.SetPanes(nPanes,sizeof(nPanes)/sizeof(int),false); m_StatusBar.SetPaneWidth(IDR_LOCK,30); m_StatusBar.SetPaneWidth(IDR_PROGRESS,50);
我们还需要向项目中添加一个新图标(IDI_LOCK
)并将其加载到变量(m_hSecured
)中。为了处理正确的 UI 更新,我们将向 UI 更新映射添加新行。
UPDATE_ELEMENT(0, UPDUI_STATUSBAR)
在我们的视图类中,我们将添加一个新变量(m_bSecured
)以及一些代码来处理我们的状态栏更新。
void CWTLBrowserView::OnStatusTextChange(const String& szText) { m_StatusBar.SetPaneText(ID_DEFAULT_PANE,szText); } void CWTLBrowserView::OnProgressChange(long nProgress, long nProgressMax) { CString szText; if (nProgressMax>0) szText.Format(_T("%d%%"),nProgress*100/nProgressMax); m_StatusBar.SetPaneText(IDR_PROGRESS,szText); } void CWTLBrowserView::OnSetSecureLockIcon(long nSecureLockIcon) { m_bSecured=nSecureLockIcon>0; }
最后,我们需要在 CMainFrame::OnIdle
中添加一些代码。
m_StatusBar.SetPaneIcon(IDR_LOCK,m_view.IsSecured() ? m_hSecured : NULL);
UIUpdateToolBar();
UIUpdateStatusBar();
return FALSE;
文件命令
为了让浏览器能够保存和打印文件,我们需要处理文件消息。要向浏览器发送命令,我们需要使用 ExecWB
函数。要查询命令状态(用于 UI 映射),我们可以使用 QueryStatusWB
函数。我们需要添加适当的函数(用于保存/打印),更新 UI 更新映射,并在 OnIdle
函数中处理它们的 UI。由于这相当简单,因此我不会将其包含在文章中(有关更多详细信息,请参阅源代码)。
编辑命令
编辑命令是一个特殊情况。由于它们可以与浏览器以及我们的地址栏一起使用,因此每次使用这些命令时,我们都需要检查我们的焦点窗口。首先,我们将它们添加到 UI 更新映射中,然后在 OnIdle
函数中处理它们的更新。
if (GetFocus()==m_URL) { DWORD dwSelection=m_URL.GetSel(); BOOL bEnable=HIWORD(dwSelection)!=LOWORD(dwSelection); UIEnable(ID_EDIT_CUT,bEnable); UIEnable(ID_EDIT_COPY,bEnable); if (m_URL.OpenClipboard()) { UIEnable(ID_EDIT_PASTE,IsClipboardFormatAvailable(CF_TEXT)); CloseClipboard(); } else UIEnable(ID_EDIT_PASTE,FALSE); UIEnable(ID_EDIT_UNDO,m_URL.CanUndo()); } else { UIEnable(ID_EDIT_CUT,m_view.QueryStatusWB(OLECMDID_CUT) & OLECMDF_ENABLED); UIEnable(ID_EDIT_COPY,m_view.QueryStatusWB(OLECMDID_COPY) & OLECMDF_ENABLED); UIEnable(ID_EDIT_PASTE,m_view.QueryStatusWB(OLECMDID_PASTE) & OLECMDF_ENABLED); UIEnable(ID_EDIT_UNDO,m_view.QueryStatusWB(OLECMDID_UNDO) & OLECMDF_ENABLED); }
在执行实际命令时,我们也需要区分它们。
LRESULT CMainFrame::OnEditCut(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { if (GetFocus()==m_URL) m_URL.Cut(); else m_view.ExecWB(OLECMDID_CUT,OLECMDEXECOPT_DONTPROMPTUSER,NULL,NULL); return 0; } LRESULT CMainFrame::OnEditCopy(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { if (GetFocus()==m_URL) m_URL.Copy(); else m_view.ExecWB(OLECMDID_COPY,OLECMDEXECOPT_DONTPROMPTUSER,NULL,NULL); return 0; } LRESULT CMainFrame::OnEditPaste(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { if (GetFocus()==m_URL) m_URL.Paste(); else m_view.ExecWB(OLECMDID_PASTE,OLECMDEXECOPT_DONTPROMPTUSER,NULL,NULL); return 0; } LRESULT CMainFrame::OnEditUndo(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { if (GetFocus()==m_URL) m_URL.Undo(); else m_view.ExecWB(OLECMDID_UNDO,OLECMDEXECOPT_DONTPROMPTUSER,NULL,NULL); return 0; }
结束语
如果我写/语法有任何错误,请原谅。英语不是我的母语,所以我偶尔会犯错误。您可以使用文章中发布的任何部分代码。如果您在软件中使用它,请给我发电子邮件告知我(别担心——不会收取任何费用)。特别感谢 Michael Dunn、Ed Gadziemski 和 PJ Naughter。我从他们的文章中学到了很多东西。