65.9K
CodeProject 正在变化。 阅读更多。
Home

Internet Explorer 工具栏(Deskband)教程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (120投票s)

2001年8月22日

CPOL

27分钟阅读

viewsIcon

3584343

downloadIcon

22816

关于使用 RBDeskband 和 CWindowImpl ATL 对象向导创建 Internet Explorer (IE) 工具栏的教程。

The Motley Fool Quote IE Toolbar

引言

在收到许多关于使用我创建的 RBDeskband 和 CWindowImpl 向导开发 Internet Explorer 工具栏的教程请求后,我创建了一个简单的示例工具栏,可以用作开发自己的工具栏或资源管理器栏的参考。本教程将引导您完成为 IE 开发工具栏的各个阶段,该工具栏与 IE 中已有的地址栏非常相似。我想做一个教程,提供一个真实的示例,并产生一个在教程完成后其他人可以使用的最终结果。因此,本教程将向您展示如何开发一个 IE 工具栏,以从 The Motley Fool 网站获取股票行情信息。那么,让我们开始吧。

先决条件

本教程假设您已经知道如何用 C++ 编程,并了解一些关于 ATL 和 COM 的信息。要完成本教程,您需要在开发机器上安装以下软件:

  • 已安装 Visual C++6
  • RBDeskBand ATL 对象向导 (版本 2.0) [在此获取]
  • CWindowImpl ATL 对象向导 [在此获取]

框架

IE 工具栏由支持 IDeskband 的 COM 组件和 IE 在加载已注册工具栏、资源管理器栏和桌面栏时所需的其他一些接口组成。RBDeskband ATL 对象向导提供了本文的大部分框架。我们需要做的是创建我们的项目,一个新的 COM 对象来容纳我们的工具栏,以及使用 CWindowImpl ATL 对象向导创建一些 CWindowImpl 类。然后将这些部分连接起来,我们将生成文章顶部图片中的 IE 工具栏。从视觉上看,工具栏由一个编辑框和一个带有一个按钮的工具栏组成。实际上,工具栏由上述部分和一个不可见的窗口组成,该窗口用于将消息反射到工具栏窗口,工具栏窗口将处理或将消息转发给自己和编辑框。

创建外壳

我们不会通过步骤来创建工具栏的外壳。

创建项目

  • 如果您尚未这样做,请启动 Visual C++6。
  • 然后,从“文件”菜单中选择“新建”菜单项;“新建”对话框将弹出。
  • 在“新建”对话框中,如果尚未选择,请选择“项目”选项卡。
  • 如果尚未选择,请从列表视图中选择“ATL COM AppWizard”。
  • 在“项目名称”中,键入“MotleyFool”。参见图 1。
  • 单击“确定”按钮。
Figure 1. New Dialog.
图 1。“新建”对话框。
  • ATL COM AppWizard 将启动。
  • 单击“完成”按钮,接受默认的 AppWizard 属性。参见图 2。
  • “新建项目信息”对话框将显示,请求确认您的项目设置。
  • 单击“确定”按钮。
Figure 2. ATL COM AppWizard
图 2。ATL COM AppWizard。

创建 DeskBand 对象

现在我们有了项目容器,我们需要添加我们的 IDeskBand 派生组件,以便 DLL 实际公开一些东西。
  • 从“插入”菜单中,选择“新建 ATL 对象”菜单项;ATL 对象向导对话框将被调用。
  • 在 ATL 对象向导对话框中,选择“RadBytes”类别。如果缺少此类别,请确保已安装 RBDeskband 和 CWindowImpl ATL 对象向导。
  • 接下来从“对象”列表中选择“DeskBand”项。
  • 单击“下一步”按钮,调用 Deskband 对象的 ATL 对象向导属性对话框。参见图 3。
  • 在“名称”属性页上,在“短名称”字段中键入“StockBar”。参见图 4。
  • 选择“DeskBand ATL 对象向导”属性页
  • 选中“Internet Explorer 工具栏”复选框。参见图 5。
  • 单击 ATL 对象向导属性对话框上的“确定”按钮。ATL 对象向导将创建 DeskBand 基本实现所需的文件。
