Internet Explorer Toolbar (Deskband) Tutorial

来源:互联网 发布:淘宝宝贝发布视频教程 编辑:程序博客网 时间:2024/05/21 07:54

转自:http://www.codeproject.com/KB/shell/ietoolbartutorial.aspx

  • Download Toolbar Binary - 20Kb
  • Download Source - 21 Kb

The Motley Fool Quote IE Toolbar

Introduction

Having recieved a number of requests for a tutorial of sorts ondeveloping Internet Explorer Toolbars with the RBDeskband andCWindowImpl wizards that I created, I have come up with a simple sampletoolbar which can be used as a reference when developing your owntoolbars or explorer bars. The tutorial will walk you through thestages of developing a toolbar for IE that is very similar to theAddress bar that is already present in IE. I wanted to do a tutorialthat would provide a realistic sample and would produce an end resultthat could be used by others after the tutorial was finish. So, thetutorial is going to show you how to develop an IE toolbar to get stockquote information from The Motley Fool website. So with that, let usget started.

Prequisites

This tutorial assumes that you already know how to program in C++and know some information about ATL and COM. To work through thistutorial, you will need the following installed on your developmentmachine:

  • Visual C++6 installed
  • RBDeskBand ATL Object Wizard (Version 2.0) [get it here]
  • CWindowImpl ATL Object Wizard [get it here]

The Framework

The IE toolbar consists of a COM component supporting IDeskband anda few other necessary interfaces for which IE looks for when loadingregistered toolbars, explorer bars and deskbands. The RBDeskband ATLObject Wizard provides most of the framework for this article. What wewill need to do is create our project, a new COM object to house ourtoolbar, and a few CWindowImpl classes using the CWindowImpl ATL ObjectWizard. Then connecting these parts together we will produce the IEtoolbar in the picture at the top of the article. Visually the toolbarconsists of an editbox and a toolbar with one button on it. Inactuality the toolbar consists of the fore mentioned and a non visiblewindow that is used to reflect messages to the Toolbar window, whichwill process or forward messges to itself and the edit box.

Creating The Shell

We will not work through the steps in creating the shell for our toolbar.

Creating The Project

  • If you have not done so already, start Visual C++6.
  • Then, from the File menu select New menu item; the New Dialog pops up.
  • In the New Dialog, select the Projects tab, if not already selected.
  • Select ATL COM AppWizard from the list view, if not already selected.
  • In the Project name, type "MotleyFool". See Figure 1.
  • Click the OK Button.
Figure 1. New Dialog.
Figure 1. New Dialog.
  • The ATL COM AppWizard will kick in.
  • Clicking the Finish Button, accepting the default AppWizard attributes. See Figure 2.
  • The New Project Information Dialog will present itself requesting confirmation of your project settings.
  • Click the OK Button.
Figure 2. ATL COM AppWizard
Figure 2. ATL COM AppWizard.

Creating The DeskBand Object

Now that we have our project container we need to add our IDeskBandderived component so that the DLL actually exposes something.

  • From the Insert menu, select New ATL Object menu item; the ATL Object Wizard dialog is invoked.
  • In the ATL Object Wizard dialog, select the RadBytes Category. Ifthis category is missing then make sure that the RBDeskband andCWindowImpl ATL Object Wizards are installed.
  • Next select the DeskBand item from the Objects list.
  • Click the Next button to invoke the ATL Object Wizard Properties dialog for the Deskband object. See Figure 3.
  • On the Names property page, type "StockBar" into the Short Name field. See Figure 4.
  • Select the DeskBand ATL Object Wizard property page
  • Check the Internet Explorer Toolbar checkbox. See Figure 5.
  • Click the OK button on the ATL Object Wizard Properties Dialog. TheATL Object Wizard will create the files necessary for our DeskBand'sbase implementation.
Figure 3. ATL Object Wizard.
Figure 3. ATL Object Wizard. Figure 4. ATL Object Wizard Properties - Names.
Figure 4. ATL Object Wizard Properties - Names. Figure 5. ATL Object Wizard Properties - DeskBand ATL Object Wizard.
Figure 5. ATL Object Wizard Properties - DeskBand ATL Object Wizard

Nowour project has the DeskBand implementation that we will modify toproduce the toolbar pictured at the top of the article. First we willcreate the window classes we will need and then come back to theDesbkand object and update it to use our window classes.

Creating The Window Classes

So back in the Framework section we said that we would need threewindow classes. One for the Edit Box, one for the toolbar, and one formessage reflection back to the toolbar. Let us now create these windowclasses.

The Edit Window

We need to create a derived class from the standard EDIT buttonwindow class because we are going to be adding methods to our class tohelp support functionality of the toolbar. This is one reason why wecannot use a CContainedWindow object directly.

  • From the Insert menu, select New ATL Object menu item; the ATL Object Wizard dialog is invoked.
  • In the ATL Object Wizard dialog, select the RadBytes Category. Ifthis category is missing then make sure that the RBDeskband andCWindowImpl ATL Object Wizards are installed.
  • Next select the CWindowImpl item from the Objects list.
  • Click the Next button to invoke the ATL Object Wizard Properties dialog for the Deskband object. See Figure 3.
  • On the Names property page, type "EditQuote" into the Short Name field.
  • Select the CWindowImpl property page. See Figure 6.
  • Select the SUPERCLASS radio button from the DECLAR_WND_* group.
  • In the Window Class Name field, type "EDITQUOTE".
  • In the Original Class Name list, select the EDIT listbox item. See Figure 7.
  • Click the OK button on the ATL Object Wizard Properties Dialog. TheATL Object Wizard will create the files necessary for our CWindowImplderived class implementation.
Figure 6. ATL Object Wizard Properties - Names.
Figure 6. ATL Object Wizard Properties - Names. Figure 7. ATL Object Wizard Properties - Names.
Figure 7. ATL Object Wizard Properties - CWindowImpl.

