必须实现的接口 垂直浏览栏例子实现了四个必须的接口:IUnknown, IObjectWithSite, IPersistStream, 和IDeskBand,它们都在CExplorerBar类中实现。 IUnknown 构造函数,析构函数和IUnknown实现比较简单,本文在此不讨论。细节请参见源代码。 IObjectWithSite接口 当用户选择某个浏览栏时,容器调用相应band对象的IObjectWithSite::SetSite方法。参数将被设置成这个现场(Site)的IUnknown指针。 通常,SetSite实现应该完成下列步骤:
- 释放当前所把持的任何现场指针。
- 如果传递到SetSite的指针被置为NULL,此则区对象被删除。SetSite可以返回S_OK。
- 如果传递到SetSite的指针被置为非NULL,则建立新的现场。SetSite应该做以下的事情:
- 调用现场QueryInterface方法请求IOleWindow接口。
- 调用IOleWindow::GetWindow获取父窗口句柄,并存储它,以便以后使用。如果不再使用的话,就释放IOleWindow接口。
- 创建此band对象的窗口为一个子窗口,其父窗口就是上一步获得的那个窗口。注意在此不能将它创建成可见窗口。
- 如果此band对象实现IInputObject,调用现场QueryInterface方法请求IInputObjectSite接口,存储这个接口的指针以备后用。
- 如果所有步骤都成功,则返回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编程技术。虽然本文所讨论的例子只提供了有限的功能,但它示范了区对象全部的特性,并且可以在此基础上进行扩充来创建独特和功能强大的的用户界面。 |