Figure 3. ATL Object Wizard.
图 3。ATL 对象向导。
Figure 4. ATL Object Wizard Properties - Names.
图 4。ATL 对象向导属性 - 名称。
Figure 5. ATL Object Wizard Properties - DeskBand ATL Object Wizard.
图 5。ATL 对象向导属性 - DeskBand ATL 对象向导
现在我们的项目有了 DeskBand 实现,我们将对其进行修改以生成文章顶部所示的工具栏。首先,我们将创建所需的窗口类,然后返回到 Deskband 对象并更新它以使用我们的窗口类。

创建窗口类

回到框架部分,我们说过需要三个窗口类。一个用于编辑框,一个用于工具栏,一个用于将消息反射回工具栏。现在让我们创建这些窗口类。

编辑窗口

我们需要从标准 EDIT 按钮窗口类派生一个类,因为我们将向我们的类添加方法以帮助支持工具栏的功能。这是我们不能直接使用 CContainedWindow 对象的原因之一。

  • 从“插入”菜单中,选择“新建 ATL 对象”菜单项;ATL 对象向导对话框将被调用。
  • 在 ATL 对象向导对话框中,选择“RadBytes”类别。如果缺少此类别,请确保已安装 RBDeskband 和 CWindowImpl ATL 对象向导。
  • 接下来从“对象”列表中选择“CWindowImpl”项。
  • 单击“下一步”按钮,调用 Deskband 对象的 ATL 对象向导属性对话框。参见图 3。
  • 在“名称”属性页上,在“短名称”字段中键入“EditQuote”。
  • 选择“CWindowImpl”属性页。参见图 6。
  • 从 DECLAR_WND_* 组中选择“SUPERCLASS”单选按钮。
  • 在“窗口类名称”字段中,键入“EDITQUOTE”。
  • 在“原始类名称”列表中,选择“EDIT”列表框项。参见图 7。
  • 单击 ATL 对象向导属性对话框上的“确定”按钮。ATL 对象向导将创建 CWindowImpl 派生类实现所需的文件。
Figure 6. ATL Object Wizard Properties - Names.
图 6。ATL 对象向导属性 - 名称。
Figure 7. ATL Object Wizard Properties - Names.
图 7。ATL 对象向导属性 - CWindowImpl。

工具栏窗口

我们需要从标准 TOOLBARCLASSNAME 窗口类派生一个类,因为我们将向我们的类添加方法以帮助支持工具栏的功能。它也将是编辑框的父级,并且是 IE 主机将从我们的 DeskBand 请求的窗口。

  • 从“插入”菜单中,选择“新建 ATL 对象”菜单项;ATL 对象向导对话框将被调用。
  • 在 ATL 对象向导对话框中,选择“RadBytes”类别。如果缺少此类别,请确保已安装 RBDeskband 和 CWindowImpl ATL 对象向导。
  • 接下来从“对象”列表中选择“CWindowImpl”项。
  • 单击“下一步”按钮,调用 Deskband 对象的 ATL 对象向导属性对话框。参见图 3。
  • 在“名称”属性页上,在“短名称”字段中键入“MFToolbar”。
  • 选择“CWindowImpl”属性页。参见图 8。
  • 从 DECLAR_WND_* 组中选择“SUPERCLASS”单选按钮。
  • 在“窗口类名称”字段中,键入“MOTLEYFOOLTOOLBAR”。
  • 在“原始类名称”列表中,选择“TOOLBARCLASSNAME”列表框项。参见图 9。
  • 单击 ATL 对象向导属性对话框上的“确定”按钮。ATL 对象向导将创建 CWindowImpl 派生类实现所需的文件。
Figure 8. ATL Object Wizard Properties - Names.
图 8。ATL 对象向导属性 - 名称。
Figure 9. ATL Object Wizard Properties - Names.
图 9。ATL 对象向导属性 - CWindowImpl。

反射窗口

我们需要创建一个反射窗口。它只是一个 CWindowImpl 窗口实现类。我们将添加一小部分功能,只为创建工具栏对象并能够从我们的桌面带类访问工具栏成员。

  • 从“插入”菜单中,选择“新建 ATL 对象”菜单项;ATL 对象向导对话框将被调用。
  • 在 ATL 对象向导对话框中,选择“RadBytes”类别。如果缺少此类别,请确保已安装 RBDeskband 和 CWindowImpl ATL 对象向导。
  • 接下来从“对象”列表中选择“CWindowImpl”项。
  • 单击“下一步”按钮,调用 Deskband 对象的 ATL 对象向导属性对话框。参见图 3。
  • 在“名称”属性页上,在“短名称”字段中键入“ReflectionWnd”。参见图 10。
  • 这次我们不会更改任何 CWindowImpl 属性页值。
  • 单击 ATL 对象向导属性对话框上的“确定”按钮。ATL 对象向导将创建 CWindowImpl 派生类实现所需的文件。