The Toolbar Window

We need to create a derived class from the standard TOOLBARCLASSNAMEwindow class because we are going to be adding methods to our class tohelp support functionality of the toolbar. It will also be the parentfor the edit box and the window which the IE host will request from ourDeskBand.

  • From the Insert menu, select New ATL Object menu item; the ATL Object Wizard dialog is invoked.
  • In the ATL Object Wizard dialog, select the RadBytes Category. Ifthis category is missing then make sure that the RBDeskband andCWindowImpl ATL Object Wizards are installed.
  • Next select the CWindowImpl item from the Objects list.
  • Click the Next button to invoke the ATL Object Wizard Properties dialog for the Deskband object. See Figure 3.
  • On the Names property page, type "MFToolbar" into the Short Name field.
  • Select the CWindowImpl property page. See Figure 8.
  • Select the SUPERCLASS radio button from the DECLAR_WND_* group.
  • In the Window Class Name field, type "MOTLEYFOOLTOOLBAR".
  • In the Original Class Name list, select the TOOLBARCLASSNAME listbox item. See Figure 9.
  • Click the OK button on the ATL Object Wizard Properties Dialog. TheATL Object Wizard will create the files necessary for our CWindowImplderived class implementation.
Figure 8. ATL Object Wizard Properties - Names.
Figure 8. ATL Object Wizard Properties - Names. Figure 9. ATL Object Wizard Properties - Names.
Figure 9. ATL Object Wizard Properties - CWindowImpl.

The Reflection Window

We need to create a reflection window. It's just a CWindowImplwindow implmented class. We are going to be adding a small bit offunctionality just to create the toolbar object and be able to accessthe toolbar member from our deskband class.

  • From the Insert menu, select New ATL Object menu item; the ATL Object Wizard dialog is invoked.
  • In the ATL Object Wizard dialog, select the RadBytes Category. Ifthis category is missing then make sure that the RBDeskband andCWindowImpl ATL Object Wizards are installed.
  • Next select the CWindowImpl item from the Objects list.
  • Click the Next button to invoke the ATL Object Wizard Properties dialog for the Deskband object. See Figure 3.
  • On the Names property page, type "ReflectionWnd" into the Short Name field. See Figure 10.
  • We will not change any of the CWindowImpl property page values this time.
  • Click the OK button on the ATL Object Wizard Properties Dialog. TheATL Object Wizard will create the files necessary for our CWindowImplderived class implementation.
Figure 10. ATL Object Wizard Properties - Names.
Figure 10. ATL Object Wizard Properties - Names.

Adding The Details

Now that we have our window classes available we can add ourfunctionality for our toolbar to the appropriate window classes. Let usstart with the deepest window class and work our way back out.

The EditQuote Details

For the EditQuote implementation, we need to be able to processkeystrokes from the user and let the host that created our deskbandobject know our edit box has focus. To accomplish the first part, weneed to look ahead and see that our DeskBand object will beimplementing the IInputObject interface. So the host will query forthat interface and know that we want to recieve messages and be giventhe chance to recieve focus. When the host sends our band messages toprocess they come through the IInputObject::TranslateAcceleratormethod. Our DeskBand will implement this method and it is best if ouredit box, which will process the message, copy theTranslateAcceleratorIO method definition so our deskband can forwardthe message easily through a logical method call.

Figure 11. FileView Pane.
Figure 11. FileView Pane.

Inthe FileView pane (See Figure 11), double click the EditQuote.h itemunder Header Files. This will open the header file in the editing area.We now need to define the method definition for TranslateAcceleratorIO.To do this, add below the virtual CEditQuote destructor the followingline of code:

Collapse
STDMETHOD(TranslateAcceleratorIO)(LPMSG lpMsg);

Now Open the EditQuote.cpp source file and add the implementation of TranslateAcceleratorIO to the file

Collapse
STDMETHODIMP CEditQuote::TranslateAcceleratorIO(LPMSG lpMsg)
{
TranslateMessage(lpMsg);
DispatchMessage(lpMsg);
return S_OK;
}

Now our DeskBand implementation can call this message and the edit boxwill process the key strokes properly. But wait, our edit box shouldnotify the toolbar to load the quote details entered if the key strokeis the enter key. For this, we will need to define a message id andsend that message to the parent window to process. In the EditQuote.hheader file below the include statement, add the definition of ourmessage id as shown below in bold.

Collapse
#include <commctrl.h>


const int WM_GETQUOTE = WM_USER + 1024;

In our EditQuote.cpp file we will add code to ourTranslateAcceleratorIO method to process the enter key. Add the codebelow in bold to the EditQuote.cpp file.

Collapse
STDMETHODIMP CEditQuote::TranslateAcceleratorIO(LPMSG lpMsg)
{
int nVirtKey = (int)(lpMsg->wParam);
if (VK_RETURN == nVirtKey)
{
// remove system beep on enter key by setting key code to 0
lpMsg->wParam = 0;
::PostMessage(GetParent(), WM_GETQUOTE, 0, 0);
return S_OK;
}


TranslateMessage(lpMsg);
DispatchMessage(lpMsg);
return S_OK;
}

Now our edit box will notify the parent when the user presses the enterkey so that the parent can retrieve the requested ticker symboldetails, this part will be implemented when we get to the toolbardetails.

The first part of the Edit boxes implementation is finished. Nowwe need to be able for the edit box to have the deskband notify thehost that we have focus or that we don't have focus any longer. To dothis we will need to add a method for the deskband to pass us it'saddress so that we can call a method of the deskband class. These nextsteps will involve adding code to the CEditQuote class and to ourDeskband class implementation.

Open the EditQuote.h file and add a forward reference to theCStockBar class so that we can defined our methods and members in ourclass header without knowing the implementation details of our deskbandclass, add the line in bold.

Collapse
#include <commctrl.h>


const int WM_GETQUOTE = WM_USER + 1024;

