ATL問題集

http://hi.baidu.com/tiantcx/item/9ec4b4cd53422914b77a2433

#1 如何使用控件不能改變大小?

答:有時我們需要創建不可改變大小的控件,像那種在運行時沒有界面的控件(例:時間控件,SysInfo 等),想做到這種功能的話,請把以下代碼加入到控件類的構造函數:

m_bAutoSize = TRUE;
SIZEL size = {24, 24};
AtlPixelToHiMetric(&size, &m_sizeExtent);
m_sizeNatural = m_sizeExtent;

#2.如何在運行時顯示屬性頁?

答:在CComControlBase::DoVerbProperties() 中會自動調用ISpecifyPropertyPages::GetPages(),::OleCreatePropertyFrame() 且創建與顯示OLE屬性頁,只要從你的控件中簡單調用DoVerbProperties()顯示,如何下代碼:

HRESULT STDMETHODCALLTYPE PopMeUp(void)
{
return DoVerbProperties(NULL, ::GetActiveWindow() );
}

#3.如何在運行時新增加屬性頁?

答:覆蓋ISpecifyPropertyPagesImpl::GetPages()來增加你的新屬性頁,或刪除它們,改變它們等到。以下代碼演示在已存在的屬性表中加入新的屬性頁:

HRESULT STDMETHODCALLTYPE GetPages(CAUUID *pPages)
{
if(SUCCEEDED(ISpecifyPropertyPages_GetPages(pPages,NULL))
{
pPages->cElems += 1;
pPages->pElems =
(GUID *)::CoTaskMemAlloc(pPages->cElems * sizeof(CLSID));
pPages->pElems[pPages->cElems - 1] = CLSID_General;
}
else
return E_FAIL;
}

#4 如何註冊控件?

這是一個很常見的問題,最簡單用Winodws自帶的Regsvr32或其它工具等,其原理是利用控件的RegsiterServer函數與UnregsiterServer來實現註冊與取消註冊,以下是代碼實現註冊:

DWORD RegisterServer( char* szPath )
{
   HINSTANCE hInstance = ::LoadLibrary( szPath );
   if ( 0 == hInstance )
   {
      return ::GetLastError();
   }
   typedef void (FAR PASCAL *REGSERVER)(void);
   REGSERVER RegServer = (REGSERVER) ::GetProcAddress( hInstance, _T( "DllRegisterServer" ));
   if ( 0 == RegServer )
   {
      ::FreeLibrary( hInstance );
      return ::GetLastError();
   }
   RegServer();
   ::FreeLibrary( hInstance );
}

#5 我如何使用手工來控制大小?

答:你只要重載IOleObject接口的SetExtent方法.

// NoteCtl.h : Declaration of the CNoteCtl
...
class ATL_NO_VTABLE CNoteCtl :
...
   STDMETHOD(SetExtent)(DWORD dwDrawAspect, SIZEL *psizel)
   {
      ATLTRACE(_T("SetExtent sizing control to 1000x1000 "));
      psizel->cx = psizel->cy = 1000;
      return IOleObjectImpl<CNoteCtl>::SetExtent(dwDrawAspect, psizel);
   }
...
};

#6 我如何重新設置控件的大小?