Figure 10. ATL Object Wizard Properties - Names.
图 10。ATL 对象向导属性 - 名称。

添加详细信息

现在我们有了可用的窗口类,我们可以将工具栏的功能添加到相应的窗口类中。让我们从最深层的窗口类开始,然后向外扩展。

EditQuote 详细信息

对于 EditQuote 实现,我们需要能够处理用户的击键,并让创建我们桌面带对象的主机知道我们的编辑框获得了焦点。为了完成第一部分,我们需要预先查看我们的 DeskBand 对象将实现 IInputObject 接口。因此,主机将查询该接口,并知道我们希望接收消息并有机会获得焦点。当主机向我们的波段发送要处理的消息时,它们通过 IInputObject::TranslateAccelerator 方法传入。我们的 DeskBand 将实现此方法,最好我们的编辑框(它将处理消息)复制 TranslateAcceleratorIO 方法定义,以便我们的桌面带可以通过逻辑方法调用轻松转发消息。

Figure 11. FileView Pane.
图 11。文件视图窗格。
在“文件视图”窗格中(参见图 11),双击“Header Files”下的“EditQuote.h”项。这将打开编辑区域中的头文件。我们现在需要定义 TranslateAcceleratorIO 的方法定义。为此,在 virtual CEditQuote 析构函数下面添加以下代码行
STDMETHOD(TranslateAcceleratorIO)(LPMSG lpMsg);
现在打开 EditQuote.cpp 源文件,并向文件中添加 TranslateAcceleratorIO 的实现
STDMETHODIMP CEditQuote::TranslateAcceleratorIO(LPMSG lpMsg)
{
   TranslateMessage(lpMsg);
   DispatchMessage(lpMsg);
   return S_OK;
}
现在我们的 DeskBand 实现可以调用此消息,编辑框将正确处理按键。但是等等,如果按键是回车键,我们的编辑框应该通知工具栏加载输入的行情详情。为此,我们需要定义一个消息 ID 并将该消息发送到父窗口进行处理。在 EditQuote.h 头文件中,在 include 语句下面,添加消息 ID 的定义,如下面粗体所示。
#include <commctrl.h>

const int WM_GETQUOTE = WM_USER + 1024;
			
在 EditQuote.cpp 文件中,我们将向 TranslateAcceleratorIO 方法添加代码以处理回车键。将下面粗体显示的代码添加到 EditQuote.cpp 文件中。
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;
}
现在,当用户按下回车键时,我们的编辑框将通知父级,以便父级可以检索请求的股票代码详情,这部分将在我们到达工具栏详情时实现。

编辑框实现的第一部分已完成。现在,我们需要让编辑框能够让桌面带通知主机我们已获得焦点或不再具有焦点。为此,我们需要添加一个方法,让桌面带将它的地址传递给我们,以便我们可以调用桌面带类的一个方法。接下来的这些步骤将涉及向 CEditQuote 类和我们的 Deskband 类实现添加代码。

打开 EditQuote.h 文件,并添加 CStockBar 类的前向引用,以便我们可以在类的头文件中定义我们的方法和成员,而无需了解桌面带类的实现细节,添加粗体行。

#include <commctrl.h>

const int WM_GETQUOTE = WM_USER + 1024;

class CStockBar;
为了让我们的类通知主机我们的桌面带已获得焦点,我们需要为 EN_SETFOCUS 添加一个消息处理程序。将下面粗体所示的命令代码处理程序代码添加到您的 EditQuote.h 文件中。
   BEGIN_MSG_MAP(CEditQuote)
      COMMAND_CODE_HANDLER(EN_SETFOCUS, OnSetFocus)
   END_MSG_MAP()
然后将 OnSetFocus 的方法定义添加到头文件中,在注释掉的处理程序原型下面,如下面粗体所示。
// 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);
在实现 OnSetFocus 方法之前,我们需要定义一个方法,让我们的桌面带告知它的地址并保留该地址以备后用。将以下代码行添加到您的 EditQuote.h 文件中 TranslateAcceleratorIO 定义下面。
   void SetBand(CStockBar* pBand);