class CStockBar;

For our class to notify the host that our deskband has focus, we needto add a message handler for EN_SETFOCUS. Add the command code handlercode below in bold to your EditQuote.h file.

Collapse
   BEGIN_MSG_MAP(CEditQuote)
COMMAND_CODE_HANDLER(EN_SETFOCUS, OnSetFocus)
END_MSG_MAP()

Then add the method definition for OnSetFocus to the header file belowthe commented out handler prototypes as follows below in bold.

Collapse
// Handler prototypes:
// 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);
LRESULT OnSetFocus(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);

Before we implement the OnSetFocus method, we need to define a methodfor our deskband to tells of it's address and to retain that addressfor later use. Add the following lines of code to your EditQuote.h filebelow the TranslateAcceleratorIO definition.

Collapse
   void SetBand(CStockBar* pBand);
private:
CStockBar* m_pBand;

Now we can move to your EditQuote.cpp source file and implement themessage handler, the SetBand method, and update theTranslateAcceleratorIO method for focus change. At the top of theEditQuote.cpp file add the following includes to the include list asshown below in bold.

Collapse
#include "stdafx.h"

#include "EditQuote.h"

#include "MotleyFool.h"

#include "StockBar.h"


Now when we can use the methods of the CStockBar class in our code. Addto the end of the constructor, the initalization of m_pBand. Don'tforget the colon operator.

Collapse
CEditQuote::CEditQuote()
: m_pBand(NULL)
{
}

Next we will add the SetBand implementation to our CEditQuote class.Notice that since it is not a com object we don't call AddRef orRelease on it. It's just a pointer to the class and when it's destroyedour CEditQuote instance will also be destroyed. We could have also donethis inline in our header file.

Collapse
void CEditQuote::SetBand(CStockBar* pBand)
{
m_pBand = pBand;
}

Next we need to add our message handler for our EN_SETFOCUS message.Add the code below to the end of the EditQuote.cpp source file.

Collapse
LRESULT CEditQuote::OnSetFocus(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
//Notify host that our band has the focus so TranslateAcceleratorIO
//messages are directed towards our band.
if (m_pBand) m_pBand->FocusChange(TRUE);
return 0;
}

We have one more section of code to add to our CEditQuoteimplementation then we can move to our CStockBar class to define andimplement the FocusChange method. Add the following code to theCEditQuote TranslateAcceleratorIO method as shown in bold. We add thiscode so the host knows that we are no longer needing messages.

Collapse
STDMETHODIMP CEditQuote::TranslateAcceleratorIO(LPMSG lpMsg)
{
int nVirtKey = (int)(lpMsg->wParam);
if (VK_RETURN == nVirtKey)
{
// remove system beep on enter key by setting key code to 0
lpMsg->wParam = 0;
::PostMessage(GetParent(), WM_GETQUOTE, 0, 0);
return S_OK;
}
else if (WM_KEYDOWN == lpMsg->message && nVirtKey == VK_TAB)
{
// we no longer need messages forwarded to our band
if (m_pBand) m_pBand->FocusChange(FALSE);
return S_FALSE;
}


TranslateMessage(lpMsg);
DispatchMessage(lpMsg);
return S_OK;
}

Open the StockBar.h header file and add the definition of FocusChange to it as shown below in bold.

Collapse
// IStockBar
public:
void FocusChange(BOOL bHaveFocus);

Now open the StockBar.cpp source file and add the implementation of FocusChange to it at the bottom.

Collapse
void CStockBar::FocusChange(BOOL bHaveFocus)
{
if (m_pSite)
{
IUnknown* pUnk = NULL;
if (SUCCEEDED(QueryInterface(IID_IUnknown, (LPVOID*)&pUnk)) && pUnk != NULL)
{
m_pSite->OnFocusChangeIS(pUnk, bHaveFocus);
pUnk->Release();
pUnk = NULL;
}
}
}

We have finished off the work needed for the edit box to work properlyin our toolbar. Now we need to build our toolbar up so that it has abutton and contains our edit box. Then we will add the nessecities toour reflection window and update our IDeskBand to provide the correctinformation to our host. We are almost there. If you were to compilethe project and run it, it would except that the band would look likethe following in figure X.

The MFToolbar Details

For the implementation of the MFToolbar window, we need to be ableto have it do the following things. It must be able to process theWM_GETQUOTE message from the EditQuote window, communicate with the webbrowser in which the toolbar is located, create the buttons and placethe child windows on itself, forward messages to the EditQuote childwindow and size itself appropriately to the users actions.

So, the first thing we should do since our toolbar is going tocontain an instance of CEditQuote is include the header file for theCEditQuote class. We will do this by opening the MFToolbar.h file andinserting the include statement for the CEditQuote class as shown inbold below.

Collapse
#include <commctrl.h>

#include "EditQuote.h"

Next we need to add a member to our toolbar class for the CEditQuoteclass. We will do this by adding a private section to the end of ourclass and defining a member variable as shown below in bold.

Collapse
   CMFToolbar();
virtual ~CMFToolbar();

private:
CEditQuote m_EditWnd;

Now that we have our member defined for our EditQuote window, we needto forward window messages to it so that keyboard inputs are processedappropriately. We do this by updating the toolbar message map to chainmessages to our member as shown below in bold.

Collapse
   BEGIN_MSG_MAP(CMFToolbar)
CHAIN_MSG_MAP_MEMBER(m_EditWnd)
END_MSG_MAP()

Looking forward, our deskband will need to get the EditQuote member todeterimine if it has focus and also to make it function. We could justexpose the EditQuote member directly by having made it a public memberinstead of private, but by making it private we can expose a methodthat will expose our member giving us flexibility later to modify theclass if the need should arise. So to expose the EditQuote member, wewill add a fuction to our toolbar class to return the reference to theEditQuote member. In the toolbar header file, add the method definitionand implementation below in bold to it.

