Jans的BLOG
Jans的BLOG

 ATL问题集(前31个)

  这些问题是以前在csdn当版主是一些朋友整理的,今天找到了,贴到这里来!

#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,并把这个定义包含到驱动程序或应用程序中。

wangweixing2000于070424

posted on 2007-09-11 14:26  Jans  阅读(2753)  评论(0编辑  收藏  举报