private:
   CStockBar* m_pBand;
现在我们可以转到 EditQuote.cpp 源文件,实现消息处理程序、SetBand 方法,并更新 TranslateAcceleratorIO 方法以进行焦点更改。在 EditQuote.cpp 文件的顶部,将以下 includes 添加到 include 列表中,如下面粗体所示。
#include "stdafx.h"
#include "EditQuote.h"
#include "MotleyFool.h"
#include "StockBar.h"
现在我们可以在代码中使用 CStockBar 类的方法。在构造函数的末尾,添加 m_pBand 的初始化。不要忘记冒号运算符。
CEditQuote::CEditQuote()
: m_pBand(NULL)
{
}
接下来我们将把 SetBand 实现添加到我们的 CEditQuote 类中。请注意,由于它不是 COM 对象,我们不会在其上调用 AddRef 或 Release。它只是指向该类的一个指针,当它被销毁时,我们的 CEditQuote 实例也将被销毁。我们也可以在头文件中以内联方式完成此操作。
void CEditQuote::SetBand(CStockBar* pBand)
{
   m_pBand = pBand;
}
接下来我们需要为 EN_SETFOCUS 消息添加消息处理程序。将以下代码添加到 EditQuote.cpp 源文件的末尾。
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;
}
我们的 CEditQuote 实现还需要添加一个代码段,然后我们就可以转到 CStockBar 类来定义和实现 FocusChange 方法。将以下代码添加到 CEditQuote TranslateAcceleratorIO 方法中,如粗体所示。我们添加此代码是为了让主机知道我们不再需要消息。
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;
}
打开 StockBar.h 头文件,并添加 FocusChange 的定义,如下面粗体所示。
// IStockBar
public:
   void FocusChange(BOOL bHaveFocus);
现在打开 StockBar.cpp 源文件,并在底部添加 FocusChange 的实现。
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;
      }
   }
}
我们已经完成了编辑框在工具栏中正常工作所需的工作。现在我们需要构建我们的工具栏,使其具有一个按钮并包含我们的编辑框。然后我们将为我们的反射窗口添加必要条件,并更新我们的 IDeskBand 以向我们的主机提供正确的信息。我们快到了。如果您编译项目并运行它,它将(除了波段在图 X 中看起来像以下内容)。

MFToolbar 详细信息

对于 MFToolbar 窗口的实现,我们需要它能够完成以下事情。它必须能够处理来自 EditQuote 窗口的 WM_GETQUOTE 消息,与工具栏所在的 Web 浏览器通信,创建按钮并在其上放置子窗口,将消息转发到 EditQuote 子窗口,并根据用户的操作适当地调整自身大小。

因此,我们应该做的第一件事是,因为我们的工具栏将包含 CEditQuote 的一个实例,所以要包含 CEditQuote 类的头文件。我们将通过打开 MFToolbar.h 文件并插入 CEditQuote 类的包含语句来完成此操作,如下面粗体所示。

#include <commctrl.h>
#include "EditQuote.h"
		
接下来,我们需要为 CEditQuote 类向我们的工具栏类添加一个成员。我们将通过在类的末尾添加一个私有部分并定义一个成员变量来完成此操作,如下面粗体所示。
   CMFToolbar();
   virtual ~CMFToolbar();

private:
   CEditQuote m_EditWnd;
现在我们已经为我们的 EditQuote 窗口定义了成员,我们需要将窗口消息转发给它,以便正确处理键盘输入。我们通过更新工具栏消息映射来将消息链式发送到我们的成员,如下面粗体所示。
   BEGIN_MSG_MAP(CMFToolbar)
      CHAIN_MSG_MAP_MEMBER(m_EditWnd)
   END_MSG_MAP()
展望未来,我们的桌面带将需要获取 EditQuote 成员以确定它是否具有焦点并使其发挥作用。我们可以通过将其设为公共成员而不是私有成员来直接公开 EditQuote 成员,但通过将其设为私有成员,我们可以公开一个方法来公开我们的成员,从而使我们以后在需要时可以灵活地修改类。因此,为了公开 EditQuote 成员,我们将在我们的工具栏类中添加一个函数以返回对 EditQuote 成员的引用。在工具栏头文件中,添加如下面粗体所示的方法定义和实现。
   CMFToolbar();
   virtual ~CMFToolbar();
   inline CEditQuote& GetEditBox() {return m_EditWnd;};