Collapse
   CMFToolbar();
virtual ~CMFToolbar();
inline CEditQuote& GetEditBox() {return m_EditWnd;};

Now we will create our toolbar window. Our toolbar consists of theEditQuote box and a button with an icon and text on it. To house theicon, our toolbar will need an image list handle to send to the toolbarwindow. So we need to add a few things to our toolbar header filebefore we go and implement the toolbar's creation. The first thing wewill add is the member variable for our image list. Add the line inbold below to your toolbar header file.

Collapse
private:
CEditQuote m_EditWnd;
HIMAGELIST m_hImageList;

Then we will add a message handler to our toolbar's message map anddefine the message handlers function definition to our header file andthe follow lines of code in bold to your header file.

Collapse
   BEGIN_MSG_MAP(CMFToolbar)
CHAIN_MSG_MAP_MEMBER(m_EditWnd)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
END_MSG_MAP()

// Handler prototypes:
// 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);
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

Before we can implement our toolbar's creation, we need to create aicon resource that our toolbar button will use next to its text. So goto the resource view and add a new icon to the project resources. Youcan do this by right clicking on "MotleyFool resources" and selecting"Insert..." from the context menu. In the Insert Resource dialog box,select Icon from the Resource type list and click the New button. Thiswill insert a blank icon resource into your project. Rename the icon'sresource ID by right clicking on the icon resource in the resource viewand selecting the properties menu item from the context menu. Changethe id to IDI_MOTLEY. Then draw or graciously borrow an icon from TheMotley Fool to use on the toolbar. I graciously borrowed the icon fromtheir website and adapted it into the icon.

Nowwe can implement it our toolbars creation. Open the MFToolbar sourcefile and implement the details of the toolbar creation as describedbelow.

First we need to include the project resource file sowe can use the icon ID in our code. Add the line in bold below to ourtoolbar's source file as shown.

Collapse
#include "stdafx.h"

#include "resource.h"

#include "MFToolbar.h"

Next we need to update our constructor implementation. We need toinitialize our handle to the image list by setting it to NULL. Don'tforget the colon.

Collapse
CMFToolbar::CMFToolbar()
: m_hImageList(NULL)
{
}

Next we need to update our destructor, it should destroy the image listand destroy the window if it has not yet been destroyed.

Collapse
CMFToolbar::~CMFToolbar()
{
ImageList_Destroy(m_hImageList);
if (IsWindow()) DestroyWindow();

}

Before we can implement our toolbar's creation, we need to add aresource symbol to our project for the toolbar button's ID. We couldjust use a #definestatement at the top of the source file, but for cleanliness and sincewe are already including the resource.h file, we will add it to ourresource file. Go to the "View" menu and select "Resource Symbols" menuitem. Click the "New" button on the Resource Symbols dialog. Then entera name of "IDM_GETQUOTE" and click OK. Then close the Resource Symbolsdialog.

Now we can create our toolbar, we defined the OnCreatemethod in our header file and need to now implement it. Add thefollowing function and its implementation to the end of the toolbarsource file.

Collapse
LRESULT CMFToolbar::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// buttons with images and text
SendMessage(m_hWnd, TB_SETEXTENDEDSTYLE, 0, (LPARAM)TBSTYLE_EX_MIXEDBUTTONS);
// Sets the size of the TBBUTTON structure.
SendMessage(m_hWnd, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
// Set the maximum number of text rows and bitmap size.
SendMessage(m_hWnd, TB_SETMAXTEXTROWS, 1, 0L);

// add our button's caption to the toolbar window
TCHAR* pCaption = _T("Get Quote");
int iIndex = ::SendMessage(m_hWnd, TB_ADDSTRING, 0,(LPARAM)pCaption);

// load our button's icon and create the image list to house it.
HICON hMotley = LoadIcon(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDI_MOTLEY));
m_hImageList = ImageList_Create(16,16, ILC_COLOR16, 1, 0);
int iImageIndex = ImageList_AddIcon(m_hImageList, hMotley);
DestroyIcon(hMotley);
// Set the toolbar's image
::SendMessage(m_hWnd, TB_SETIMAGELIST, 0, (LPARAM)m_hImageList);

// add the button for the toolbar to the window
TBBUTTON Button;
ZeroMemory((void*)&Button, sizeof(TBBUTTON));
Button.idCommand = IDM_GETQUOTE;
Button.fsState = TBSTATE_ENABLED;
Button.fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT;
Button.dwData = 0;
Button.iString = iIndex;
Button.iBitmap = 0;
::SendMessage(m_hWnd, TB_INSERTBUTTON, 0, (LPARAM)&Button);

// create our EditQuote window and set the font.
RECT rect = {0,0,0,0};
m_EditWnd.Create(m_hWnd, rect, NULL, WS_CHILD|WS_VISIBLE, WS_EX_CLIENTEDGE);
m_EditWnd.SetFont(static_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT)));
return 0;
}

If you try to compile at this point, you will see that there areunresolved externals for the image list method calls. We need to add alibrary to the project. To do this select the "Project|Settings" menuitem. On the Project Settings dialog, Select All Configurations fromthe "Settings For" combo box. Then select the "Link" tab and append tothe "Object/Library modules" edit box "comctl32.lib". Then click OK. Ifyou compile the project now, it will compile successfully and the imagelist unresolved externals will disappear.

Westill have a few things we need to do to the Toolbar window. It needsto process Command messages, resopnd to WM_GETQUOTE messages, andresize itself. Let's conquer the latter first.

To orgainze thetooblar correctly, we should have the toolbar responsd to WM_SIZEmessages. To do this, we will add to our tooblar header file a messagehandler for the WM_SIZE message and add a function definition forOnSize which WM_SIZE messages will be sent to. Open our toolbar headerfile and add the lines in bold below to it as shown.

Collapse
   BEGIN_MSG_MAP(CMFToolbar)