void CMyCtrl::SetNewSize (int cx, int cy)
{
   SIZEL szlPixels, szlMetric;
   szlPixels.cx = cx;
   szlPixels.cy = cy;
   AtlPixelToHiMetric(&szlPixels, &szlMetric);
   // IOleObjectImpl
   SetExtent(DVASPECT_CONTENT, &szlMetric);
   // update control sizing...
   m_rcPos.right= m_rcPos.left + cx;
   m_rcPos.bottom= m_rcPos.top + cy;
   if (m_spInPlaceSite != NULL) {
      // needed for IE to accept the resizing
      m_spInPlaceSite->OnPosRectChange(&m_rcPos);
   }
   SetFocus();

#7 如何取得當前容器是在設計狀態?

答:ATL提供了CComControlBase::GetAmbientUserMode()來取得其狀態.

BOOL IsUserMode()
{
BOOL bUserMode = TRUE;
HRESULT hRet = GetAmbientUserMode(bUserMode);
if (FAILED(hRet) || bUserMode)
{
return TRUE;
}
return FALSE;
}

#8 如何使某些只能在運行時修改?

答:COleControl提供了兩個方法來輔助實現:AmbientUserModeGetNotSupported,AmbientUserMode()來取得當前容器的狀態,是在運行時還是設計時;而GetNotSupported()能產生CTL_E_GETNOTSUPPORTED自動化異常.

HRESULT CNoteCtl::get_RuntimeOnly( long* pTest )
{
   BOOL bUserMode;
   GetAmbientUserMode( bUserMode );
   if (! bUserMode )
      return CTL_E_GETNOTSUPPORTED;
   *pTest = 100;
   return S_OK;
}

#9 如何做一個簡單的控件容器?

MFC控件嚮導支持簡單框架控件,ATL 2.1不支持,以下代碼演示在ATL 3.0(VC6環境)中實現簡單的ISimpleFrameSite的容器框架.

1.定義兩個宏(主要是爲了方便)

#define RELEASE_OBJECT( ptr )if (ptr) { IUnknown *pUnk = (ptr); (ptr) = NULL; pUnk->Release(); }#define QUICK_RELEASE(ptr) if (ptr) ((IUnknown *)ptr)->Release();

2.在控件類中加入成員變量:

ISimpleFrameSite* m_pSimpleFrameSite;

3.在控件類的構造函數中加入:

m_pSimpleFrameSite = NULL;

4.在控件類的析構函數中加入:

QUICK_RELEASE(m_pSimpleFrameSite);

5.覆蓋IOleObject::SetClientSite:

STDMETHOD(SetClientSite)(IOleClientSite *pClientSite){   HRESULT hr = IOleObjectImpl<你的控件類>::SetClientSite(pClientSite);    RELEASE_OBJECT(m_pSimpleFrameSite);   if( pClientSite != NULL )      pClientSite->QueryInterface( IID_ISimpleFrameSite,                                   (void **)&m_pSimpleFrameSite);    return hr;}

6.在控件類中加入成員變量:

WNDPROC m_fnOldWindowProc;

7.覆蓋Create函數:

HWND Create( HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL,             DWORD dwStyle = WS_CHILD | WS_VISIBLE, DWORD dwExStyle = 0, UINT nID = 0 ){   HWND hWnd = CWindowImpl<你的控件類>::Create( hWndParent, rcPos,                                                szWindowName, dwStyle, dwExStyle, nID);   if (hWnd)   {      ::SetProp(hWnd, "ABC", static_cast<HANDLE> ((你的控件類*) this));      m_fnOldWindowProc = (WNDPROC) ::SetWindowLong( hWnd, GWL_WNDPROC, (LONG) SimpleFrameWindowProc);   }   return hWnd;}

8.在你的控件類中加入定義:

static LRESULT CALLBACK SimpleFrameWindowProc( HWND hWnd, UINT uMsg,                                                WPARAM wParam, LPARAM lParam );

9.加入實理代碼:

LRESULT CALLBACK 你的控件類::SimpleFrameWindowProc( HWND hWnd, UINT uMsg,                                                    WPARAM wParam, LPARAM lParam ){   你的控件類* pThis = static_cast<你的控件類*> (::GetProp(hWnd, "ABC"));   WNDPROC fnOldWindowProc = pThis->m_fnOldWindowProc;    LRESULT lResult;   BOOL bProcess = TRUE;   DWORD dwCookie;   HRESULT hr = E_FAIL;    if(pThis->m_pSimpleFrameSite)   {      hr = pThis->m_pSimpleFrameSite->PreMessageFilter(hWnd, uMsg, wParam,                                             lParam, &lResult, &dwCookie);      bProcess = (hr != S_FALSE);   }    if (bProcess)      lResult = fnOldWindowProc(hWnd, uMsg, wParam, lParam);    if(pThis->m_pSimpleFrameSite && bProcess)   {      pThis->m_pSimpleFrameSite->PostMessageFilter( hWnd, uMsg, wParam, lParam,                                                    &lResult, dwCookie);   }    return lResult;}

10.在.RGS文件中的MiscStatus中新增加OR 0x10000

'MiscStatus' = s '0'{   '1' = s '131473'}

to:

'MiscStatus' = s '0'{   '1' = s '197009'}

#10 如何在ATL控件中使用Dialog資源?

答:這兒是Microsoft的Mark Davis的回答:

1.使用ATL對象嚮導新增加對話框資源(例如:CMyDialog)。
2.編輯Dialog。
3.在你的控件類中加入內部成員變量(例如:CMyDialog m_dlg)。
4.在你的控件中映射消息WM_CREATE,在消息處理函數裏創建Dialog(例如:m_dlg.Create(m_hWnd))

有時你的處理一些標準的Windows窗口的問題,像WM_SIZE等,根據你的情況來作相應的處理。

#11 如何在我的控件加入AboutBox?

答:1.在接口中加入新的方法,並在接口文件(.idl)中改變dispid爲DISPID_ABOUTBOX。
2.產生Dialog資源,並設置ID爲IDD_ABOUTBOX。
3.在你的控件中加入以下代碼: 
class CAboutDlg : public CDialogImpl<CAboutDlg>
{
public:
   enum { IDD = IDD_ABOUTBOX };
   BEGIN_MSG_MAP(CAboutDlg)
      COMMAND_ID_HANDLER(IDOK, OnOK)
   END_MSG_MAP()
   HRESULT OnOK(WORD, WORD, HWND, BOOL&)
   {
      EndDialog(0);
      return 0;
   }
};

4.在你當才加的新方法中加入實現代碼,例如:
CAboutDlg dlg;
dlg.DoModal();

#12 如何處理控件的滾動條?

在你的Active X控件中加入滾動條需要在你的控件類的構造函數中把窗口m_bWindowOnly標誌設置爲TRUE,你也需要映射與處理消息WM_CREATE,並在處理函數中在窗口類型中加入WS_HSCROLL與WS_VSCROLL類型,如以下代碼:

LRESULT OnCreate(UINT nMsg, WPARAM wParam,
    LPARAM lParam, BOOL& bHandled)
{
DWORD dwStyle = GetWindowLong(GWL_STYLE);
dwStyle |= WS_VSCROLL | WS_HSCROLL;
SetWindowLong(GWL_STYLE, dwStyle);
return 0L;
}

映射與處理消息WM_HSCROLL與WM_VSCROLL,並並覆蓋TranslateAccelerator()來加入鍵盤支持:

STDMETHOD(TranslateAccelerator)(MSG *pMsg)
{
switch(pMsg->wParam)
{
case VK_UP:
{
::SendMessage(m_hWnd, WM_VSCROLL,
    SB_LINEUP, MAKELONG(0,m_hWnd));
break;
}
case VK_DOWN:
{
::SendMessage(m_hWnd, WM_VSCROLL,
    SB_LINEDOWN, MAKELONG(0,m_hWnd));
break;
}
//以上面相似:
// case VK_LEFT:
// case VK_RIGHT:
// case VK_PRIOR:
// case VK_NEXT:
}
return S_FALSE;

#13 如何使我的控件對IE來說是安全的?

要使控件對IE來說是安全的話,則必需實現IObjectSafety接口,ATL提供了IObjectSafetyImpl包裝類,以下代碼是演示這個功能,加精是新增加的:

class ATL_NO_VTABLE CNoteCtl :
   public CComObjectRootEx<CComSingleThreadModel>,
   ...
   // Derive from IObjectSafety
public IObjectSafety
{
...
BEGIN_COM_MAP(CNoteCtl)
   COM_INTERFACE_ENTRY(INoteCtl)
   COM_INTERFACE_ENTRY(IDispatch)
   ...
   // Add it to our interface map
COM_INTERFACE_ENTRY(IObjectSafety)
END_COM_MAP()
   ...
   // IObjectSafety implementation
   STDMETHODIMP GetInterfaceSafetyOptions( REFIID riid, DWORD *pdwSupportedOptions, DWORD *pdwEnabledOptions )
   {
      ATLTRACE(_T("CNoteCtl::GetInterfaceSafetyOptions() "));

      *pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER |
                             INTERFACESAFE_FOR_UNTRUSTED_DATA;
      *pdwEnabledOptions = *pdwSupportedOptions;
      return S_OK;
   }
   STDMETHODIMP SetInterfaceSafetyOptions(REFIID riid, DWORD dwOptionSetMask, DWORD dwEnabledOptions)
   {
      ATLTRACE(_T("CNoteCtl::SetInterfaceSafetyOptions "));
      return S_OK;
   }
...
};

#14 如何在控件中使用字體?

在ATL 2.x開始支持內置字體屬性,首先,處理這個屬性不像MFC那麼簡單;第二,你需要在你的控件的IDL文件中加入字體屬性的聲明(其實在VC6的ATL嚮導中支持這些屬性了,你在嚮導中選上的話,嚮導自動會在idl文件中加入相關聲明)

ATL並沒有完全實現內置字體屬性,它提供了內部成員變量指向IFontDisp接口,可是你仍然需要進行OLE字體的初始化,以下代碼是演示:

在你的控件類的構造函數中加入以下代碼:

CMyCtl(){   static FONTDESC _fontDesc =     { sizeof(FONTDESC), OLESTR("MS Sans Serif"),       FONTSIZE( 12 ), FW_BOLD,        ANSI_CHARSET, FALSE, FALSE, FALSE };   OleCreateFontIndirect( &_fontDesc,IID_IFontDisp,(void **)&m_pFont );}

在你需要使用的地方使用以下代碼,一般是在控件的OnDraw方法中,如下:

//取得字體CComQIPtr<IFont, &IID_IFont> pFont( m_pFont使用它...   if ( hOldFont )      SelectObject( hdc, hOldFont );} );if ( pFont ){   HFONT hOldFont = 0;   HFONT hFont;   pFont->get_hFont( &hFont );   hOldFont = (HFONT) SelectObject( hdc, hFont );   //

一般在VC6的ATL嚮導中選擇了Font字體屬性的話,嚮導會在IDL文件中自動產生以下代碼,沒有的話手工加入以下聲明(加粗部分):

#include <olectl.h>import "oaidl.idl";[       uuid(E63A22F1-9BD3-11D0-A6D7-0000837E3100),        version(1.0), helpstring("NoteIt 1.0 Type Library")]library NOTEITLib{   importlib("stdole32.tlb");   importlib("stdole2.tlb");   // Interface is now inside the library block   [      object,      uuid(E63A2306-9BD3-11D0-A6D7-0000837E3100),      dual,      helpstring("INoteCtl Interface"),      pointer_default(unique)   ]   interface INoteCtl : IDispatch   {      ...      [propputref, id(DISPID_FONT)]      HRESULT Font([in]IFontDisp* pFont);      [propput, id(DISPID_FONT)]      HRESULT Font([in]IFontDisp* pFont);      [propget, id(DISPID_FONT)]      HRESULT Font([out, retval]IFontDisp** ppFont);      ...   };...}

#15 在COM/ATL中如何處理錯誤?

基於Windows的組件都有支持ISupportErrorInof接口,它允許將組件的錯誤信息返回給客戶端,在VC5以後提供了本地的支持,如下:

_com_error( HRESULT hr, IErrorInfo* perrinfo = NULL ) throw( );

_com_error( const _com_error& that ) throw( );

這個函數檢查IErrorInfo接口指針是否存在,如果存在將拋出_com_error異常對象,你只要捕獲這個_com_error異常對象就要以了,以下是示例代碼:

STDMETHODIMP CMessageHandler::NewMessage(BSTR inMessage, BSTR inTo,
                                         BSTR inFrom, BSTR inReply)
{
    HRESULT hr = S_OK;

    try
{

    ......

    if(FAILED(hr))
        _com_error(hr);
    }
    catch (_com_error& e) {
        hr = Error((BSTR)e.Description(), e.HelpContext(), e.HelpFile(),e.GUID(), e.Error());
        ATLTRACE("com error: %d - %s ", e.Error(), (const char*)e.Description());
    }
    return hr;
}

至於返回錯誤信息到客戶端,請參閱我的《COM的錯誤處理》(也在文檔中心)。

#16 如何自定義控件的Verbs?

Microsoft標準文檔定義了OLE對象從容器中響應消息,在一個對象容器或客戶端鏈接到對象,通常是調動IOleObject::DoVerb()來響應用戶或容器的消息,你可以通過雙擊對象或點擊鼠標右鍵的上下文菜單來提供的選擇來操作,容器對象裝入上下文菜單是通過調用IOleObject::EnumVerbs().

典型的服務對象或控件是在IOleObject::EnumVerbs()的實現中調用OleRegEnumVerbs() ,ATL默認實現了這些功能,但你必須按照以下步驟:

1.首先添加菜單項到.RGS文件中,verb關鍵字存儲在註冊,如下:

HKEY_LOCAL_MACHINESOFTWAREClassesCLSIDVerb
      1 = <verb1>
      2 = <verb2>
      3 =

以下是verb的格式:

Verb_Number = <Verb_String, Menu_Flag, Verb_Flag>

Verb_Number是個枚舉類型,Verb_String是有效的字符串,像"屬性",Menu_Flag描述如何調用::AppendMenu,Verb_Flag是OLEVERBATTRIB枚舉類型的值之一,如下:

OLEVERBATTRIB_NEVERDIRTIES       = 1,
OLEVERBATTRIB_ONCONTAINERMENU    = 2

所以請修改你的.RGS文件,如下:

   NoRemove CLSID
   {
      ForceRemove {E14A8DEA-8C72-11D1-891C-00C04FA3FB11} = s 'X Class'
      {
         ProgID = s 'X.X.1'
         VersionIndependentProgID = s 'X.X'
         ForceRemove 'Programmable'
...
         'verb'
         {
            '1' = s '&Play,0,2'
'2' = s '&Transpose,0,2'
'3' = s '&Detune,0,2'
'4' = s '&Properties,0,2'
         }
...
      }
   }

當容器檢測到作過在對象上的verb操作將調用IOleObject::DoVerb(),在ATL,你需要覆蓋IOleObjectImpl::DoVerb(),如下:

STDMETHOD(DoVerb)(LONG iVerb,
LPMSG lpmsg,
IOleClientSite *pActiveSite,
LONG lindex,  
HWND hwndParent,
LPCRECT lprcPosRect)
{
if (iVerb == 1)//The verb number mentioned in the .rgs file
   {
       //Do whatever you want
   }
else if(iVerb == 2)
{
}
?
?
return IOleObjectImpl<ClassName>::DoVerb(iVerb, lpmsg,
    pActiveSite, lindex, hwndParent, lprcPosRect);
}

#17 ATL裏設置默認屬性、默認方法?

對於屬性只要在.IDL文件中將其ID設爲0就行了。如:

[propget, id(0), helpstring("property test")] HRESULT test([out, retval] short *pVal);

同理對於方法也生效。

#18 如何使某個參數可選擇?

HRESULT MyFunc([in]BSTR szName,[in, optional] VARIANT Param1, [out, optional] VARIANT Param2)你在MyFunc程序中得檢查Param1.vt是否爲VT_EMPTY,如果是,用戶未使用該參數。

#19 如何使用自定義結構和枚舉類型?

在你的IDL文件中加入如下相似的代碼:

typedef struct _Point2D

{

double x;

double y;

} Point2D;

HRESULT GetPos([out,retval]Point2D* pvar);

typedef enum tagFontAlign
{
[helpstring("Left")]Left=0,
[helpstring("Center")]Center=1,
[helpstring("Right")]Right=2,
}FontAlign;
[propget, id(2), helpstring("對齊方式")] HRESULT Align([out, retval] FontAlign *pVal);
[propput, id(2), helpstring("對齊方式")] HRESULT Align([in] FontAlign newVal);
在接下來的接口定義中添加屬性Align時,屬性的數據類型就填FontAlign,其它操作照常。編譯完以後,你就應該在VB Project中的Object Browser中看到有這麼一個枚舉類型。在控件屬性中選中Align時,就會有個Combo Box讓你選擇FontAlign中的一個值。 

加上一句,如果用的是atl7

那就這麼用:在.h中也可以用,只要在接口聲明的.h中包含它的.h即可!

[export]

enum wwx

{

a=0,

b=1,

c=3

};其他用法一樣

[export]

struct Point2D

{

double x;

double y;

};

#20 OLE_COLOR與COLORREF的有區別嗎?

OLE_COLOR與COLORREF之間是有一定區別的:OLE_COLOR和COLORREF都是DWORD類型,但對於COLORREF來說,它的最高一個字節永遠是0x00。即如果是紅色,對於COLORREF來說是0x000000FF。而OLE_COLOR的最高一個字節有兩種情況:0x80(也就是10000000,最高位是1)或0x00(也就是00000000,最高位是0)。當OLE_COLOR的最高位是0時,它與COLORREF是相同的,最後三個字節代表RGB,可以相互賦值。例如紅色用OLE_COLOR來表示同樣是0x000000FF。但當OLE_COLOR的最高位是1時,它的中間兩個字節一定都是0x00,最後一個字節表示的是系統顏色索引值。例如系統定義菜單的顏色索引值是4,所以用OLE_COLOR來表示就是0x80000004。在VB中,如果你選中一個FORM,在它的屬性頁中你可以看到它的BackColor屬性,你點擊下拉框,就可以選擇是使用調色板色還是系統色,調色板色就是對應了OLE_COLOR的高位爲0的情況,系統色對應的是OLE_COLOR高位爲1的情況。你試一下就知道是怎麼回事了,詳細請參看:MSDN/Platform SDK/Component Services/COM/Controls and Property Pages/Functions/OleTranslateColor的Remarks。

OLE_COLOR與COLORREF的轉達換處理:在MFC中可有OLEControl::TranslateColor()來轉達換,在ATL或其它地方可調用API:OleTranslateColor()來進行從OLE_COLOR到COLORREF的轉換。返過來可用如下方法:OLE_COLOR ocConverted = (OLE_COLOR)clrBack;
同樣,VARIANT_BOOL和BOOL之間也有區別:BOOL爲long,在BOOL中,TURE爲1,FALSE爲0。VAIRNAT_BOOL爲short,在VARIANT_BOOL中,VARIANT_TRUE爲-1(0xFFFF),VARIANT_FALSE爲0(0x0000)。並且VARIANT_BOOL是和VB中的Boolean相同的,就像BSTR和String的關係一樣。所以,在自動化組件及控件中應該使用VARIANT_BOOL。

#21 如何讓我的控件輸出數組?

參閱如下代碼:

void CMyControl::GetArray( VARIANT FAR* pVariant ){    //商業代碼   int nCount = GetCount();   //定義維數   SAFEARRAYBOUND saBound[1];   //定義數組指針對性   SAFEARRAY* pSA;   saBound[0].cElements = nCount;   saBound[0].lLbound = 0;   //創建數組   pSA = SafeArrayCreate( VT_BSTR, 1, saBound );   for( long i = 0; i < nCount; i++ )   {      BSTR bstr;      //商業代碼      bstr = GetItem( i ).AllocSysString(); //給數組賦值      SafeArrayPutElement( pSA, &i, bstr );      ::SysFreeString( bstr );   }   // 初始化傳遞的參數   VariantInit( pVariant );   //設置返值的類型爲數組   pVariant->vt = VT_ARRAY | VT_BSTR;   pVariant->parray = pSA;}

Visual Basic 代碼:

    Dim t As Variant    Dim i as Integer    MyControl1.GetArray t    For i = 0 To MyControl1.Count - 1        ListBox.AddItem t( i )    Next i

#22 如何取得控件的HWND?

     HWND CMyOcx::GetApplicationWindow()
      {
         HWND hwnd = NULL;
         HRESULT hr;
         //*****這段代碼在VC++ v4.1工作
         if (m_pInPlaceSite != NULL)
             {
             m_pInPlaceSite->GetWindow(&hwnd);
             return hwnd;
             }
         //****** 這段代碼在Visual Basic工作
         LPOLECLIENTSITE pOleClientSite = GetClientSite();
         if ( pOleClientSite )
          {
             IOleWindow* pOleWindow;
             hr = pOleClientSite->QueryInterface( IID_IOleWindow, (LPVOID*)
                    &pOleWindow );
             if ( pOleWindow )
              {
                 pOleWindow->GetWindow( &hwnd );
                 pOleWindow->Release();
                 return hwnd;
              }
          }
         return NULL;
     }

#23 爲什麼AmbientUserMode總是返回TRUE?

答:如果你在控件類的構造函數,析構函數,OnSetClientSite方法中使用AmbientUserMode()會總是返回TRUE,因爲控件還未設置ambient IDispatch連接點到容器,下面演示在OnSetClientSite()中取得其值:

void CYourCtrl::OnSetClientSite()
{
if ( m_ambientDispDriver.m_lpDispatch && AmbientUserMode() )
    RecreateControlWindow();//商業代碼
}

m_ambientDispDriver變量是用於維護COleControl的ambient的自動化接口,只有它m_lpDispatch有效時纔會返回這個屬性值。

#24 如何在控件中控制鍵盤?

用ATL開發控件經常需要在一個活動的Form(VB的表單)中處理鍵盤,如果ActiveX控件容器中包含了其它子窗口或窗口控制需要對鍵盤進行控制的話,那麼你需要在控件中實現幾個方法,具有UI界面的控件總是會調用IOleInPlaceActiveObject::TranslateAccelerator()IOleControl::GetControlInfo(),你可能需要覆蓋IOleControl::OnMnemonics()與正確處理Windows的鍵盤消息,而不管是個容器還是在用戶模式。

下面演示在ATL開發Active X控件中在子窗口中處理鍵盤消息。

1.UI激活:當控件初激活時才能收到鍵盤消息,以下代碼演示如何在ATL控件中處理WM_MOUSEACTIVEATE消息來激活UI,它使用了IsUserMode()方法中使用CComControlBase::InPlaceActivate(OLEIVERB_UIACTIVATE)來完成UI激活,這後控件就可以接收鍵盤消息了。

LRESULT OnMouseActivate(UINT uMsg,
    WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if(IsUserMode())
{
InPlaceActivate(OLEIVERB_UIACTIVATE);
}
return 0;
}

2.設置子窗口焦點:

LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&
bHandled)
{
CComControl<CJazzControl>::OnSetFocus (uMsg,
    wParam, lParam, bHandled);
if (IsUserMode())
{
InPlaceActivate (OLEIVERB_UIACTIVATE);
m_ChildControl.SetFocus();
}
return 0;
}

3.實現IOleInPlaceActiveObject::TranslateAccelerator().ATL提供了IOleInPlaceActiveObject接口的包裝類IOleInPlaceActiveObjectImpl,ATL 2.1默認實現IOleInPlaceActiveObjectImpl::TranslateAccelerator()返回E_NOTIMPL,你需要覆蓋它:

STDMETHOD(TranslateAccelerator)(MSG *pMsg)
{
if (
((pMsg->message >= WM_KEYFIRST) &&
    (pMsg->message <= WM_KEYLAST))
&&
((pMsg->wParam == VK_TAB) ||
    (pMsg->wParam == VK_RETURN))
)
{
CComQIPtr<IOleControlSite,&IID_IOleControlSite>
spCtrlSite(m_spClientSite);
if(spCtrlSite)
{
return spCtrlSite->TranslateAccelerator(pMsg, 0);
}
}
return S_FALSE;
}

上述的代碼是在子窗口的編輯框中處理TAB與ENTER鍵,如果你需要處理UP ARROW, DOWN ARROW, PAGE UP, and PAGE DOWN,可如下示例:

if((pMsg->wParam == VK_UP) ||
    (pMsg->wParam == VK_DOWN)||
   (pMsg->wParam == VK_LEFT) ||
       (pMsg->wParam == VK_RIGHT))
{
::IsDialogMessage(m_hWnd, pMsg);
return S_OK;
}

如果Active X控件有滾動條,你需要處理VK_UP與VK_DOWN,如下示例:

if (pMsg->wParam == VK_UP)
{
::SendMessage(m_hWnd,WM_VSCROLL,
   SB_LINEUP,MAKELONG(0,m_hWnd));
return S_FALSE;
}

默認按鈕的處理:當用戶按下ENTER,你應該允許焦點轉移到默認的按鈕上(如果一個按鈕設置爲“默認”),那麼你需要實現IOleControl::GetControlInfo()來接受ENTER與ESC鍵,ATL默認實現IOleControlImpl::GetControlInfo() 返回E_NOTIMPL,你需要覆蓋它:

HRESULT STDMETHODCALLTYPE GetControlInfo(CONTROLINFO *pCI)
{
if(!pCI)
{
return E_POINTER;
}
pCI->hAccel = NULL; //load your accelerators here, if any  
pCI->cAccel = 0;   
pCI->dwFlags = 0;
return S_OK;
}

#25 如何持續化參數屬性?

在正常狀態下支持二進制與文本的持續化,控件分別需要實現IPersistStreamInit與IPersistPropertyBag接口,ATL提供了該接口的包裝類IPersistStreamInitImpl與IPersistPropertyBagImpl,在裝入與存儲屬性中,這兩個類分別調用了CComControlBase::IPersistStreamInit_Load()/Save()與CComControlBase::IPersistPropertyBag_Load()/Save() ,且調用CComDispatchDriver::GetProperty(),在這裏面又調用了invoke()來指定特殊的屬性值,然後CComDispatchDriver::GetProperty()只實現支持單個屬性值,籤於此點,ATL 2.1的屬性持續化機制不支持索引屬性。

要突破這個限制,得在你的控件中覆蓋持續化路徑,並依照標準來實現文本與二進制的持續化,作爲替代,你的屬性應象這樣定義:

[propget, id(4), helpstring("Indexed Property")] HRESULT ParamProp(
[in] short nIndex, [out, retval] short *pVal);
[propput, id(4), helpstring("Indexed Property ")] HRESULT ParamProp(
[in] short nIndex, [in] short newVal);

爲了支持IPersistStreamInit,你需要覆蓋CComControlBase::IPersistStreamInit_Save():

HRESULT IPersistStreamInit_Save(LPSTREAM pStm,
BOOL fClearDirty ,
ATL_PROPMAP_ENTRY* pMap
)
{
if(!pStm)
{
return E_POINTER;
}
for(UINT nIndex = 0; nIndex < 12; nIndex++)
{
if(FAILED(pStm->Write(&(m_nColor[nIndex]),
    sizeof(m_nColor[nIndex]), NULL))
{
return E_UNEXPECTED;
}
}
//調用默認的基類來實現存儲單屬性值PROP_MAP
return CComControlBase::IPersistStreamInit_Save(pStm, fClearDirty, pMap);
}

如果你要實現IPersistPropertyBag接口,你得覆蓋了Load()與Save()方法.

#26 在ATL發行版本中出錯信息:“unresolved external symbol _main”

答:這是VC6的一個BUG,由於VC6在ATL使用_ATL_MIN_CRT_宏,該宏會使CRT啓動代碼無效,去掉該宏就可以了,如下做法:Project->Setting->C/C++ 的Category中選擇Preprocessor的Preprocessor definitions:中去掉_ATL_MIN_CRT_。

#27 如何在ATL中取得windowsless窗口的HWND?

答:windowsless 就是沒有窗口。你的ATL控件沒有窗口, m_hWnd不是NULL能是什麼。至於Ondraw得到的 hdc 實際是父窗口的hdc。huhu 你注意看 M$ 的form 系列控件(就是IE頁面中的那些textbox checkbox ....), 都是windwosless的。
if (m_bWndLess)
{
HDC hDC;
HWND hWnd;
// Get the HDC from the client
m_spInPlaceSite->GetDC(NULL, OLEDC_NODRAW, &hDC);
// Get the HWND from the HDC
hWnd = WindowFromDC(hDC);
m_spInPlaceSite->ReleaseDC(hDC);
}
注意:不要亂動那個hWnd因爲這個東西不是你的。

#28 如何在客戶端中使用CoCreateInstanceEx()?

答:stdafx.h的最前面加入#define _WIN32_DCOM

#29 爲何在Visual C++ Compoents中找不到ATL proxy Generator組件?

答:這是VC5爲支持Connection Point的做法,VC6已整合到Wizard裏面。具體位置:選擇編譯你的項目,然後直接在你的類中擊鼠標右鍵選擇Implement Connection Point,後面的界面與VC5的一模一樣。

#30 在ATL中如何使用IPicture接口顯示圖片?

一下描述一種最簡單的在 ALT 中使用 IPicture 來 顯示圖片的實例。控件的屬性頁可以選擇圖片, 選好後控件的背景就變成該圖片
1.建立一個ALT的project,加入ALT對象選 controls選 full controls (也可以選別的)Next選Stock properties將Picture 加入 supported //這樣, 會爲控件生成一個picture屬性,以及一個預製的 picture 屬性對話框,方便選擇圖片。OK//m_pPicture 是一個 IPictureDisp.
//由於M$的一個BUG 導致 build時 有三個warning 先不要管它, 後面會有解決辦法
2.修改 HRESULT OnDraw(ATL_DRAWINFO& di)如下
HRESULT OnDraw(ATL_DRAWINFO& di)
{
RECT& rc = *(RECT*)di.prcBounds;
Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom);
/////////////////////////////////////draw our picture
LPPICTURE pPict ;
DWORD dwAttr ;
OLE_XSIZE_HIMETRIC cxSrc;
OLE_YSIZE_HIMETRIC cySrc;
if ((m_pPicture != NULL) &&SUCCEEDED(m_pPicture->QueryInterface(IID_IPicture, (LPVOID*)&pPict)))
{
pPict->get_Attributes(&dwAttr);
if(dwAttr==S_OK)
{
pPict->get_Width(&cxSrc);
pPict->get_Height(&cySrc);
pPict->Render(di.hdcDraw,rc.left, rc.top, rc.right, rc.bottom,0,0,cxSrc,cySrc,&rc);
}
}
///////////////////////////////////////finished draw
SetTextAlign(di.hdcDraw, TA_CENTER¦TA_BASELINE);
LPCTSTR pszText = _T("ATL 3.0 : catest");
TextOut(di.hdcDraw,
(rc.left + rc.right) / 2,
(rc.top + rc.bottom) / 2,
pszText,
lstrlen(pszText));
return S_OK;
}

#31 什麼是GUID?

GUID用於標識軟件接口,它與COM(部件對象模型)中用於標識COM接口的標識符相同,它還用於OSF(開放軟件基金)的DCE(分佈式計算環境)中,標識RPC(遠程過程調用)目標。如果你想了解GUID如何生成以及爲什麼能在統計意義上唯一,請參考Kraig Brockschmidt的《Inside OLE, Second Edition (Microsoft Press, 1995)》第66頁,原始算法規範由OSF制定,相關部分見http://www.opengroup.org/onlinepubs/9629399/apdxa.htm

爲了在設備驅動程序中使用GUID,首先需要使用UUIDGEN或者GUIDGEN生成GUID,然後把結果放到頭文件中。GUIDGEN更易於使用,它允許選擇GUID的輸出格式,並把結果送到剪貼板。圖2-18顯示了GUIDGEN的運行窗口。你可以把它的輸出粘貼到頭文件中:

// {CAF53C68-A94C-11D2-BB4A-00C04FA330A6}
DEFINE_GUID(<<name>>, 0xCAF53C68, 0xA94C, 0x11D2, 0xBB, 0x4A, 0x00, 0xC0, 0x4F, 0xA3, 0x30, 0xA6);

然後,用有意義的名字換掉<<name>>,如GUID_SIMPLE,並把這個定義包含到驅動程序或應用程序中。

發佈了16 篇原創文章 · 獲贊 5 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章