现在我们将创建我们的工具栏窗口。我们的工具栏由 EditQuote 框和一个带图标和文本的按钮组成。为了容纳图标,我们的工具栏将需要一个图像列表句柄发送到工具栏窗口。因此,在我们开始实现工具栏的创建之前,我们需要在工具栏头文件中添加一些东西。我们要添加的第一件事是图像列表的成员变量。将下面粗体所示的行添加到您的工具栏头文件中。
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);
在实现工具栏的创建之前,我们需要创建一个图标资源,供工具栏按钮在其文本旁边使用。因此,转到资源视图并向项目资源添加一个新图标。您可以通过右键单击“MotleyFool resources”并从上下文菜单中选择“Insert...”来完成此操作。在“Insert Resource”对话框中,从“Resource type”列表中选择“Icon”,然后单击“New”按钮。这将向您的项目插入一个空白图标资源。通过右键单击资源视图中的图标资源并从上下文菜单中选择“properties”菜单项来重命名图标的资源 ID。将 ID 更改为 IDI_MOTLEY。然后绘制或慷慨地从 The Motley Fool 借用一个图标以用于工具栏。我慷慨地从他们的网站借用了该图标并将其改编成图标。

现在我们可以实现工具栏的创建了。打开 MFToolbar 源文件并按照以下说明实现工具栏创建的详细信息。

首先,我们需要包含项目资源文件,以便在代码中使用图标 ID。将下面粗体所示的行添加到我们的工具栏源文件中,如所示。
#include "stdafx.h"
#include "resource.h"
#include "MFToolbar.h"
接下来,我们需要更新构造函数的实现。我们需要通过将其设置为 NULL 来初始化图像列表的句柄。不要忘记冒号。
CMFToolbar::CMFToolbar()
: m_hImageList(NULL)
{
}
接下来我们需要更新析构函数,它应该销毁图像列表并销毁窗口(如果尚未销毁)。
CMFToolbar::~CMFToolbar()
{
   ImageList_Destroy(m_hImageList);
   if (IsWindow()) DestroyWindow();
}
在实现工具栏的创建之前,我们需要为工具栏按钮的 ID 向项目添加一个资源符号。我们可以直接在源文件顶部使用 `#define` 语句,但为了整洁起见,并且由于我们已经包含了 resource.h 文件,我们将把它添加到我们的资源文件中。转到“视图”菜单并选择“资源符号”菜单项。单击“资源符号”对话框上的“新建”按钮。然后输入名称“IDM_GETQUOTE”并单击“确定”。然后关闭“资源符号”对话框。

现在我们可以创建工具栏了,我们已经在头文件中定义了 OnCreate 方法,现在需要实现它。将以下函数及其实现添加到工具栏源文件的末尾。
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;
}
如果您此时尝试编译,您会看到图像列表方法调用存在未解析的外部符号。我们需要向项目添加一个库。为此,请选择“项目|设置”菜单项。在“项目设置”对话框中,从“用于设置”组合框中选择“所有配置”。然后选择“链接”选项卡,并在“对象/库模块”编辑框中附加“comctl32.lib”。然后单击“确定”。如果您现在编译项目,它将成功编译,并且图像列表未解析的外部符号将消失。

我们仍然需要对工具栏窗口做一些事情。它需要处理命令消息,响应 WM_GETQUOTE 消息,并调整自身大小。让我们首先解决后者。

为了正确组织工具栏,我们应该让工具栏响应 WM_SIZE 消息。为此,我们将在工具栏头文件中添加一个 WM_SIZE 消息处理程序,并为 OnSize 添加一个函数定义,WM_SIZE 消息将发送到该函数。打开我们的工具栏头文件,并向其中添加下面粗体所示的行。
   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);
现在我们需要实现 OnSize 函数。打开工具栏源文件,并将下面的函数实现添加到文件末尾。
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;
}
当用户按下回车键时,我们仍然需要响应工具栏按钮和编辑框的用户输入。我们希望工具栏做的是告诉 Web 浏览器主机导航到 Motley Fool 网站并检索请求的股票行情。首先,我们需要 Deskband 对象告诉我们的工具栏窗口 Web 浏览器实例是什么,以便工具栏窗口可以与之通信。为此,我们将添加一个私有成员变量和一个公共方法,Deskband 可以在其中设置 Web 浏览器实例。然后我们的窗口将使用设置的成员变量来告诉 Web 浏览器导航到哪里以及检索什么。