CHAIN_MSG_MAP_MEMBER(m_EditWnd)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SIZE, OnSize)
END_MSG_MAP()

// Handler prototypes:
// 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);
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

Now we need to implement our OnSize function. Open the toolbar sourcefile and add the function implementation below to the end of the file.

Collapse
LRESULT CMFToolbar::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// based on the size of the window area minus the size of the toolbar button,
// indent the toolbar so that we can place the edit box before the toolbar
// button. This will right justify the toolbar button in the toolbar and the
// edit box will use the vaction space to the left of the button but after the
// toolbar text as it's usable space.
RECT wndRect, btnRect;
GetClientRect(&wndRect);
::SendMessage(m_hWnd, TB_GETITEMRECT, 0, (LPARAM)&btnRect);
wndRect.right -= (btnRect.right - btnRect.left);
SendMessage(TB_SETINDENT, wndRect.right - wndRect.left);
// put a small spacing gap between the edit box's right edge and the toolbar button's left edge
wndRect.right -= 3;
m_EditWnd.MoveWindow(&wndRect, FALSE);
return 0;
}

We still need to respond to user input for the toolbar button and fromthe edit box when the user presses the enter key. What we want thetoolbar to do is tell the web browser host to navigate to the motelyfool website and retrieve the stock quotes requested. First we need forthe Deskband object to tell our toolbar window what the web browserinstance is so that the toolbar window can communicate with it. To dothis, we will add a private member variable and a public method inwhich the deskband can set the web browser instance. Our window willthen use the member variable set to tell the web browser where tonavigate and what to retrieve.

To do this, open the toolbar header file and add the lines in bold to the file.

Collapse
   CMFToolbar();
virtual ~CMFToolbar();
inline CEditQuote& GetEditBox() {return m_EditWnd;};
void SetBrowser(IWebBrowser2* pBrowser);

private:
CEditQuote m_EditWnd;
HIMAGELIST m_hImageList;
IWebBrowser2* m_pBrowser;

Now, open the toolbar source file. We will update the constructor andinitialize our member variable to null. Then we will update the toolbardestructor and release the member variable if it hasn't been. Then wewill implement the SetBrowser method.

Initialize the web browser member variable.

Collapse
CMFToolbar::CMFToolbar()
: m_hImageList(NULL)
, m_pBrowser(NULL)
{
}

Release the web browser object if held.

Collapse
CMFToolbar::~CMFToolbar()
{
ImageList_Destroy(m_hImageList);
SetBrowser(NULL);
if (IsWindow()) DestroyWindow();
}

Implement SetBrowser()

Collapse
void CMFToolbar::SetBrowser(IWebBrowser2* pBrowser)
{
if (m_pBrowser) m_pBrowser->Release();
m_pBrowser = pBrowser;
if (m_pBrowser) m_pBrowser->AddRef();
}

If you try and compile the project, you will notice that IWebBrowser2is undefine in our header file. This is because we need to update ourstdafx.h header file to include system files that define IWebBrowser2.To do this, open stdafx.h and add the following lines in bold to thefile, then recompile.

Collapse
extern CComModule _Module;
#include <atlcom.h>

#include <atlwin.h>


//
// These are needed for IDeskBand
//

#include <shlguid.h>

#include <shlobj.h>


Now we can add message handlers for WM_COMMAND and WM_GETQUOTE to ourtoolbar class to handle the toolbar button being pressed and the enterkey being pressed in the edit box by the user. To do this, we will needto add to our toolbar header file message handlers and functiondefinitions for WM_COMMAND and WM_GETQUOTE. We will also need to add aprivate method which both will call if they need to to preform the samefunctionality (better than repeating code that does the same thing). Solet's add the message handlers to the header file.

Collapse
   BEGIN_MSG_MAP(CMFToolbar)
CHAIN_MSG_MAP_MEMBER(m_EditWnd)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_COMMAND, OnCommand)
MESSAGE_HANDLER(WM_GETQUOTE, OnGetQuote)

END_MSG_MAP()

// Handler prototypes:
// 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);
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnGetQuote(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

Now we can add the function delcaration for our GetQuote privaet method.

Collapse
private:
CEditQuote m_EditWnd;
HIMAGELIST m_hImageList;
IWebBrowser2* m_pBrowser;
void GetQuote();

Now let's switch to our source file and implement our message handlerfunctions and the GetQuote method. Add the code below to the end of thetoolbar source file.

Collapse
LRESULT CMFToolbar::OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (!HIWORD(wParam))
{
long lSite = LOWORD(wParam);
if ( lSite == IDM_GETQUOTE)
{
GetQuote();
return 0;
}
}
return -1;
}

LRESULT CMFToolbar::OnGetQuote(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
GetQuote();
return 0;
}

void CMFToolbar::GetQuote()
{
// if we have a web browser pointer then try to navigate to The Motley Fool site to retrieve stock quotes.
if (m_pBrowser)
{
VARIANT vEmpty;
VariantInit(&vEmpty);
m_pBrowser->Stop();
_bstr_t bsSite;
// if the user has entered stock quotes then append them to the url
if (m_EditWnd.GetWindowTextLength())
{
BSTR bstrTickers = NULL;
m_EditWnd.GetWindowText(&bstrTickers);
bsSite = "http://quote.fool.com/news/symbolnews.asp?Symbols=";
bsSite += bstrTickers;
SysFreeString(bstrTickers);
}
// if the user has not entered any stock quotes then just take them to The Motley Fool website.
else
bsSite = "http://www.fool.com";
// have the webrowser navigate to the site URL requested depending on user input.
m_pBrowser->Navigate(bsSite, &vEmpty, &vEmpty, &vEmpty, &vEmpty);
}
}

If you try to compile, you will notice that _bstr_t is undefined. Thatis because the class is defined in comdef.h. We need to add this to ourstdafx.h header file so that we can use it as well as any other classin our project (which we will need to for IInputObject). Open thestdafx.h header file and add the lines in bold to the file asindicated.

