Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WTL Browser

0.00/5 (No votes)
23 May 2004 2  
How to create a simple browser using IE engine and WTL.

Sample Image - wtlbrowser.jpg

Introduction

This tutorial will help you build a mini browser using IE engine. It is based on WTL and uses a wrapper class which I've wrote for handling IWebBrowser2 interface. Since I know it can be hard to read code, this tutorial will help you develop a mini browser - step by step.

Background

Most of my projects are related to internet browsing. I'm using SDI with HTML view a lot of time. Sometimes, I need to use real browser capabilities, so I've written a wrapper for IWebBrowser2. This wrapper class can handle a window which is embedded in IE. It can also handle events sinking in a very easy way (for example: OnDocumentComplete).

Creating a new project

We first start by creating a new WTL project. I assume you have WTL files installed (if not, check here). On the first wizard screen, select an SDI application and check for generating .CPP files.

On the second screen, change the default view to an HTML view.

First step will be to edit stdafx.h. Please include atlmisc.h (we'll use CString from time to time) and atlctrlx.h (for CMultiPaneStatusBarCtrl). We also need to comment out the _ATL_DLL definition (we don't want our executable to depend on atl.dll) and to change IE version to version 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.

Updating the view

In out view class, we need to include browser.h and inherit the view class from it. We also need to chain it to the message map so the class can handle several messages (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*/)

};

Creating the menu

We need to add some new items to the menu. A typical browser handles back, forward, home, stop and refresh. We'll add those commands to the menu and the toolbar.

Since some of the items aren't allowed from time to time, we need to handle their UI (we can't always use back and forward). First, we need to add them to the UI update map (in mainfrm.h).

UPDATE_ELEMENT(ID_VIEW_GOTO_BACK, UPDUI_MENUPOPUP|UPDUI_TOOLBAR)
UPDATE_ELEMENT(ID_VIEW_GOTO_FORWARD, UPDUI_MENUPOPUP|UPDUI_TOOLBAR)

We update them via the OnIdle function.

UIEnable(ID_VIEW_GOTO_BACK,m_view.CanBack());
UIEnable(ID_VIEW_GOTO_FORWARD,m_view.CanForward());

CWebBrowser2 exposes 2 functions (CanBack & CanForward) which can determine the status of the back and forward actions.

Since the default wizard starts on microsoft.com, we need to change the code to about:blank, and start on the normal home page. We need to change the code in 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;

Creating the address bar

We now have a working framework, but we still can't enter any URL there! To solve the problem, we'll create an address bar, so the user can enter a URL for navigating. We'll add a new member variable (CEdit) to our mainframe class - called m_URL. We'll create and initialize it from CMainFrame::OnCreate. Since we also want auto completion for our address bar, we'll use SHAutoComplete function on the edit control.

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();

If we try to compile the project, we'll get an error while linking the files. This error occurs since SHAutoComplete is exported from shlwapi.dll. To solve it, we need to add the library (shlwapi.lib) to our project.

After compiling the project, we'll see our new bar waiting for us to type in. But hey! If we try to press the Enter key, our browser would just keep sleeping! Let's fix it!

Navigating

Since the HTML framework forwards all keystrokes to the HTML document, we can't just wait for the WM_CHAR message. We need to add some code to the PreTranslateMessage function. We need to grab the WM_CHAR message from the address bar and handle the VK_RETURN character.

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

We can now enter a URL and watch our browser navigate it. But something is still missing. Why can't we use back, forward and the rest of our new commands? We need to add their functions to our mainframe class (since there is nothing special there, simply check the source code to see how it's done).

UI Tweaks

Let's test our browser. Try to go to http://www.codeproject.com/ and click on the WTL section. Why can't we see the new location in our address bar? To fix it, we need to update the address bar each time our location changes. A good place is to handle the OnNavigateComplete2 from our view class. Since we need to update m_URL which resides inside the mainframe class, we'll create a new reference to it inside our view class and pass the variable on the constructor. We now can handle our message and update the address bar.

void CWTLBrowserView::OnNavigateComplete2(IDispatch* pDisp, const String& szURL)
{
    m_URL.SetWindowText(GetLocationURL());
}

Another useful tweak includes a progress notification, secured icon, and status bar text. They all reside on the status bar. It's time to bring our big guns - CMultiPaneStatusBarCtrl! We'll create a new variable inside the mainframe class, plus a reference inside our view class. The status bar should contain 3 parts: the default text, a secured icon, and a progress notification. Since the default text has a unique id (ID_DEFAULT_PANE), all we need to do is to create additional two identifiers. From the "View->Resource Symbols" menu, we need to create our new symbols: IDR_LOCK and IDR_PROGRESS. After creating them, we can initialize our new status bar from the CMainFrame::OnCreate function.

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);

We also need to add a new icon to our project (IDI_LOCK) and load it into a variable (m_hSecured). To handle proper UI updating, we'll add a new line to the UI update map:

UPDATE_ELEMENT(0, UPDUI_STATUSBAR)

In our view class, we'll add a new variable (m_bSecured) and some code to handle our status bar updates:

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;
}

Finally, we need to add some code to CMainFrame::OnIdle:

m_StatusBar.SetPaneIcon(IDR_LOCK,m_view.IsSecured() ? m_hSecured : NULL);
UIUpdateToolBar();
UIUpdateStatusBar();
return FALSE;

File Command

To allow the browser to save and print files, we need to handle file messages. To send a command to the browser, we need to use the ExecWB function. To query a command status (for the UI map), we can use the QueryStatusWB function. We need to add the proper functions (for saving/printing), update the UI update map and handle their UI from the OnIdle function. Since it is fairly simple, I won't include it in the article (check the source code for more details).

Edit Command

Edit commands are a special case. Since they can be used with the browser, as well as with our address bar, we need to check our focus window each time we use those commands. First, we add them to the UI update map, then we handle their update via the OnIdle function:

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);
}

We also need to distinguish them while executing the actual commands:

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;
}

Final Words

Please excuse me for any typing/grammar mistakes I might have made. English is not my natural language, so I make mistakes from time to time. You can use any part of the code posted with the article. If you use it within your software, please email me and let me know (don't worry - no money will be asked for it). Special thanks to Michael Dunn, Ed Gadziemski and PJ Naughter. I've learned a lot from their articles.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here