为此,打开工具栏头文件,并添加粗体行到文件中。
   CMFToolbar();
   virtual ~CMFToolbar();
   inline CEditQuote& GetEditBox() {return m_EditWnd;};
   void SetBrowser(IWebBrowser2* pBrowser);

private:
   CEditQuote m_EditWnd;
   HIMAGELIST m_hImageList;
   IWebBrowser2* m_pBrowser;
现在,打开工具栏源文件。我们将更新构造函数并初始化我们的成员变量为 null。然后我们将更新工具栏析构函数并释放成员变量(如果它尚未释放)。然后我们将实现 SetBrowser 方法。

初始化 Web 浏览器成员变量。
CMFToolbar::CMFToolbar()
: m_hImageList(NULL)
, m_pBrowser(NULL)
{
}
如果持有,释放 Web 浏览器对象。
CMFToolbar::~CMFToolbar()
{
   ImageList_Destroy(m_hImageList);
   SetBrowser(NULL);
   if (IsWindow()) DestroyWindow();
}
实现 SetBrowser()
void CMFToolbar::SetBrowser(IWebBrowser2* pBrowser)
{
   if (m_pBrowser) m_pBrowser->Release();
   m_pBrowser = pBrowser;
   if (m_pBrowser) m_pBrowser->AddRef();
}
如果您尝试编译项目,您会注意到我们的头文件中未定义 IWebBrowser2。这是因为我们需要更新我们的 stdafx.h 头文件以包含定义 IWebBrowser2 的系统文件。为此,打开 stdafx.h 并将以下粗体行添加到文件中,然后重新编译。
extern CComModule _Module;
#include <atlcom.h>
#include <atlwin.h>

//
// These are needed for IDeskBand
//

#include <shlguid.h>
#include <shlobj.h>
现在我们可以为我们的工具栏类添加 WM_COMMAND 和 WM_GETQUOTE 的消息处理程序,以处理工具栏按钮被按下和用户在编辑框中按下回车键。为此,我们需要在我们的工具栏头文件中添加 WM_COMMAND 和 WM_GETQUOTE 的消息处理程序和函数定义。我们还需要添加一个私有方法,如果它们需要执行相同的功能,两者都将调用它(比重复做相同事情的代码更好)。所以让我们将消息处理程序添加到头文件中。
   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);
现在我们可以为 GetQuote 私有方法添加函数声明了。
private:
   CEditQuote m_EditWnd;
   HIMAGELIST m_hImageList;
   IWebBrowser2* m_pBrowser;
   void GetQuote();
现在让我们切换到源文件并实现我们的消息处理函数和 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);
   }
}
如果您尝试编译,您会注意到 _bstr_t 未定义。这是因为该类在 comdef.h 中定义。我们需要将其添加到我们的 stdafx.h 头文件中,以便我们可以在我们的项目中使用它以及任何其他类(IInputObject 需要这样做)。打开 stdafx.h 头文件,并按指示将粗体行添加到文件中。
#include <shlobj.h>

// needed for IInputObject and _bstr_t
#include <comdef.h>
工具栏窗口的实现已完成。现在我们可以转到反射窗口,它创建我们的工具栏并将命令消息转发给它。

反射窗口详细信息

对于反射窗口,其唯一目的是创建工具栏窗口(它并不真正需要这样做,但这样做可以简化消息转发)并将消息转发给它。反射窗口不可见,它只是一个添加的层,以便工具栏的消息能够到达工具栏。如果我们没有这个窗口,工具栏消息将发送到父窗口(我们无法控制),我们将永远无法获取它们。这不好,因为我们需要响应来自工具栏的 WM_COMMAND 消息。因此需要反射窗口。所以让我们创建工具栏窗口并为其进行消息转发。

打开 ReflectionWnd.h 头文件。我们需要包含工具栏头文件,并为我们的反射窗口添加一个私有成员变量,以创建它并将其传递给消息链。首先,添加下面的 include 语句,这样我们就可以使用 CMFToolbar 类了。

