旅途

如果想飞得高,就该把地平线忘掉

Windows区对象(Bands)的创建与定制

 

Windows区对象(Bands)的创建与定制
编译/赵湘宁

本文例子代码

1 简介
  1.1 浏览栏区对象
  1.2 工具栏区对象

  1.3 桌面区对象

2 实现区对象
  2.1 注册

3 一个简单的例子
  3.1 DLL函数
  3.2 注册定制的浏览栏
  3.3 必须实现的接口
    3.3.1 IUnknown
    3.3.2 IObjectWithSite
    3.3.3 IPersistStream
    3.3.4 IDeskBand
  3.4 可选择的接口实现
    3.4.1 IInputObject
  3.5 窗口过程
4 总结

一、 简介
Windows的区(Bands)对象有三种:既浏览栏(Explorer Bar)区对象,工具栏(Tools Bands)区对象,和桌面区对象(Desk Bands)。

浏览栏区对象
浏览栏区对象简称浏览栏,它是从IE4.0引入的,它是邻近浏览器窗格的一个显示区域。实际上它是IE窗口中的一个子窗口,可以用它来显示信息及与用户交互。浏览栏即可以是以垂直方式定位在浏览器窗格的左边。也可以水平方式定位在浏览器窗格下面。(如图一)

图一
在浏览栏中可以创建很多子菜单或选项,用户能以不同方式选择这些子菜单或选项提供的功能,打开IE或者资源管理器,从“查看”菜单中选择“浏览栏”,可以看到Windows提供了几种标准的浏览栏菜单,如“搜索(Search)”,“收藏夹(Favorites)”, 和“历史记录(History)”,以及“文件夹(All Folders)”。(如图二)

图二
为了创建定制的浏览栏,必须编程实现,然后注册它们。Windows在外壳(Shell)4.71中引入了区对象。它提供与普通窗口一样的功能。但因为它是以IE或外壳为容器的COM对象,所以实现起来就与普通窗口有所不同。图一中显示的就是一个简单的浏览栏例子。图中有一个垂直的浏览栏和一个水平的浏览栏。

工具栏区对象
工具栏区对象简称工具栏,它是在IE5.0中引入用以支持单选工具栏(radio toolbar)特性的。IE工具栏实际上是一个Rebar控件,它包含了几个工具栏(toolbar)控件。通过创建工具栏,你可以将某个区对象功能添加到Rebar控件中。不论是在IE中还是在资源管理器中,区对象都是一样的,所以工具栏也是一个通用窗口。(如图三)

图三

用户可以从“查看”菜单中的“工具栏”子菜单中选择显示单选工具栏,也可以在工具栏区域单击鼠标右键从它的上下文菜单中选择显示单选工具栏。

桌面区对象
区对象也可以用在桌面,也就是创建桌面区对象。虽然它们的基本实现与浏览栏类似,但桌面区与IE没有关系,它不用IE作为容器。它主要用来创建桌面浮动窗口。通过在任务栏上单击右键,然后在弹出的菜单中选择“工具栏”的子菜单选项。(如图四)

图四