Collapse
#include <shlobj.h>


// needed for IInputObject and _bstr_t
#include <comdef.h>


Our implementation of the toolbar window is complete. Now we can moveon to the Reflection window which creates our toolbars and forwardscommand messages to it.

The Reflection Window Details

For the Reflection Window, it's only purpose is to create thetoolbar window (which it doesn't need to really do, but by doing soeases message forwarding) and forward messages to it. The reflectionwindow is not visible, it's just a layer added so that message from thetoolbar get to the toolbar. If we didn't have this window present,toolbar messages would get sent to the parent window (which we do notcontrol) and we would never get them. This is not good since we need torespond to WM_COMMAND messages from the toolbar. Thus the need for thereflection window. So let's create the toolbar window and the messageforwarding for it.

Open the ReflectionWnd.h header file. Wewill need to include the toolbar header file and add a private membervariable to our reflection window to create it and to pass it to themessage chain. First things first, add the include statement below sowe can use the CMFToolbar class.

Collapse
#include <commctrl.h>

#include "MFToolbar.h"

Next add a private member variable to the end of the reflection window class for the toolbar as shown below.

Collapse
CReflectionWnd();
virtual ~CReflectionWnd();

private:
CMFToolbar m_ToolbarWnd;

Next update the reflection window message map to forward messages to the toolbar window as shown below.

Collapse
   BEGIN_MSG_MAP(CReflectionWnd)
CHAIN_MSG_MAP_MEMBER(m_ToolbarWnd)
END_MSG_MAP()

We will also need a public function for our deskband class to get atour toolbar window. We will do the same as we did with the EditQuotewindow by providing a function to get at the member variableindirectly. Add the line of code in bold below to the header file asindicated.

Collapse
   CReflectionWnd();
virtual ~CReflectionWnd();
inline CMFToolbar& GetToolBar() { return m_ToolbarWnd;};

Lastly, we need to create the toolbar window and will do so in theWM_CREATE message handler for our reflection window. Add the code belowin bold to the reflection window header file. Then we will implementthe OnCreate method in the source file.

Collapse
   BEGIN_MSG_MAP(CReflectionWnd)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
CHAIN_MSG_MAP_MEMBER(m_ToolbarWnd)
END_MSG_MAP()

// Handler prototypes:
// 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);
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

Now open the ReflectionWnd.cpp source file and add the implementation of OnCreate to its end.

Collapse
const DWORD DEFAULT_TOOLBAR_STYLE = 
/*Window styles:*/ WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | WS_TABSTOP |
/*Toolbar styles:*/ TBSTYLE_TOOLTIPS | TBSTYLE_FLAT | TBSTYLE_TRANSPARENT | TBSTYLE_LIST | TBSTYLE_CUSTOMERASE |
TBSTYLE_WRAPABLE |
/*Common Control styles:*/ CCS_TOP | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE;

LRESULT CReflectionWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
RECT rect;
GetClientRect(&rect);
m_ToolbarWnd.Create(m_hWnd, rect, NULL, DEFAULT_TOOLBAR_STYLE);
return 0;
}

You will notice that we defined a constant for the toolbar style. This was done to make the code more readable.

The only thing left to do to the reflection window code is update the destructor to destory the window if it's still present.

Collapse
CReflectionWnd::~CReflectionWnd()
{
if (IsWindow()) DestroyWindow();
}

 

Finishing Off The Deskband Toolbar

All that's left is for our deskband to create the toolbar window,have the host use the toolbar window, remove the unused IPersistStreamimplementation, implement IInputObject (for focus control) and do somecode tweaking. So lets wrap this toolbar up. Open the StockBar.h headerfile. Remove the following lines of code that are in bold since wemoved them to our stdafx.h for our other classes to also use.

Collapse
#include "resource.h"       // main symbols


//
// These are needed for IDeskBand
//

#include <shlguid.h>

#include <shlobj.h>

Next remove the following line from the class delcaration.

Collapse
public IPersistStream,

The top of the class declaration should now look like this,