#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()
我们还需要一个公共函数供我们的桌面带类访问我们的工具栏窗口。我们将像处理 EditQuote 窗口一样,通过提供一个函数来间接获取成员变量。将下面粗体所示的代码行添加到头文件中,如指示所示。
   CReflectionWnd();
   virtual ~CReflectionWnd();
   inline CMFToolbar& GetToolBar() { return m_ToolbarWnd;};
最后,我们需要创建工具栏窗口,并将在我们的反射窗口的 WM_CREATE 消息处理程序中这样做。将下面粗体所示的代码添加到反射窗口头文件中。然后我们将在源文件中实现 OnCreate 方法。
   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);
现在打开 ReflectionWnd.cpp 源文件,并在其末尾添加 OnCreate 的实现。
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();
}

完成桌面带工具栏

剩下的一切就是让我们的桌面带创建工具栏窗口,让主机使用工具栏窗口,移除未使用的 IPersistStream 实现,实现 IInputObject(用于焦点控制),并进行一些代码调整。所以让我们完成这个工具栏。打开 StockBar.h 头文件。移除以下粗体代码行,因为我们已将它们移到 stdafx.h 中供其他类使用。

#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 Map 并移除以下两行代码,
   COM_INTERFACE_ENTRY(IPersist)
   COM_INTERFACE_ENTRY(IPersistStream)
您的 COM 映射现在应该看起来像这样,
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 和 IPersistStream 的函数声明部分,其中包括以下行。
// 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);
现在在 IDockingWindow 和 IStockBar 函数声明部分之间,应该没有与 IPersist 或 IPersistStream 相关的内容。

现在我们需要从 StockBar.cpp 源文件中删除 IPersist 和 IPersistStream 函数实现。找到 GetClassID、IsDirty、Load、Save 和 GetSizeMax 函数实现,并将它们从文件中删除。它们应该直接位于我们之前添加的 FocusChange 方法上方。

现在我们可以开始向我们的桌面带添加代码了。打开 stockbar.h 头文件,并添加反射窗口类的 include,如下面粗体所示。
#include "resource.h"       // main symbols
#include "ReflectionWnd.h"
接下来,找到文件末尾的 protected 部分,并将行替换为
   HWND m_hWnd;
   CReflectionWnd m_ReflectWnd;
如果您尝试编译,您会发现它不会成功,因为我们已经删除了 m_hWnd 并且没有从类源文件中删除所有出现的 m_hWnd。我们将用更适合我们的反射窗口和工具栏窗口的代码替换所有出现的 m_hWnd。打开 StockBar.cpp 源文件,从类构造函数中删除以下行
   m_hWnd(NULL),
您的类构造函数现在应该如下所示,并添加了 SetBand 调用:
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);
}
接下来,通过用反射窗口构造替换临时 m_hWnd 构造来更新 RegisterAndCreateWindow 函数。您的 RegisterAndCreateWindow 实现应如下所示:
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();
}
当我们浏览代码时,我们应该修复一些其他问题。工具栏的默认高度为 22,所以我们需要更新 GetBandInfo 方法,以便所有 y 测量值都是 22 而不是 20。我们还希望我们的 Integral 尺寸不可调整,所以我们需要将 DBIM_INTEGRAL 的 x 和 y 值设置为 0。在此期间,我们应该修复工具栏的标题,使其显示为“The Motley Fool”。您会在源文件顶部附近找到此常量定义,现在更新它。

我们现在将更新 GetWindow 调用,以便返回工具栏窗口句柄,而不是无效的 m_hWnd 变量。更新您的 GetWindow 方法,使其如下所示:
STDMETHODIMP CStockBar::GetWindow(HWND* phwnd)
{
   HRESULT hr = S_OK;
   if (NULL == phwnd)
   {
      hr = E_INVALIDARG;
   }
   else
   {
      *phwnd = m_ReflectWnd.GetToolBar().m_hWnd;
   }
   return hr;
}
现在我们需要更新 CloseDW 方法,使其不再销毁我们的窗口,而是隐藏它。我们这样做很像 MFC CToolbar 类,类析构函数将销毁窗口。您的 CloseDW 方法应如下所示:
STDMETHODIMP CStockBar::CloseDW(unsigned long dwReserved)
{
   ShowDW(FALSE);
   return S_OK;
}
在我们的类实现中,我们将更新下一个需要更新的方法。更新 ShowDW 类以显示或隐藏主机正在使用的工具栏窗口。您的 ShowDW 类应如下所示:
STDMETHODIMP CStockBar::ShowDW(BOOL fShow)
{
   m_bShow = fShow;
   m_ReflectWnd.GetToolBar().ShowWindow(m_bShow ? SW_SHOW : SW_HIDE);
   return S_OK;
}
我们几乎完成了对 CStockBar 类的修改,我们还需要进行最后一次更新。我们需要修改 SetSite 实现,以便在拥有 IInputObjectSite 对象时释放工具栏可能正在使用的浏览器窗口,并且我们需要查询 OleCommandTarget 的 ServiceProvider 以获取 IWebBrowser2 接口,然后将其设置到我们的工具栏。SetSite 方法实现的修改部分如下粗体所示。
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;
}
如果您现在尝试编译和使用工具栏,它将部分功能。制表和输入控制将无法正常工作,因为我们尚未为我们的桌面带实现 IInputObject。现在让我们来做,因为这是我们为简单桌面带编写的最后一段代码。您可能还会注意到工具栏的上下文菜单和“视图|工具栏”菜单仍然显示“CStockBar 类”。我们将在下面的“收尾工作”部分解决这个问题。