桌面区的初始浮动位置在任务栏:(如图五

图五

用户可以将桌面区拖到桌面上,这时它就成了一个普通窗口:(如图六)

图六
二、实现区对象
尽管可以像使用普通窗口一样使用区对象,但它们毕竟是COM对象,存在于某个容器之中。如浏览栏和工具栏位于IE之中,桌面区位于外壳之中。虽然它们的功能不同,但其基本实现非常相似。一个主要的差别是它们的注册方式不同,而注册方式的不同又决定了对象的类型及其容器。这一部分我们先讨论所有区对象实现的共性。其它的实现细节可参考垂直浏览栏例子程序
区对象除了要实现 IUnknown 和 IClassFactory 两个接口之外,所有的区对象还必须实现以下这几个接口:
  •  IDeskBand 
  •  IObjectWithSite 
  •  IPersistStream
另外,在注册时除了注册它们的CLSID之外,浏览栏和桌面区对象还必须进行组件类别(category)的注册。它决定了对象的类型及其容器。工具栏不需要进行种类注册。归纳起来,需要进行CATID注册的三种区对象是:
区对象类型 组件类型
垂直浏览栏 CATID_InfoBand
水平浏览栏 CATID_CommBand
桌面区 CATID_DeskBand

对于如何注册区对象的进一步讨论请参见注册部分
如果某个区对象接受用户输入,它还必须实现IInputObject接口。如果要往上下文菜单中添加菜单项目,还必须实现IContextMenu接口。注意:工具栏区对象不支持上下文菜单。
    因为区对象实现的是子窗口,所以它们还必须有窗口过程来处理Windows的消息。
    区对象可以通过其IOleCommandTarget接口发送命令到它的容器。为了得到这个接口的指针,必须调用容器的IInputObjectSite::QueryInterface方法请求IID_IoleCommandTarget。然后用IOleCommandTarget::Exec把命令发送到容器。命令组是CGID_DeskBand。当某个区对象的IDeskBand::GetBandInfo方法被调用时,容器用dwBandID参数将一个标示符赋给这个对象。这个标示符被用于IOleCommandTarget::Exec方法调用时所用命令组中的三个命令。目前命令组共支持四个IOleCommandTarget::Exec命令IDs。这四个命令的解释如下:
DBID_BANDINFOCHANGED——Band的信息已改变。参数pvaIn的值应该是最近一次调用所用的band标示符。容器将调用这个标示符所指的band对象的IDeskBand::GetBandInfo方法请求更新的信息。
DBID_MAXIMIZEBAND——容器将最大化band。参数pvaIn的值应该是最近一次调用所用的band标示符。
DBID_SHOWONLY——关闭或打开容器中其它band。参数pvaIn的值为VT_UNKNOWN类型,可以取下列值之一:
描述
pUnk 这个对象IUnknown接口的指针。所有其它的桌面band将被隐藏。
0 隐藏所有桌面band。
1 显示所有桌面band。
DBID_PUSHCHEVRON——目前没有实现。

注册
区对象必须作为进程内服务器(in-process)注册。其线程模型必须为“Apartment”。也就是说区对象必须以DLL的形式来实现。用来描述服务器注册条目的缺省值是一个菜单文本串。就拿浏览栏来说。这个菜单出现在资源管理器或IE “查看(View)”菜单的“浏览栏(Explorer Bar)”子菜单中。而工具栏的菜单则出现在资源管理器或IE “查看(View)”菜单的“工具栏(Toolbars)”子菜单中。桌面区出现在任务栏上下文菜单的“工具栏(Toolbars)”子菜单中。作为菜单资源,提供键盘快捷的方法与一般菜单快捷键相同。也就是将“&”字符放在某个单词字母前表示这个字母显示下划线来指示快捷键。
通常区对象的注册条目如下:
HKEY_CLASSES_ROOT
                        ...
                        CLSID
                        ...
                        {Band 对象的 CLSID GUID} = "菜单文本串"
                        InProcServer32 = "DLL 路径名"
                        ThreadingModel = "Apartment"
工具栏区对象必须还要注册对象的CLSID。为此必须在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar下创建一个REG_SZ值,用工具栏区对象的CLSID GUID串命名。如:
HKEY_LOCAL_MACHINE
                        Software
                        Microsoft
                        Internet Explorer
                        Toolbar
                        { Band 对象的 CLSID GUID }

除此之外,还有几个可选的注册值可以加到注册表中,本文的例子中未使用这些值。
  • HKEY_CLASSES_ROOT\CLSID\{Band 对象的 CLSID GUID}\Instance\CLSID, 它应该被设置为 "{4D5C8C2A-D075-11D0-B416-00C04FB90376}". 
  • HKEY_CLASSES_ROOT\CLSID\{Band对象的CLSID GUID}\Instance\InitPropertyBag\Url 它应该被设置为要在浏览栏显示的包含HTML内容的文件位置。
  • \HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Explorer Bars\{Band 对象的 CLSID GUID}\BarSize 它应该被设置为栏目的高和宽,它需要八个字节才能作为串放入注册表,字节之间用逗号分开。开始的四个字节一像素为单位指定大小,格式要用十六进制,从最左边字节开始。最后四个字节是保留字节,应该将它置为零。例如,垂直浏览栏的缺省宽度为291(0x123)像素,则BarSize 的值应该是"23,01,00,00,00,00,00,00" 
如果要用浏览栏显示HTML,则前两个注册项是必须的。最后一个注册项则根据垂直的或者水平的浏览栏定义相应的缺省宽度和高度。
能显示HTML的浏览栏(缺省宽度为291各像素单位)注册表条目的形式如下:
HKEY_CLASSES_ROOT
                        ...
                        CLSID
                        ...
                        {Band 对象的 CLSID GUID} = "菜单文本串"
                        InProcServer32 = "DLL 路径名"
                        ThreadingModel = "Apartment"
                        Instance
                        CLSID = "{4D5C8C2A-D075-11D0-B416-00C04FB90376}"
                        InitPropertyBag
                        Url = "HTML文件"
                        ...
                        HKEY_CURRENT_USER
                        ...
                        Software
                        ...
                        Microsoft
                        ...
                        Internet Explorer
                        ...
                        Explorer Bars
                        { Band 对象的 CLSID GUID }
                        BarSize = "23,01,00,00,00,00,00,00"

你可以通过编程的方式来处理区对象类别 CATID 的注册。创建一个组件类别管理器对象(CLSID_StdComponentCategoriesMgr)并请求一个指向ICatRegister接口的指针。将区对象的CLSID和CATID传递到ICatRegister::RegisterClassImplCategories。
三、定制浏览栏的一个简单例子
这个例子展示了前面所介绍过的垂直浏览栏的整个实现过程。它借助了平台SDK(Platform SDK——在msdn中可以找到)中关于band对象示范代码。其中还包括了水平浏览栏和桌面band的实现代码。详细实现细节请参见:CommBand.cpp和DeskBand.cpp。
创建定制浏览栏的基本过程是这样的:
  1. 实现DLL需要的函数。
  2. 实现必须的COM接口。
  3. 实现任何想要的可选接口。
  4. 注册对象的CLSID。
  5. 进行恰当的组件种类注册。
  6. 创建IE子窗口,调整窗口大小适合浏览栏的显示区域。
  7. 使用子窗口显示信息并与用户交互。
实际上,只要通过恰当的组件种类注册,浏览栏例子代码便既可用于浏览栏的实现,也能用于桌面band实现。更加复杂的实现将需要定制每种对象类型的显示区域和容器。但大多数的定制工作都能通过范例代码以及Windows子窗口的编程技术来完成。例如,你可以添加用户交互控制或者进行色彩丰富的图形显示处理。

DLL函数
所有三种区对象被打包在一个DLL中,它输出以下的函数:
  • DllMain
  • DllCanUnloadNow 
  • DllGetClassObject 
  • DllRegisterServer 
这些函数可以在BandObjs.cpp中找到,它们服务于所有三种区对象。前三个函数乃标准的实现,我们不再本文中讨论。类工厂也是标准实现,代码可以在ClsFact.cpp中找到

注册定制的浏览栏

有了COM对象后,必须对浏览栏的CLSID进行注册。另外如果要与IE或资源管理器
协调运行,还必须进行的恰当的组件种类(CATID_InfoBand)注册。这个工作由DllRegisterServer处理。浏览栏例子代码有关的处理部分如下:
...
                        //注册浏览栏对象
                        if(!RegisterServer(CLSID_SampleExplorerBar, TEXT("垂直浏览栏例子")))
                        return SELFREG_E_CLASS;
                        //注册浏览栏的对象组件种类
                        if(!RegisterComCat(CLSID_SampleExplorerBar, CATID_InfoBand))
                        return SELFREG_E_CLASS;
                        ...
区对象的注册使用通常的COM过程,它由私有函数RegisterServer处理。
除了CLSID之外,这个区对象服务器还必须注册一个以上的组件种类。这实际上是垂直浏览栏和水平浏览栏实现之间的主要差别。这个过程的处理是通过创建一个组件种类管理器对象(CLSID_StdComponentCategoriesMgr),并用ICatRegister::RegisterClassImplCategories方法来注册区对象服务器。在这个例子中,组件种类注册的处理是通过将浏览栏的CLSID和CATID传递到私有函数RegisterComCat完成的:
BOOL RegisterComCat(CLSID clsid, CATID CatID)
                        {
                        ICatRegister   *pcr;
                        HRESULT        hr = S_OK ;
                        CoInitialize(NULL);
                        hr = CoCreateInstance(  CLSID_StdComponentCategoriesMgr,
                        NULL,
                        CLSCTX_INPROC_SERVER,
                        IID_ICatRegister,
                        (LPVOID*)&pcr);
                        if(SUCCEEDED(hr))
                        {
                        hr = pcr->RegisterClassImplCategories(clsid, 1, &CatID);
                        pcr->Release();
                        }
                        CoUninitialize();
                        return SUCCEEDED(hr);
                        }

必须实现的接口
垂直浏览栏例子实现了四个必须的接口:IUnknown, IObjectWithSite, IPersistStream, 和IDeskBand,它们都在CExplorerBar类中实现。
IUnknown
构造函数,析构函数和IUnknown实现比较简单,本文在此不讨论。细节请参见源代码。
IObjectWithSite接口
当用户选择某个浏览栏时,容器调用相应band对象的IObjectWithSite::SetSite方法。参数将被设置成这个现场(Site)的IUnknown指针。
通常,SetSite实现应该完成下列步骤:
  1. 释放当前所把持的任何现场指针。
  2. 如果传递到SetSite的指针被置为NULL,此则区对象被删除。SetSite可以返回S_OK。
  3. 如果传递到SetSite的指针被置为非NULL,则建立新的现场。SetSite应该做以下的事情:
  1. 调用现场QueryInterface方法请求IOleWindow接口。
  2. 调用IOleWindow::GetWindow获取父窗口句柄,并存储它,以便以后使用。如果不再使用的话,就释放IOleWindow接口。
  3. 创建此band对象的窗口为一个子窗口,其父窗口就是上一步获得的那个窗口。注意在此不能将它创建成可见窗口。
  4. 如果此band对象实现IInputObject,调用现场QueryInterface方法请求IInputObjectSite接口,存储这个接口的指针以备后用。
  5. 如果所有步骤都成功,则返回S_OK,否则返回OLE定义的错误代码以指示错误类型。
以下是浏览栏实现SetSite的方法。m_pSite是私有成员变量,用它来保存IInputObjectSite指针,而m_hwndParent保存父窗口句柄。
STDMETHODIMP CExplorerBar::SetSite(IUnknown* punkSite)
                        {
                        //如果某个现场被把持,则释放它
                        if(m_pSite)
                        {
                        m_pSite->Release();
                        m_pSite = NULL;
                        }
                        //如果punkSite 不为NULL, 建立一个新的现场
                        if(punkSite)
                        {
                        //获取父窗口
                        IOleWindow  *pOleWindow;
                        m_hwndParent = NULL;
                        if(SUCCEEDED(punkSite->QueryInterface(IID_IOleWindow, (LPVOID*)&pOleWindow)))
                        {
                        pOleWindow->GetWindow(&m_hwndParent);
                        pOleWindow->Release();
                        }
                        if(!m_hwndParent)
                        return E_FAIL;
                        if(!RegisterAndCreateWindow())
                        return E_FAIL;
                        //获取柄保存IInputObjectSite指针
                        if(SUCCEEDED(punkSite->QueryInterface(IID_IInputObjectSite, (LPVOID*)&m_pSite)))
                        {
                        return S_OK;
                        }
                        return E_FAIL;
                        }
                        return S_OK;
                        }
这个例子的GetSite只简单地用SetSite保存的现场指针实现了对现场QueryInterface方法的调用。
STDMETHODIMP CExplorerBar::GetSite(REFIID riid, LPVOID *ppvReturn)
                        {
                        *ppvReturn = NULL;
                        if(m_pSite)
                        return m_pSite->QueryInterface(riid, ppvReturn);
                        return E_FAIL;
                        }
窗口创建由私有方法RegisterAndCreateWindow负责。如果这个窗口不存在,此方法将浏览栏窗口创建成一个大小适当的子窗口,它的父窗口就是由SetSite获得的那个窗口。子窗口的句柄存储在m_hwnd变量中。
BOOL CExplorerBar::RegisterAndCreateWindow(void)
                        {
                        //如果这个窗口不存在,则创建它
                        if(!m_hWnd)
                        {
                        //子窗口不能没有父窗口
                        if(!m_hwndParent)
                        {
                        return FALSE;
                        }
                        //如果窗口类没有注册,则必须注册
                        WNDCLASS wc;
                        if(!GetClassInfo(g_hInst, EB_CLASS_NAME, &wc))
                        {
                        ZeroMemory(&wc, sizeof(wc));
                        wc.style          = CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
                        wc.lpfnWndProc    = (WNDPROC)WndProc;
                        wc.cbClsExtra     = 0;
                        wc.cbWndExtra     = 0;
                        wc.hInstance      = g_hInst;
                        wc.hIcon          = NULL;
                        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
                        wc.hbrBackground  = (HBRUSH)CreateSolidBrush(RGB(0, 0, 192));
                        wc.lpszMenuName   = NULL;
                        wc.lpszClassName  = EB_CLASS_NAME;
                        if(!RegisterClass(&wc))
                        {
                        //如果注册失败,下面的CreateWindow函数将失败
                        }
                        }
                        RECT  rc;
                        GetClientRect(m_hwndParent, &rc);
                        //创建这个窗口。WndProc 将建立m_hWnd变量
                        CreateWindowEx(   0,
                        EB_CLASS_NAME,
                        NULL,
                        WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER,
                        rc.left,
                        rc.top,
                        rc.right - rc.left,
                        rc.bottom - rc.top,
                        m_hwndParent,
                        NULL,
                        g_hInst,
                        (LPVOID)this);
                        }
                        return (NULL != m_hWnd);
                        }
IPersistStream接口
IE将调用浏览栏的IPersistStream接口,以便允许这个浏览栏加载或存储持久性数据。如果没有持久性数据,这个方法仍然必须返回一个成功代码。IPersistStream接口从IPersist继承而来,所以要实现五个方法:
GetClassID, IsDirty, Load, Save, GetSizeMax。
本文的这个浏览栏例子不使用持久性数据,并且只有IPersistStream的最小实现。GetClassID返回对象的CLSID(CLSID_SampleExplorerBar),其余的方法返回S_OK, 或者S_FALSE, 或者 E_NOTIMPL。有关细节请参见IPersistStream的实现。

IDeskBand接口
IDeskBand接口是区对象专用接口。它只有一个方法。IDeskBand接口从IDockingWindow继承而来,而IDockingWindow又从IOleWindow继承而来。
IOleWindow有两个方法:GetWindow 和 ContextSensitiveHelp。浏览栏例子的GetWindow实现返回浏览栏的子窗口句柄m_hwnd。因为不实现上下文敏感帮助,所以ContextSensitiveHelp返回E_NOTIMPL。
IDockingWindow接口有三个方法:ShowDW, CloseDW, 和 ResizeBorder。ResizeBorder不在任何区对象中使用,应该返回E_NOTIMPL。ShowDW方法根据其不同的参数值控制浏览栏窗口的显示或隐藏:
STDMETHODIMP CExplorerBar::ShowDW(BOOL fShow)
                        {
                        if(m_hWnd)
                        {
                        if(fShow)
                        {
                        //显示窗口
                        ShowWindow(m_hWnd, SW_SHOW);
                        }
                        else
                        {
                        //隐藏窗口
                        ShowWindow(m_hWnd, SW_HIDE);
                        }
                        }
                        return S_OK;
                        }
                        CloseDW方法摧毁浏览栏窗口:
                        STDMETHODIMP CExplorerBar::CloseDW(DWORD dwReserved)
                        {
                        ShowDW(FALSE);
                        if(IsWindow(m_hWnd))
                        DestroyWindow(m_hWnd);
                        m_hWnd = NULL;
                        return S_OK;
                        }
其余的方法,如GetBandInfo是IDeskBand专用的。IE使用它来指定浏览栏的标示符以及视图模式。IE还可能填写DESKBANDINFO结构的dwMask成员从浏览栏请求更多的信息,这个结构用第三个参数传递。GetBandInfo应该存储这个标示符和视图模式并用所请求的数据填写DESKBANDINFO结构。下面是本文浏览栏例子所实现GetBandInfo:
STDMETHODIMP CExplorerBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi)
                        {
                        if(pdbi)
                        {
                        m_dwBandID = dwBandID;
                        m_dwViewMode = dwViewMode;
                        if(pdbi->dwMask & DBIM_MINSIZE)
                        {
                        pdbi->ptMinSize.x = MIN_SIZE_X;
                        pdbi->ptMinSize.y = MIN_SIZE_Y;
                        }
                        if(pdbi->dwMask & DBIM_MAXSIZE)
                        {
                        pdbi->ptMaxSize.x = -1;
                        pdbi->ptMaxSize.y = -1;
                        }
                        if(pdbi->dwMask & DBIM_INTEGRAL)
                        {
                        pdbi->ptIntegral.x = 1;
                        pdbi->ptIntegral.y = 1;
                        }
                        if(pdbi->dwMask & DBIM_ACTUAL)
                        {
                        pdbi->ptActual.x = 0;
                        pdbi->ptActual.y = 0;
                        }
                        if(pdbi->dwMask & DBIM_TITLE)
                        {
                        lstrcpyW(pdbi->wszTitle, L"浏览栏例子");
                        }
                        if(pdbi->dwMask & DBIM_MODEFLAGS)
                        {
                        pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT;
                        }
                        if(pdbi->dwMask & DBIM_BKCOLOR)
                        {
                        //通过移开这个标志来使用默认的背景颜色
                        pdbi->dwMask &= ~DBIM_BKCOLOR;
                        }
                        return S_OK;
                        }
                        return E_INVALIDARG;
                        }