Collapse
class ATL_NO_VTABLE CStockBar : 
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CStockBar, &CLSID_StockBar>,
public IDeskBand,
public IObjectWithSite,
public IDispatchImpl<IStockBar, &IID_IStockBar, &LIBID_MOTLEYFOOLLib>
{

Next scroll down to the COM Map and remove the following two lines of code,

Collapse
   COM_INTERFACE_ENTRY(IPersist)
COM_INTERFACE_ENTRY(IPersistStream)

Your COM Map should now look like this,

Collapse
BEGIN_COM_MAP(CStockBar)
COM_INTERFACE_ENTRY(IStockBar)
COM_INTERFACE_ENTRY(IOleWindow)
COM_INTERFACE_ENTRY_IID(IID_IDockingWindow, IDockingWindow)
COM_INTERFACE_ENTRY(IObjectWithSite)
COM_INTERFACE_ENTRY_IID(IID_IDeskBand, IDeskBand)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

Scroll down the header file further and remove the sections of functiondeclarations for IPersist and IPersistStream, which includes thefollowing lines.

Collapse
// IPersist
public:
STDMETHOD(GetClassID)(CLSID *pClassID);

// IPersistStream
public:
STDMETHOD(IsDirty)(void);
STDMETHOD(Load)(IStream *pStm);
STDMETHOD(Save)(IStream *pStm, BOOL fClearDirty);
STDMETHOD(GetSizeMax)(ULARGE_INTEGER *pcbSize);

There should now be nothing related to IPersist or IPersistStreambetween the IDockingWindow and IStockBar function declaration sections.

Nowwe need to remove the IPersist and IPersistStream functionimplementations from the StockBar.cpp source file. Find the GetClassID,IsDirty, Load, Save, and GetSizeMax function implementations and removethem from the file. They should be directly above the FocusChangeMethod we added earlier.

Now we can start adding code to ourdeskband. Open the stockbar.h header file and add the include for thereflection window class as shown below in bold.

Collapse
#include "resource.h"       // main symbols

#include "ReflectionWnd.h"

Next, find the protected section at the end of the file and replace the line

Collapse
   HWND m_hWnd;

with

Collapse
   CReflectionWnd m_ReflectWnd;

If you try to compile, you will find that it will be unsuccessful sincewe have removed m_hWnd and have not removed all occurances of m_hWndfrom the class source file. We will replace all occurances with moreappropriate code for our Reflection window and toolbar window. Open theStockBar.cpp source file, Remove the following line from the classconstructor

Collapse
   m_hWnd(NULL),

your class constructor should now look as follows with a SetBand call added,

Collapse
CStockBar::CStockBar(): 
m_dwBandID(0),
m_dwViewMode(0),
m_bShow(FALSE),
m_bEnterHelpMode(FALSE),
m_hWndParent(NULL),
m_pSite(NULL)
{
m_ReflectWnd.GetToolBar().GetEditBox().SetBand(this);
}

Next, update the RegisterAndCreateWindow function by replacing thetemporary m_hWnd construction with the Reflection Window construction.Your RegisterAndCreateWindow implementation should look as follows:

Collapse
BOOL CStockBar::RegisterAndCreateWindow()
{
RECT rect;
::GetClientRect(m_hWndParent, &rect);
m_ReflectWnd.Create(m_hWndParent, rect, NULL, WS_CHILD);
// The toolbar is the window that the host will be using so it is the window that is important.
return m_ReflectWnd.GetToolBar().IsWindow();
}

As we scroll through the code, we should fix some other things. Atoolbar has a default height of 22 so we need to update our GetBandInfomethod so that all the y measurements are 22 not 20. We also want ourIntegral sizing to be non sizeable so we need to set the DBIM_INTEGRALx and y values to 0. While we are at it, we should fix the title of ourtoolbar so it says "The Motley Fool". You'll find this constant definednear the top of the source file, update it now.

Wewill now update the GetWindow call so that the toolbar window handle isreturned and not the invalid m_hWnd variable. Update your GetWindowmethod so it looks as follows,

Collapse
STDMETHODIMP CStockBar::GetWindow(HWND* phwnd)
{
HRESULT hr = S_OK;
if (NULL == phwnd)
{
hr = E_INVALIDARG;
}
else
{
*phwnd = m_ReflectWnd.GetToolBar().m_hWnd;
}
return hr;
}

Now we need to update teh CloseDW method so that it does not destoryour window buy hide it. We do this much like the MFC CToolbar classdoes, the class destructor will destroy the window. Your CloseDW methodshould look as follows,

Collapse
STDMETHODIMP CStockBar::CloseDW(unsigned long dwReserved)
{
ShowDW(FALSE);
return S_OK;
}

Working our way through our class implementation, we will update thenext method that needs updating. Update the ShowDW class to show orhide the toolbar window which the host is using. Your ShowDW classshould look as follows,

Collapse
STDMETHODIMP CStockBar::ShowDW(BOOL fShow)
{
m_bShow = fShow;
m_ReflectWnd.GetToolBar().ShowWindow(m_bShow ? SW_SHOW : SW_HIDE);
return S_OK;
}

We are almost done modifying the CStockBar class we need to do one lastbit of updating. We need to modify the SetSite implementation torelease the browser window that the toolbar may be using if we have aIInputObjectSite object and we need to query the OleCommandTarget'sServiceProvider for the IWebBrowser2 interface which we will then setto our toolbar. The modified parts of the SetSite method implementationare below in bold.

Collapse
STDMETHODIMP CStockBar::SetSite(IUnknown* pUnkSite)
{
//If a site is being held, release it.
if(m_pSite)
{
m_ReflectWnd.GetToolBar().SetBrowser(NULL);
m_pSite->Release();
m_pSite = NULL;
}

//If punkSite is not NULL, a new site is being set.
if(pUnkSite)
{
//Get the parent window.
IOleWindow *pOleWindow = NULL;

m_hWndParent = NULL;

if(SUCCEEDED(pUnkSite->QueryInterface(IID_IOleWindow, (LPVOID*)&pOleWindow)))
{
pOleWindow->GetWindow(&m_hWndParent);
pOleWindow->Release();
}

if(!::IsWindow(m_hWndParent))
return E_FAIL;

if(!RegisterAndCreateWindow())
return E_FAIL;

//Get and keep the IInputObjectSite pointer.
if(FAILED(pUnkSite->QueryInterface(IID_IInputObjectSite, (LPVOID*)&m_pSite)))
{
return E_FAIL;
}

IWebBrowser2* s_pFrameWB = NULL;
IOleCommandTarget* pCmdTarget = NULL;
HRESULT hr = pUnkSite->QueryInterface(IID_IOleCommandTarget, (LPVOID*)&pCmdTarget);
if (SUCCEEDED(hr))
{
IServiceProvider* pSP;
hr = pCmdTarget->QueryInterface(IID_IServiceProvider, (LPVOID*)&pSP);

pCmdTarget->Release();

if (SUCCEEDED(hr))
{
hr = pSP->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, (LPVOID*)&s_pFrameWB);
pSP->Release();
_ASSERT(s_pFrameWB);
m_ReflectWnd.GetToolBar().SetBrowser(s_pFrameWB);
s_pFrameWB->Release();
}
}

}
return S_OK;
}

If you try to compile and use the toolbar right now, it will functionpartially. Tabbing and input control will not work correctly since wehave yet to implement IInputObject for our deskband. Let's do that nowsince it's the last bit of code we will write for our simple deskband.You may also notice that the Toolbars context menu and View|Toolbarsmenus still say CStockBar Class. we will fix this problem in theFinishing Touches section below.

IInputObject Implementation

