Having recieved a number of requests for a tutorial of sorts on developing Internet Explorer Toolbars with the RBDeskband and CWindowImpl wizards that I created, I have come up with a simple sample toolbar which can be used as a reference when developing your own toolbars or explorer bars. The tutorial will walk you through the stages of developing a toolbar for IE that is very similar to the Address bar that is already present in IE. I wanted to do a tutorial that would provide a realistic sample and would produce an end result that could be used by others after the tutorial was finish. So, the tutorial is going to show you how to develop an IE toolbar to get stock quote information from The Motley Fool website. So with that, let us get started.
This tutorial assumes that you already know how to program in C++ and know some information about ATL and COM. To work through this tutorial, you will need the following installed on your development machine:
The IE toolbar consists of a COM component supporting IDeskband and a few other necessary interfaces for which IE looks for when loading registered toolbars, explorer bars and deskbands. The RBDeskband ATL Object Wizard provides most of the framework for this article. What we will need to do is create our project, a new COM object to house our toolbar, and a few CWindowImpl classes using the CWindowImpl ATL Object Wizard. Then connecting these parts together we will produce the IE toolbar in the picture at the top of the article. Visually the toolbar consists of an editbox and a toolbar with one button on it. In actuality the toolbar consists of the fore mentioned and a non visible window that is used to reflect messages to the Toolbar window, which will process or forward messges to itself and the edit box.
We will not work through the steps in creating the shell for our toolbar.
So back in the Framework section we said that we would need three window classes. One for the Edit Box, one for the toolbar, and one for message reflection back to the toolbar. Let us now create these window classes.
We need to create a derived class from the standard EDIT button window class because we are going to be adding methods to our class to help support functionality of the toolbar. This is one reason why we cannot use a CContainedWindow object directly.
We need to create a derived class from the standard TOOLBARCLASSNAME window class because we are going to be adding methods to our class to help support functionality of the toolbar. It will also be the parent for the edit box and the window which the IE host will request from our DeskBand.
We need to create a reflection window. It's just a CWindowImpl window implmented class. We are going to be adding a small bit of functionality just to create the toolbar object and be able to access the toolbar member from our deskband class.
Now that we have our window classes available we can add our functionality for our toolbar to the appropriate window classes. Let us start with the deepest window class and work our way back out.
For the EditQuote implementation, we need to be able to process keystrokes from the user and let the host that created our deskband object know our edit box has focus. To accomplish the first part, we need to look ahead and see that our DeskBand object will be implementing the IInputObject interface. So the host will query for that interface and know that we want to recieve messages and be given the chance to recieve focus. When the host sends our band messages to process they come through the IInputObject::TranslateAccelerator method. Our DeskBand will implement this method and it is best if our edit box, which will process the message, copy the TranslateAcceleratorIO method definition so our deskband can forward the message easily through a logical method call.
STDMETHOD(TranslateAcceleratorIO)(LPMSG lpMsg);
STDMETHODIMP CEditQuote::TranslateAcceleratorIO(LPMSG lpMsg)
{
TranslateMessage(lpMsg);
DispatchMessage(lpMsg);
return S_OK;
}
#include <commctrl.h>
const int WM_GETQUOTE = WM_USER + 1024;
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;
}
The first part of the Edit boxes implementation is finished. Now we need to be able for the edit box to have the deskband notify the host that we have focus or that we don't have focus any longer. To do this we will need to add a method for the deskband to pass us it's address so that we can call a method of the deskband class. These next steps will involve adding code to the CEditQuote class and to our Deskband class implementation.
Open the EditQuote.h file and add a forward reference to the CStockBar class so that we can defined our methods and members in our class header without knowing the implementation details of our deskband class, add the line in bold.
#include <commctrl.h>
const int WM_GETQUOTE = WM_USER + 1024;
class CStockBar;
BEGIN_MSG_MAP(CEditQuote)
COMMAND_CODE_HANDLER(EN_SETFOCUS, OnSetFocus)
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 OnSetFocus(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
void SetBand(CStockBar* pBand);
private:
CStockBar* m_pBand;
#include "stdafx.h"
#include "EditQuote.h"
#include "MotleyFool.h"
#include "StockBar.h"
CEditQuote::CEditQuote()
: m_pBand(NULL)
{
}
void CEditQuote::SetBand(CStockBar* pBand)
{
m_pBand = pBand;
}
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;
}
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;
}
// IStockBar
public:
void FocusChange(BOOL bHaveFocus);
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;
}
}
}
For the implementation of the MFToolbar window, we need to be able to have it do the following things. It must be able to process the WM_GETQUOTE message from the EditQuote window, communicate with the web browser in which the toolbar is located, create the buttons and place the child windows on itself, forward messages to the EditQuote child window and size itself appropriately to the users actions.
So, the first thing we should do since our toolbar is going to contain an instance of CEditQuote is include the header file for the CEditQuote class. We will do this by opening the MFToolbar.h file and inserting the include statement for the CEditQuote class as shown in bold below.
#include <commctrl.h>
#include "EditQuote.h"
CMFToolbar();
virtual ~CMFToolbar();
private:
CEditQuote m_EditWnd;
BEGIN_MSG_MAP(CMFToolbar)
CHAIN_MSG_MAP_MEMBER(m_EditWnd)
END_MSG_MAP()
CMFToolbar();
virtual ~CMFToolbar();
inline CEditQuote& GetEditBox() {return m_EditWnd;};
private:
CEditQuote m_EditWnd;
HIMAGELIST m_hImageList;
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);
#include "stdafx.h"
#include "resource.h"
#include "MFToolbar.h"
CMFToolbar::CMFToolbar()
: m_hImageList(NULL)
{
}
CMFToolbar::~CMFToolbar()
{
ImageList_Destroy(m_hImageList);
if (IsWindow()) DestroyWindow();
}
#define
statement at the top of the source file, but for cleanliness and since we are already including the resource.h file, we will add it to our resource file. Go to the "View" menu and select "Resource Symbols" menu item. Click the "New" button on the Resource Symbols dialog. Then enter a name of "IDM_GETQUOTE" and click OK. Then close the Resource Symbols dialog.
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;
}
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);
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;
}
CMFToolbar();
virtual ~CMFToolbar();
inline CEditQuote& GetEditBox() {return m_EditWnd;};
void SetBrowser(IWebBrowser2* pBrowser);
private:
CEditQuote m_EditWnd;
HIMAGELIST m_hImageList;
IWebBrowser2* m_pBrowser;
CMFToolbar::CMFToolbar()
: m_hImageList(NULL)
, m_pBrowser(NULL)
{
}
CMFToolbar::~CMFToolbar()
{
ImageList_Destroy(m_hImageList);
SetBrowser(NULL);
if (IsWindow()) DestroyWindow();
}
void CMFToolbar::SetBrowser(IWebBrowser2* pBrowser)
{
if (m_pBrowser) m_pBrowser->Release();
m_pBrowser = pBrowser;
if (m_pBrowser) m_pBrowser->AddRef();
}
extern CComModule _Module;
#include <atlcom.h>
#include <atlwin.h>
//
// These are needed for IDeskBand
//
#include <shlguid.h>
#include <shlobj.h>
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);
private:
CEditQuote m_EditWnd;
HIMAGELIST m_hImageList;
IWebBrowser2* m_pBrowser;
void GetQuote();
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);
}
}
#include <shlobj.h>
// needed for IInputObject and _bstr_t
#include <comdef.h>
For the Reflection Window, it's only purpose is to create the toolbar window (which it doesn't need to really do, but by doing so eases message forwarding) and forward messages to it. The reflection window is not visible, it's just a layer added so that message from the toolbar get to the toolbar. If we didn't have this window present, toolbar messages would get sent to the parent window (which we do not control) and we would never get them. This is not good since we need to respond to WM_COMMAND messages from the toolbar. Thus the need for the reflection window. So let's create the toolbar window and the message forwarding for it.
Open the ReflectionWnd.h header file. We will need to include the toolbar header file and add a private member variable to our reflection window to create it and to pass it to the message chain. First things first, add the include statement below so we can use the CMFToolbar class.
#include <commctrl.h>
#include "MFToolbar.h"
CReflectionWnd();
virtual ~CReflectionWnd();
private:
CMFToolbar m_ToolbarWnd;
BEGIN_MSG_MAP(CReflectionWnd)
CHAIN_MSG_MAP_MEMBER(m_ToolbarWnd)
END_MSG_MAP()
CReflectionWnd();
virtual ~CReflectionWnd();
inline CMFToolbar& GetToolBar() { return m_ToolbarWnd;};
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);
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;
}
CReflectionWnd::~CReflectionWnd()
{
if (IsWindow()) DestroyWindow();
}
All that's left is for our deskband to create the toolbar window, have the host use the toolbar window, remove the unused IPersistStream implementation, implement IInputObject (for focus control) and do some code tweaking. So lets wrap this toolbar up. Open the StockBar.h header file. Remove the following lines of code that are in bold since we moved them to our stdafx.h for our other classes to also use.
#include "resource.h" // main symbols
//
// These are needed for IDeskBand
//
#include <shlguid.h>
#include <shlobj.h>
public IPersistStream,
class ATL_NO_VTABLE CStockBar :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CStockBar, &CLSID_StockBar>,
public IDeskBand,
public IObjectWithSite,
public IDispatchImpl<IStockBar, &IID_IStockBar, &LIBID_MOTLEYFOOLLib>
{
COM_INTERFACE_ENTRY(IPersist)
COM_INTERFACE_ENTRY(IPersistStream)
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()
// 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);
#include "resource.h" // main symbols
#include "ReflectionWnd.h"
HWND m_hWnd;
CReflectionWnd m_ReflectWnd;
m_hWnd(NULL),
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);
}
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();
}
STDMETHODIMP CStockBar::GetWindow(HWND* phwnd)
{
HRESULT hr = S_OK;
if (NULL == phwnd)
{
hr = E_INVALIDARG;
}
else
{
*phwnd = m_ReflectWnd.GetToolBar().m_hWnd;
}
return hr;
}
STDMETHODIMP CStockBar::CloseDW(unsigned long dwReserved)
{
ShowDW(FALSE);
return S_OK;
}
STDMETHODIMP CStockBar::ShowDW(BOOL fShow)
{
m_bShow = fShow;
m_ReflectWnd.GetToolBar().ShowWindow(m_bShow ? SW_SHOW : SW_HIDE);
return S_OK;
}
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;
}
To get tabbing and input control to work correctly for any deskband is quite simple. You need but to implement IInputObject. The host will query our deskband to see if this interface is implemented and if it is will call the methods to see if we require input focus and let us also process messages from the user through the host. To do this, open the stockbar.h header file. To the stockbar class declaration add the line below in bold,
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>
{
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()
// IInputObject
public:
STDMETHOD(HasFocusIO)(void);
STDMETHOD(TranslateAcceleratorIO)(LPMSG lpMsg);
STDMETHOD(UIActivateIO)(BOOL fActivate, LPMSG lpMsg);
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;
}
The are only 2 finishing touches to make, One is to fix the context menu 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, and see. While you only need to change one of them, it's nicer if they all match.
Now let's add button support for our toolbar. Update the stockbar.rgs file contents by appending the text below to it's contents.
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'
}
}
}
}
}
}
While this tutorial is long hopefully the explaination was clear. From writing this tutorial it is easy to see that the RBDeskband ATL Object Wizard has some room for improvement but provided enough of a base for us to develop our simple example. In the end you can see that the toolbar we created is much like the Address bar. The differences lie in how MS implemented theirs versus how I implemented mine. As always feedback is welcome. Enjoy.
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 may to use can be found here