可选择的接口实现
由两个接口的实现是可选择的,一个是IInputObject,另一个是 IContextMenu。本文的浏览栏例子实现了IInputObject。对于IContextMenu的实现细节请参考有关文档。

IInputObject接口
如果某个band对象要接受用户输入。那就必须实现IInputObject接口。IE实现IInputObjectSite并用IInputObject维护用户的输入焦点。浏览栏需要实现三个方法:UIActivateIO, HasFocusIO, 和 TranslateAcceleratorIO。
IE调用UIActivateIO通知浏览栏它以被激活或者被置灰。当被激活时,浏览栏例子调用SetFocus来设置窗口输入焦点。
当要确定哪个窗口有输入焦点时,IE调用HasFocusIO。如果浏览栏的窗口或它的子窗口之一有输入焦点,HasFocusIO返回S_OK。否则,它返回S_FALSE。
TranslateAcceleratorIO允许对象处理键盘加速键。本文浏览栏例子没有实现这个方法,所以它返回S_FALSE。
浏览栏例子实现IInputObjectSite的细节如下:
STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg)
                        {
                        if(fActivate)
                        SetFocus(m_hWnd);
                        return S_OK;
                        }
                        STDMETHODIMP CExplorerBar::HasFocusIO(void)
                        {
                        if(m_bFocus)
                        return S_OK;
                        return S_FALSE;
                        }
                        STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg)
                        {
                        return S_FALSE;
                        }