To get tabbing and input control to work correctly for any deskbandis quite simple. You need but to implement IInputObject. The host willquery our deskband to see if this interface is implemented and if it iswill call the methods to see if we require input focus and let us alsoprocess messages from the user through the host. To do this, open thestockbar.h header file. To the stockbar class declaration add the linebelow in bold,

Collapse
class ATL_NO_VTABLE CStockBar : 
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CStockBar, &CLSID_StockBar>,
public IDeskBand,
public IObjectWithSite,
public IInputObject,
public IDispatchImpl<IStockBar, &IID_IStockBar, &LIBID_MOTLEYFOOLLib>
{

Next scroll down to the COM Map and add an entry for IInputObject as shown below in bold,

Collapse
BEGIN_COM_MAP(CStockBar)
COM_INTERFACE_ENTRY(IStockBar)
COM_INTERFACE_ENTRY(IInputObject)
COM_INTERFACE_ENTRY(IOleWindow)
COM_INTERFACE_ENTRY_IID(IID_IDockingWindow, IDockingWindow)
COM_INTERFACE_ENTRY(IObjectWithSite)
COM_INTERFACE_ENTRY_IID(IID_IDeskBand, IDeskBand)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

Next add the following section of function declarations to your header file, I placed mine before the IStockBar section.

Collapse
// IInputObject
public:
STDMETHOD(HasFocusIO)(void);
STDMETHOD(TranslateAcceleratorIO)(LPMSG lpMsg);
STDMETHOD(UIActivateIO)(BOOL fActivate, LPMSG lpMsg);

All that remains is to implement these three functions. Add thefunction implementations below to the end of the stockbar.cpp sourcefile.

Collapse
STDMETHODIMP CStockBar::HasFocusIO(void)
{
// if any of the windows in our toolbar have focus then return S_OK else S_FALSE.
if (m_ReflectWnd.GetToolBar().m_hWnd == ::GetFocus())
return S_OK;
if (m_ReflectWnd.GetToolBar().GetEditBox().m_hWnd == ::GetFocus())
return S_OK;
return S_FALSE;
}
STDMETHODIMP CStockBar::TranslateAcceleratorIO(LPMSG lpMsg)
{
// the only window that needs to translate messages is our edit box so forward them.
return m_ReflectWnd.GetToolBar().GetEditBox().TranslateAcceleratorIO(lpMsg);
}
STDMETHODIMP CStockBar::UIActivateIO(BOOL fActivate, LPMSG lpMsg)
{
// if our deskband is being activated then set focus to the edit box.
if(fActivate)
{
m_ReflectWnd.GetToolBar().GetEditBox().SetFocus();
}
return S_OK;
}

Our toolbar is functionaly done, compile, run it and see. It works asdescribed and is fairly simple. Let's put some finishing UI touches onit for IE and our users to use.

Finishing Touches

The are only 2 finishing touches to make, One is to fix the contextmenu text. The other is to add button support to the main IE toolbar.Let's do them in order.

To fix the context menu text problem,open the StockBar.rgs project file and change all occurances of"StockBar Class" to "The Motley Fool Quotes". Compile it, run it, andsee. While you only need to change one of them, it's nicer if they allmatch.

Now let's add button support for our toolbar. Updatethe stockbar.rgs file contents by appending the text below to it'scontents.

Collapse
HKLM
{
Software
{
Microsoft
{
'Internet Explorer'
{
Extensions
{
ForceRemove{A26ABCF0-1C8F-46e7-A67C-0489DC21B9CC} = s 'The Motley Fool Quotes'
{
val BandClsid = s '{A6790AA5-C6C7-4BCF-A46D-0FDAC4EA90EB}'
val ButtonText = s 'The Motley Fool'
val Clsid = s '{E0DD6CAB-2D10-11D2-8F1A-0000F87ABD16}'
val 'Default Visible' = s 'Yes'
val 'Hot Icon' = s '%MODULE%,425'
val Icon = s '%MODULE%,425'
val MenuStatusBar = s 'The Motley Fool Stock Quote Toolbar'
val MenuText = s 'The Motley Fool'
}
}
}
}
}
}

The replace the 425 with the id from resource.h of IDI_MOTLEY. Alsoreplace the BandClsid value with the GUID of our toolbar, the abovevalues represent the source code for the article. Now compile thetoolbar again. Then start IE and right click on the Standard Buttonstoolbar, Select "Customize" from the context menu. Scroll down theAvailable toolbar buttons listbox and find "The Motley Fool" item, SeeFigure 12. Select it and click the Add button in the middle of thedialog. The item will move to the right as in Figure 13. Click theClose Button. You'll see the button added as shown in the before figure14 and after figure 15.

Figure 12. Customize Toolbar - Available Toolbar Buttons.
Figure 12. CustomizeToolbar - Available Toolbar Buttons. Figure 13. Customize Toolbar - Current Toolbar Buttons.
Figure 13. Customize Toolbar - Current Toolbar Buttons. Figure 14. IE Standard Buttons - Before.
Figure 14. IE Standard Buttons - Before. Figure 15. IE Standard Buttons - After.
Figure 15. IE Standard Buttons - After.

Conclusion

While this tutorial is long hopefully the explaination was clear.From writing this tutorial it is easy to see that the RBDeskband ATLObject Wizard has some room for improvement but provided enough of abase for us to develop our simple example. In the end you can see thatthe toolbar we created is much like the Address bar. The differenceslie in how MS implemented theirs versus how I implemented mine. Asalways feedback is welcome. Enjoy.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Erik Thompson


Member Erik lives in Redmond, Washington. He works as a Senior SoftwareEngineer specializing in C++, COM, ATL and the middle-tier and now.NET. When he isn't coding for work, he can be found trying to extendInternet Explorer with yet another Desk band or simplifying hisdevelopment process with ATL Object Wizards.

He spends his free time snowboarding, mountain biking, and online gaming.
Occupation: Web DeveloperLocation: United States United States