IInputObject 实现

让任何桌面带的制表和输入控制正常工作非常简单。您只需实现 IInputObject。主机将查询我们的桌面带以查看是否实现了此接口,如果实现,将调用方法以查看我们是否需要输入焦点,并让我们还可以通过主机处理来自用户的消息。为此,打开 stockbar.h 头文件。在 stockbar 类声明中添加以下粗体行,

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>
{
接下来向下滚动到 COM Map 并添加 IInputObject 的条目,如下面粗体所示,
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()
接下来将以下函数声明部分添加到您的头文件中,我将其放在 IStockBar 部分之前。
// IInputObject
public:
   STDMETHOD(HasFocusIO)(void);
   STDMETHOD(TranslateAcceleratorIO)(LPMSG lpMsg);
   STDMETHOD(UIActivateIO)(BOOL fActivate, LPMSG lpMsg);
剩下的就是实现这三个函数。将下面的函数实现添加到 stockbar.cpp 源文件的末尾。
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;
}
我们的工具栏功能上已完成,编译、运行并查看。它按描述工作,并且相当简单。让我们为 IE 和我们的用户添加一些最终的 UI 润饰。

收尾工作

只有 2 个收尾工作,一个是修复上下文菜单文本。另一个是为主 IE 工具栏添加按钮支持。让我们按顺序完成它们。

要修复上下文菜单文本问题,请打开 StockBar.rgs 项目文件,并将所有出现的“StockBar Class”更改为“The Motley Fool Quotes”。编译它,运行它,然后查看。虽然您只需要更改其中一个,但如果它们都匹配会更好看。

现在让我们为工具栏添加按钮支持。通过将以下文本附加到 stockbar.rgs 文件的内容来更新其内容。

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'
               }
            }
         }
      }
   }
}
然后将 425 替换为 resource.h 中 IDI_MOTLEY 的 ID。还要将 BandClsid 值替换为我们工具栏的 GUID,上述值表示文章的源代码。现在再次编译工具栏。然后启动 IE 并右键单击“标准按钮”工具栏,从上下文菜单中选择“自定义”。向下滚动“可用工具栏按钮”列表框,找到“The Motley Fool”项,参见图 12。选择它并单击对话框中间的“添加”按钮。该项将向右移动,如图 13 所示。单击“关闭”按钮。您将看到添加的按钮,如图 14 之前和图 15 之后所示。
Figure 12. Customize Toolbar - Available Toolbar Buttons.
图 12。自定义工具栏 - 可用工具栏按钮。
Figure 13. Customize Toolbar - Current Toolbar Buttons.
图 13。自定义工具栏 - 当前工具栏按钮。
Figure 14. IE Standard Buttons - Before.
图 14。IE 标准按钮 - 之前。
Figure 15. IE Standard Buttons - After.
图 15。IE 标准按钮 - 之后。

结论

虽然本教程很长,但希望解释清晰。从编写本教程可以看出,RBDeskband ATL 对象向导还有一些改进空间,但它为我们开发我们的简单示例提供了足够的基础。最后,您可以看到我们创建的工具栏与地址栏非常相似。区别在于微软如何实现他们的工具栏,以及我如何实现我的工具栏。一如既往,欢迎反馈。尽情享受吧。

© . All rights reserved.