窗口过程
因为区对象的显示用的是子窗口,所以它必须实现窗口过程来处理Windows消息。浏览栏例子实现了一个最简单的版本,它的窗口过程只处理了五个消息:WM_NCCREATE, WM_PAINT, WM_COMMAND, WM_SETFOCUS, 和 WM_KILLFOCUS。如果要实现更多的功能,很容易扩充使它处理其它的消息。
LRESULT CALLBACK CExplorerBar::WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
                        {
                        CExplorerBar  *pThis = (CExplorerBar*)GetWindowLong(hWnd, GWL_USERDATA);
                        switch (uMessage)
                        {
                        case WM_NCCREATE:
                        {
                        LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
                        pThis = (CExplorerBar*)(lpcs->lpCreateParams);
                        SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);
                        //设置窗口句柄
                        pThis->m_hWnd = hWnd;
                        }
                        break;
                        case WM_PAINT:
                        return pThis->OnPaint();
                        case WM_COMMAND:
                        return pThis->OnCommand(wParam, lParam);
                        case WM_SETFOCUS:
                        return pThis->OnSetFocus();
                        case WM_KILLFOCUS:
                        return pThis->OnKillFocus();
                        }
                        return DefWindowProc(hWnd, uMessage, wParam, lParam);
                        }
这里WM_COMMAND消息处理器简单地返回零。WM_PAINT消息处理器创建文本并显示在资源管理器或IE的区对象中。
LRESULT CExplorerBar::OnPaint(void)
                        {
                        PAINTSTRUCT ps;
                        RECT        rc;
                        BeginPaint(m_hWnd, &ps);
                        GetClientRect(m_hWnd, &rc);
                        SetTextColor(ps.hdc, RGB(255, 255, 255));
                        SetBkMode(ps.hdc, TRANSPARENT);
                        DrawText(ps.hdc, TEXT("浏览栏例子"), -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
                        EndPaint(m_hWnd, &ps);
                        return 0;
                        }
WM_SETFOCUS 和 WM_KILLFOCUS消息处理器通过调用本现场的IInputObjectSite::OnFocusChangeIS方法通知输入焦点现场改变:
LRESULT CExplorerBar::OnSetFocus(void)
                        {
                        FocusChange(TRUE);
                        return 0;
                        }
                        LRESULT CExplorerBar::OnKillFocus(void)
                        {
                        FocusChange(FALSE);
                        return 0;
                        }
                        void CExplorerBar::FocusChange(BOOL bFocus)
                        {
                        m_bFocus = bFocus;
                        //通知焦点已改变的输入对象现场
                        if(m_pSite)
                        {
                        m_pSite->OnFocusChangeIS((IDockingWindow*)this, bFocus);
                        }
                        }
四、总结

区对象提供了灵活和强大的扩展方式,通过定制浏览栏使得IE的功能大为增强。桌面区的实现扩展了普通窗口的能力。尽管需要一些对COM的编程,但终究以子窗口的形式提供了一种用户界面。从而使今后的许多这种编程实现都能用类似的Windows编程技术。虽然本文所讨论的例子只提供了有限的功能,但它示范了区对象全部的特性,并且可以在此基础上进行扩充来创建独特和功能强大的的用户界面。




posted on 2007-07-30 16:20 旅途 阅读(504) 评论(0)  编辑 收藏 引用 所属分类: COM+/DCOM


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理