必須實現的接口 垂直瀏覽欄例子實現了四個必須的接口: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編程技術。雖然本文所討論的例子只提供了有限的功能,但它示范了區對象全部的特性,并且可以在此基礎上進行擴充來創建獨特和功能強大的的用戶界面。 |