ATL问题集

使用ATL创建控件过程发现一个问题:

ATL控件自身使用SetWindowPos, MoveWindow改变自身大小。但是一点击鼠标又恢复创建时的大小。经过GOOGLE发现了方案。

下面:#5,#6解决了这个问题。

公布ATL问题集:

#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 to1000x1000\n"));
      psizel->cx = psizel->cy = 1000;
      returnIOleObjectImpl::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 ((你的控件类*)this));


     m_fnOldWindowProc = (WNDPROC) ::SetWindowLong( hWnd, GWL_WNDPROC, (LONG)SimpleFrameWindowProc);


   }


   return hWnd;


}


 


8.在你的控件类中加入定义:


static LRESULT CALLBACKSimpleFrameWindowProc( HWND hWnd, UINT uMsg,


                                              WPARAMwParam, 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资源?


 


答:这儿是MicrosoftMark 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)中改变dispidDISPID_ABOUTBOX
2.
产生Dialog资源,并设置IDIDD_ABOUTBOX
3.
在你的控件中加入以下代码: 
class CAboutDlg : public CDialogImpl
{
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_HSCROLLWS_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_HSCROLLWM_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,
   ...
   // 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, DWORDdwOptionSetMask, DWORD dwEnabledOptions)
   {
     ATLTRACE(_T("CNoteCtl::SetInterfaceSafetyOptions "));
      return S_OK;
   }...
};


 


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


 


ATL 2.x开始支持内置字体属性,首先,处理这个属性不像MFC那么简单;第二,你需要在你的控件的IDL文件中加入字体属性的声明(其实在VC6ATL向导中支持这些属性了,你在向导中选上的话,向导自动会在idl文件中加入相关声明)


 


ATL并没有完全实现内置字体属性,它提供了内部成员变量指向IFontDisp接口,可是你仍然需要进行OLE字体的初始化,以下代码是演示:


 


在你的控件类的构造函数中加入以下代码:


CMyCtl(){   static FONTDESC _fontDesc=     { sizeof(FONTDESC), OLESTR("MS SansSerif"),       FONTSIZE( 12 ),FW_BOLD,        ANSI_CHARSET, FALSE, FALSE,FALSE };  


OleCreateFontIndirect(&_fontDesc,IID_IFontDisp,(void **)&m_pFont );}


 


 


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


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


 


一般在VC6ATL向导中选择了Font字体属性的话,向导会在IDL文件中自动产生以下代码,没有的话手工加入以下声明(加粗部分)


#include import"oaidl.idl";


[      


uuid(E63A22F1-9BD3-11D0-A6D7-0000837E3100),       


version(1.0), helpstring("NoteIt1.0 Type Library")]


library


NOTEITLib{


importlib("stdole32.tlb");  


importlib("stdole2.tlb");  


// Interface is now inside the libraryblock  


[


object,     


uuid(E63A2306-9BD3-11D0-A6D7-0000837E3100),     


dual,     


helpstring("INoteCtlInterface"),     


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(BSTRinMessage, 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 =
      3 =


 


以下是verb的格式:


 


Verb_Number =


 


Verb_Number是个枚举类型,Verb_String是有效的字符串,像"属性",Menu_Flag描述如何调用::AppendMenuVerb_FlagOLEVERBATTRIB枚举类型的值之一,如下:


 


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::DoVerb(iVerb, lpmsg,
    pActiveSite, lindex, hwndParent, lprcPosRect);
}


 


#17 ATL里设置默认属性、默认方法?


 


对于属性只要在.IDL文件中将其ID设为0就行了。如:


 


[propget, id(0), helpstring("propertytest")] 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_COLORCOLORREF的有区别吗?


 


OLE_COLORCOLORREF之间是有一定区别的:OLE_COLORCOLORREF都是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 PropertyPages/Functions/OleTranslateColorRemarks


 


OLE_COLORCOLORREF的转达换处理:在MFC中可有OLEControl::TranslateColor()来转达换,在ATL或其它地方可调用API:OleTranslateColor()来进行从OLE_COLORCOLORREF的转换。返过来可用如下方法:OLE_COLOR ocConverted = (OLE_COLOR)clrBack;
同样,VARIANT_BOOLBOOL之间也有区别:BOOLlong,在BOOL中,TURE1FALSE0VAIRNAT_BOOLshort,在VARIANT_BOOL中,VARIANT_TRUE-10xFFFF),VARIANT_FALSE00x0000)。并且VARIANT_BOOL是和VB中的Boolean相同的,就像BSTRString的关系一样。所以,在自动化组件及控件中应该使用VARIANT_BOOL


 


#21 如何让我的控件输出数组?


 


参阅如下代码:


void CMyControl::GetArray( VARIANT FAR*pVariant )


{   


//商业代码   intnCount = 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    Fori = 0 To MyControl1.Count - 1       ListBox.AddItem t( i )    Next i


 


#22 如何取得控件的HWND?


 


     HWNDCMyOcx::GetApplicationWindow()
      {
         HWND  hwnd = NULL;
         HRESULT hr;
         //*****
这段代码在VC++ v4.1工作
         if (m_pInPlaceSite != NULL)
             {
            m_pInPlaceSite->GetWindow(&hwnd);
             returnhwnd;
             }
         //******
这段代码在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变量是用于维护COleControlambient的自动化接口,只有它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, LPARAMlParam, BOOL& 
bHandled)
{
CComControl::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 
spCtrlSite(m_spClientSite);
if(spCtrlSite)
{
return spCtrlSite->TranslateAccelerator(pMsg, 0);
}
}
return S_FALSE;
}


 


上述的代码是在子窗口的编辑框中处理TABENTER键,如果你需要处理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_UPVK_DOWN,如下示例:


 


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


 


默认按钮的处理:当用户按下ENTER,你应该允许焦点转移到默认的按钮上(如果一个按钮设置为默认),那么你需要实现IOleControl::GetControlInfo()来接受ENTERESC键,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 如何持续化参数属性?


 


在正常状态下支持二进制与文本的持续化,控件分别需要实现IPersistStreamInitIPersistPropertyBag接口,ATL提供了该接口的包装类IPersistStreamInitImplIPersistPropertyBagImpl,在装入与存储属性中,这两个类分别调用了CComControlBase::IPersistStreamInit_Load()/Save()CComControlBase::IPersistPropertyBag_Load()/Save() ,且调用CComDispatchDriver::GetProperty()在这里面又调用了invoke()来指定特殊的属性值,然后CComDispatchDriver::GetProperty()只实现支持单个属性值,签于此点,ATL 2.1的属性持续化机制不支持索引属性。


 


要突破这个限制,得在你的控件中覆盖持续化路径,并依照标准来实现文本与二进制的持续化,作为替代,你的属性应象这样定义:


 


[propget, id(4), helpstring("IndexedProperty")] 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,由于VC6ATL使用_ATL_MIN_CRT_宏,该宏会使CRT启动代码无效,去掉该宏就可以了,如下做法:Project->Setting->C/C++ Category中选择PreprocessorPreprocessor definitions:中去掉_ATL_MIN_CRT_


 


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


 


答:windowsless 就是没有窗口。你的ATL控件没有窗口, m_hWnd不是NULL能是什么。至于Ondraw得到的 hdc 实际是父窗口的hdchuhu 你注意看 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.
建立一个ALTproject,加入ALT对象选 controls full controls (也可以选别的)NextStock propertiesPicture 加入 supported  //这样, 会为控件生成一个picture属性,以及一个预制的 picture 属性对话框,方便选择图片。OK//m_pPicture 是一个 IPictureDisp.
//
由于M$的一个BUG 导致 build 有三个warning 先不要管它, 后面会有解决办法
2.
修改 HRESULTOnDraw(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(<>, 0xCAF53C68, 0xA94C, 0x11D2, 0xBB, 0x4A, 0x00,0xC0, 0x4F, 0xA3, 0x30, 0xA6);


 


然后,用有意义的名字换掉<>,如GUID_SIMPLE,并把这个定义包含到驱动程序或应用程序中。


#32 如何动态创建 ocx
答:看下面代码

#include
  CComModule _Module;
#include
#include
#pragma comment(lib,"atl")

 

CComQIPtr m_spBrowser;
CAxWindow content_wnd;
......
if(   _tcslen(m_tcHtmlFileName) > 0 )
{
RECT rc;
GetClientRect( &rc );
if(m_spBrowser==NULL)
{
LPOLESTR pstrbrowserid;
StringFromCLSID(IID_IWebBrowser2,&pstrbrowserid);
_bstr_t bstrbrowser(pstrbrowserid);
CoTaskMemFree(pstrbrowserid);
if(content_wnd.IsWindow())
content_wnd.DestroyWindow();
content_wnd.Create( m_hWnd, rc, LPCTSTR(bstrbrowser),WS_CHILD¦WS_VISIBLE¦WS_HSCROLL¦WS_VSCROLL);//create a browser control
HRESULT hrbrowser;
hrbrowser = content_wnd.QueryControl( IID_IWebBrowser2,reinterpret_cast(&m_spBrowser) );
}

 


#33 如何获取窗体上 ocx 的接口指针?



解决方法:
CWindow::GetDlgControl
()
CAxWindow 中的 QueryControl
拿上面的例子:
hrbrowser = content_wnd.QueryControl( IID_IWebBrowser2,reinterpret_cast(&m_spBrowser) );

 


 #34 如何调整控件的大小



要调整控件大小,只需使用标准的 MoveWindow SetWindowPos API (或它们的 CWindow 包装)调整宿主窗口的大小即可。为响应收到的窗口消息,宿主对象自动调整控件大小以填充宿主窗口。

 


#35 #import Java com 组件后生成的 .tlh 文件中的汉字参数问题



解决办法 :
1
、先保存好这个 tlh 文件的时间戳
2
、打开 .tlh 文件 , 手动把 " 参数 " 改称 "var"
3
、然后保存文件,并手动把时间戳改回 1 种保存的时间辍
f5-----ok


 


#36 如何使用 IStream



我以前写的代码 :
void bufStorageWrite()
{
CoInitialize(NULL);
IStream *pStream = NULL;
HRESULThr = S_OK;
HGLOBAL hGLobal =   GlobalAlloc(GMEM_FIXED,SIZE_T(nCount*sizeof(Coord3D)));
::CreateStreamOnHGlobal(hGLobal,true,&pStream);
if(pStream)
{
DWORD dwData[2] = {1,0};
Coord3D coord = {1,2,3,4};
clock_t t1 = clock();
for(int i=0; i {
pStream->Write(&coord,sizeof(Coord3D),NULL);//(&dwData,2*sizeof(DWORD), NULL);
}
clock_t t2 = clock();
cout<<_T("bufStorageWrite WriteTime")<
 

pStream->Release();
}

 

CoUninitialize();
}

 


#37 如何操作 DATE 类型?



看看下面代码,加上 #include
void CVarUseDlg::OnDate()
{
       VARIANT timeSelection;
       COleDateTime timeNow;
       DATE curDate;
       HRESULT hr;
       //
获取当前时间 .
       timeNow = COleDateTime::GetCurrentTime();
      
       //
设置一个时间给 VARIANT
       timeSelection.vt = VT_DATE;
       timeSelection.date = timeNow.m_dt;
      
       //Convert Variant into string usingVariant Change Type.
       hr = VariantChangeType(&timeSelection,&timeSelection, 0, VT_BSTR);
       CString sCurTime(timeSelection.bstrVal);
      
      
       //Get Time as System Time.
       SYSTEMTIME mySysTime;
       timeNow.GetAsSystemTime(mySysTime);
      
       //Use COleDateTime functionality to getchange SYSTEMTIME into DATE.
       COleDateTime pastTime(mySysTime);
       curDate = pastTime.m_dt;

 


 

       //Use COldeDateTime Format command to getdate as CString.
       LPCTSTR format = _T("%X %z");  //Current time and time zone.
       //Note see "strftime" help forvalid formating strings.   
       CString sTime = pastTime.Format(format);

 

}

 


#38 我用 VB 写了一个 DLL ,用 VC 怎么调用啊 ?



#import "your.dll" no_namespace 前加上下面这句:
#import "msvbvm60.dll" no_namespace rename("EOF","EndOfFile") rename("RGB","ColorRGB")
使用 VB 来开发控件的时候,需要将 VB 的虚拟机装上去
msvbvm60
VB 做的控件都要用到的一个 DLL

 


#39 那怎么发布使用了 dll Activex 呢?  


 


1 、如果需要创建 cab 文件,首先需要Cabarc 或者 Makecab ,它们随着 Cabinet  SDK 的安装就有了, Cabinet  SDK 的下载地址是http://msdn.microsoft.com/workshop/ management/cab/cabdl.asp  
Cabarc
可以创建、查看或者解出 cab 里面的文件,而 Makecab 则只可以用来创建 cab 文件。  
2
、制作 cab 文件时需要将所有的相关文件都包含进去,可以通过 Depends VC 自带的)检查需要的文件。使用 inf 文件将这些东西都写进去。  
3
inf 搞法: inf 文件描述 cab 中所有的 ocx dll 文件, inf 通过一些命名区域来提供需要的信息。  
 
怎么写 inf  
最开始一般是 [Version] 区:  
eg:  [Version]  
       signature="$XXXX$"  
       AdvancedINF=2.0  
接下来就是最重要的 [Add.Code] 区:  
eg:  [Add.Code]  
       Ctrl1.dll=C1Section  
       Ctrl2.dll=Ctrl2.dll  
前面是要下载的文件名,后面是对应这个文件的区域名,可以是任何名字,不过一般都是和文件的名字相同,这样方便维护。还有需要注意是在 [Add.Code] 区出现的文件要根据依赖性进行排序,例如前面说的 ctrl1.dll 要依赖于 ctrl2.dll ,则 ctrl2.dll 要出现在 ctrl1.dll 的前面。因为安装时是按照相反的顺序进行的,也就是说先安装 ctrl2.dll ,然后才是 ctrl1.dll ,哧哧,记清楚了,不要搞反了。  
再接下来是各个文件的区域了  
[Ctrl1.dll]  
file-win32-x86=thiscab  
RegisterServer=yes  
clsid={.....}  
DestDir=    
FileVersion=1,0,0,0  
[Ctrl1.dll]
区域中的第一个 file 值告诉 ie 到哪里去得到这个 dll file 一共包括三个部分,第一部分是 file ,这个永远都是这样的(至少目前来说);第二部分告诉声明支持的 OS win32 表示 windows mac 就是苹果 MAC  OX 了;第三部分是 CPU 类型,比如说 x86  ppc  (Power  PC)  mips 或者 alpha 了。  
file
的值可以取三个一个 URL ignore thiscab ,如果是 URL 则说明到 URL 所在的位置去下;如果是 ignore 说明对于这种 OS CPU ,不需要下载这个文件 (ctrl1.dll) ;如果是 thiscab 很明显就在当前的 cab 文件中了。  
接下来是 RegisterServer ,可以取两个值 yes no ,如果为 yes 则说明 ie 要注册该 dll ,如果是 no 就不必了;  
再下来是 DestDir ,它的值是 dll 将要存到本地硬盘的位置,如果它的值是 10 ,则将 dll 放到 /Windows 或者 /WinNT 下;如果是 11 ,则放到 /Windows/System 或者  
/WinNT/System32
下;如果是空(就是没有值)则会放到 /Windows 或者 /WinNT 下的 Downloaded  Program  Files 目录下;  
最后是 FileVersion ,这个就比较明显了,说明了 ctrl1.dll 的版本号。  
有时候我们使用 VB 来开发控件的时候,需要将 VB 的虚拟机装上去,它需要一些其它的说明的,简单地讲一下吧:  
[Add.Code] 中增加一项 MSVBVM60.DLL=MSVBVM60.DLL (以 VB6 为例)下面是  
MSVBVM60.DLL
区域:  
[MSVBVM60.DLL]  
           hook=MSVBVM60.cab_Installer 
           FileVersion=6,0,81,76  
FileVersion
很明显,是版本号,就不再说发,就说说 hook 吧。  
hook
区域是在安装的时候需要执行的区域,它分为两种,一种是有条件的,另外一种是无条件的,无条件的 hook 区域是必须执行的,反之则根据条件判断是否执行。以 [Setup  Hooks] 标记的区域是无条件区域,如下所示  
[Setup  Hooks]  
       hookname=section-name  
 
[section-name]  
run=%EXTRACT_DIR%/setup.exe  
无条件区域常用来通过一个 inf 文件执行一个安装程序,这就是我们在资源管理器右键点击一个 inf 文件时在执行安装这样的菜单的原因了  
ie 下载了一个 cab 文件,如果文件中没有 [Add.Code] ,则处理 [Setup  Hooks] 区域,运行 run 所指定程序,哧哧,上面就是 setup.exe  
条件区域则为在一定条件下执行,前面为 MSVBVM60.DLL 指定的 hook 区域就是一个条件区域,如果在 MSVBVM60.DLL 指定的 CLSID 或者 version 不能满足需要而且没有 file 这个命名值,则执行 hook 所指定的区域。  
[MSVBVM60.cab_Installer]  
file-win32-x86=http://activex.microsoft.com/controls/vb6/VBRun60.cab  
run=%EXTRACT_DIR%/VBRun60.exe  
上面 [MSVBVM60.cab_Installer] 是一个 hook 区域,它也包含了一个file , 指定一个 URL ,表示 MSVBVM60.DLL 可以从这个 URL 下载得到; run 则说明了执行哪一个文件  
这里有必要说明一下的是, MS 对一些常用的 Redistributable  Microsoft  DLLs  
可以通过指定 CODEBASE 属性为 http://activex.microsoft.com/controls ,这样在 cab 文件中就中需要包含这些文件,在计算机上有一个文件 redist.txt 上面的 dll 就是 Redistributable  Microsoft  DLLs  
 
 
创建一个 cab 文件: 
cabarc  N  ctrl1.cab  ctrl1.inf  ctrl1.dll  
N
表示要创建一个新的文件, ctrl1.cab 是创建的文件名, ctrl1.inf cab inf ,后而是需要加到 cab 里的文件,可以使用通配符。  
然后就可以将 cab 文件放到网页上了  
< OBJECT  ID="Ctrl1Obj"  
              CLASSID="clsid:....................................."  
              CODEBASE="http://server.com/ctrl1.cab#version=8,0,0,5007"> 
< /OBJECT>  
这里也在一个 version ,不过这里的 version 是指控件的 version ,而 inf 里的是文件的 version  
 
 
制作电子签名:  
首先从下面的网址下载制作签名的工具 SignCode ,地址是  
http://msdn.microsoft.com/workshop/gallery/tools/authenticode/authcode.asp 
从签名授权中心如 VeriSign 或者你的局域网上运行的 Microsoft  Certificate    
Server
授权服务器得到一个 certificate ,在申请授权的过程你会得到一个私钥。  
也可以使用 MakeCert.exe Cert2Spc.exe 创建的私钥进行测试,方法是首先使用 MakeCert 创建一个 X.509 certificate .cer 文件)  
       MakeCert  -sv  MyKey.pvk  n "CN=My  Software  Company"  MyCert.cer  
然后利用 Cert2Spc .cer 文件转换成为 PKCS  #7 软件发布 Certificate(.spc 文件 )  
       Cert2Spc  MyCert.cer  MyCert.spc  
利用你下载的 SignCode 对你的 cab 文件进行电子签名  
       SignCode  -spc  MyCert.spc  -v MyKey.pvk  -t  http://  
              timestamp.verisign.com/scripts/timstamp.dll  ctrl1.cab  
SignCode
还可以指定一些其它的参数,就不说了,太长了,哧哧。  
虽然可以利用测试的 .cer .spc 文件,但是在发布的时候,必须申请。  
 
其实东西在 SDK 中都有说明,不过都是 E 文的,慢慢看就没有什么发  
---------------------------------------------------------------  
 
再补充一点,如果只是测试,你可以 SignCode 时回车,根据提示一步步往下选,命令行参数又臭又长,还经常出错,这样会节省不少你的时间  
---------------------------------------------------------------  
 
这是因为 ocx 关联了你的两个 dll ,在系统调用 ocx 中的注册函数是需要调用你的 dll 但系统无法找到,所以加载 ocx 失败造成注册失败  
 
解决的办法是在 cab 的安装文件里把 dll 安装到系统的目录下,或者动态加载 dll ,或者指定加载 dll 的目录




 


#40 如何在应用程序中判断DLL COM )已注册?


读注册表 /HKEY_CLASSES_ROOT/APPID ,看你的 DLL 是否注册,  



代码如下:
HKEY  valueKey;  
 if  (ERROR_SUCCESS!=RegOpenKeyEx(HKEY_CLASSES_ROOT,  
                      "?????",0,KEY_READ,&valueKey))   
 {           //?????
为类 ID, "CLSID//{3B5B0834-5D5D-46C9-AFC9-FD746EDCC272}"  
         //
未注册成功  
        return;  
  }  
  else  
             //
已经注册成功
 


#41 我想在程序中连续注册好几个控件 , 如果用 regsvr32   命令会出现多个注册成功的对话框有没有什么方法可以解决那?



/u
是注销。  
/s
是不弹出注册成功的对话框  
regsvr32    ...  /s
代码实现如下:
HMODULE  hModule  =  LoadLibrary(strFilePath);  
 
 if(hModule  ==  NULL)  
  {  
        strMessage.Format("LoadLibrary(/"%s/")  
失败 !",strFilePath);  
        MessageBox(NULL,strMessage,"RegisterServer32",MB_ICONEXCLAMATION); 
          return  FALSE;  
 }  
  FARPROC  pFarProc  = GetProcAddress(hModule,"DllRegisterServer");  
  if(pFarProc  ==  NULL)  
  {  
   MessageBox(NULL,"GetProcAddress(/"DllRegisterServer/") Failed","RegisterServer32",MB_ICONEXCLAMATION);  
    }  
     else  
     {  
          (*pFarProc)();  
     }


#42 IWebBrowser2 疑惑 . ,有时能获取控件的句柄有时没法获取,怎么回事?



CComQIPtr m_pWebBrowser2;  
//----------------------  
使用 CLSID_WebBrowser--------------------  
HRESULT  hrrs  =  CoCreateInstance(CLSID_WebBrowser, NULL,CLSCTX_INPROC,IID_IWebBrowser2,(void**)&m_pWebBrowser2);  
//----------------------  
使用 CLSID_InternetExplorer---------------  
HRESULT  hrrs  =  CoCreateInstance(CLSID_InternetExplorer, NULL,CLSCTX_INPROC,IID_IWebBrowser2,(void**)&m_pWebBrowser2);  
//----------------------  
获取句柄 ---------------------------------  
HWND  hIE;  
hrrs  =  m_pWebBrowser2->get_HWND((long*)&hIE);  
以上两个方法来获取 IWebBrowser2, 两种都能获取,可是第一种为什么得不到窗口句柄,而第二种可以呢?请指教  , 有个么办法使第一种也能得到 Browser 窗口句柄呢?  
---------------------------------------------------------------  
 
解答:
CLSID_WebBrowser:
是微软 web 预览控件的实现类的 id, 它同过属性 Parent 来得到窗口句柄 , 因为控件没法知道它被放在哪个容器里 , 所以不会实现 HWND 属性 !  
CLSID_InternetExplorer:
是微软 ie 浏览器自动化实现类的 id, 它通过属性 HWND 得到 ! 因为得到句柄一定封装好了 .   哈哈


#43 作了个 ActiveX 控件,嵌入网页中使用,版本更新时,客户端不能更新,如何解决?


网页中调用控件部分代码  
< OBJECT classid="clsid:C69A0449-8786-11D4-B209-00104B13AFD4" CODEBASE="PLWeb.cab#version=1,0,0,2"  height=605 id=PLWeb1  
                                                        style="LEFT:  0px;  TOP: 0px"  width=995  VIEWASTEXT>    
                                               
                                               
                                               
                                               
                                
 
cab
包中的内容  
plweb.ocx  (1.0.0.2
)  
plweb.inf  
(  
     [version]  
     signature="$CHICAGO$"  
     AdvancedINF=2.0  
 
     [Add.Code]  
     PLWeb.ocx=PLWeb.ocx  
 
     [PLWeb.ocx]  
     file-win32-x86=thiscab  
     ;  ***  add  your  controls  CLSID here  ***  
     clsid={C69A0449-8786-11D4-B209-00104B13AFD4}  
     ;  Add  your  ocx's  file  version here.  
     FileVersion=1,0,0,2  
     DestDir=11  
     RegisterServer=yes        
)  
客户端在第一次浏览具有控件的网页时,可以正常下载注册,但如果更新控件版本时,客户端可以下载(在 internet 的临时文件夹中可以找到新的 plweb.cab 文件),但是不能注册新版本控件。  
       
不知道这是什么原因引起的,如何解决?  
---------------------------------------------------------------  
 
< OBJECT  classid="clsid:C69A0449-8786-11D4-B209-00104B13AFD4" CODEBASE="PLWeb.cab#version=1,0,0,3"  height=605 id=PLWeb1  
style="LEFT:  0px;  TOP:  0px"  width=995 VIEWASTEXT>  
 
你可以手工在工程中更改版本号来达到升级   
 
如果没有注册成功 , 有可能是客户机环境变了 .  
你的控件需要运行库支持 , 现在客户机没有了 [ 重做了系统等 ]  
打包时注意一下 , 如果是基于 atl , 就打包 ATL.dll, 如果是基于 mfc 的就打包 mfc42.dll msvcrt.dll, 如果需要 ole 就打包 olepro32.dll oleaut32.dll .  
---------------------------------------------------------------  
 
检查一下是否在修改程序时 , vc 自动创建部分的代码中的 id 修改了


#44 如何利用 COM ,用 VC word Excel 等文件中插入图片?


http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/q311/7/65.asp&NoWebContent=1 
 
在上面这个例子中,如果我想再加入在当前鼠标位置插入图片的功能,该怎么写?  
---------------------------------------------------------------  
 
void  CWebOfficeCtrl::LoadUnit(void)  
{  
          AFX_MANAGE_STATE(AfxGetStaticModuleState());  
 
           //  TODO:  
在此添加调度处理程序代码  
           LPDISPATCH  lpDisp; 
          lpDisp=m_pWebOfficeFrame->m_pWebOfficeView->m_pSelection->GetIDispatch(); 
           _Application_Word m_WordApp;  
           _Document_Word m_WordDoc;  
          m_WordDoc.AttachDispatch(lpDisp,TRUE);  
           //m_WordDoc.Activate(); 
          m_WordApp=m_WordDoc.GetApplication();  
             
           InlineShapes_Word m_WordInlineShapes;  
           InlineShape_Word m_WordInlineShape;  
           Selection_Word m_WordSelection;  
          m_WordSelection=m_WordApp.GetSelection();  
          m_WordInlineShapes=m_WordSelection.GetInlineShapes();  
           COleVariant vTrue((short)TRUE),vFalse((short)FALSE),vOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR); 
          //AfxMessageBox(m_UnitName,MB_ICONINFORMATION);  
          m_WordInlineShape=m_WordInlineShapes.AddPicture(m_UnitName,vFalse,vTrue,vOptional); 
           /*  
           //2003.11.29  Add By  DigitalTitan[
设置图元重叠属性 ]  
           Shape_Word m_WordShape;  
          m_WordShape=m_WordInlineShape.ConvertToShape();  
           WrapFormat_Word m_WordWrapFormat;  
           m_WordWrapFormat=m_WordShape.GetWrapFormat(); 
          //m_WordWrapFormat.SetAllowOverlap(TRUE);3  
          m_WordWrapFormat.ReleaseDispatch();  
          m_WordShape.ReleaseDispatch();  
           //  
           */  
           m_WordInlineShape.ReleaseDispatch(); 
          m_WordInlineShapes.ReleaseDispatch();  
          m_WordSelection.ReleaseDispatch();  
           m_WordDoc.ReleaseDispatch(); 
           m_WordApp.ReleaseDispatch(); 
}

 

 
WORD 已经启动时,并打开了几个文件,我想在其中的一个打开的 WORD 文件的光标位置插入一幅 BMP  
          if(!WordApp.CreateDispatch("Word.Application",NULL))//
创建一个新的 word 程序  
           {  
                      AfxMessageBox("
创建 ms_word 服务失败 ");  
                      exit(1);  
           }  
 
           //
让用户能够查看自动化的过程  
           WordApp.SetVisible(true); 
 
           //
docs word 程序连接, docs 代表 word 中所有文档  
           docs=WordApp.GetDocuments(); 
           COleVariant covOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);  
           //
docs 来打开一个文档,并将句柄返回给 doc  
          //doc=docs.Add(COleVariant("C://ox.doc"),covOptional,covOptional,covOptional); 
//
这里要查找我要加入的 WORD 文件是否打开  
//
怎么样在当前光标处加入文件 .  
 
//  
 
---------------------------------------------------------------  
 
1.
得到 ActivateDocument  
2.
得到 ActivateDocument 中的 Shapes  
3.
调用 Shapes 的方法 :  AddPicture  
 
只要指定图片文件名,就可以加入图片了。  
---------------------------------------------------------------  
 
InlineShapes_Word  m_WordInlineShapes;  
           InlineShape_Word m_WordInlineShape;  
           Selection_Word m_WordSelection;  
          m_WordSelection=m_WordApp.GetSelection();  
          m_WordInlineShapes=m_WordSelection.GetInlineShapes();  
           COleVariant vTrue((short)TRUE),vFalse((short)FALSE),vOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR); 
          //AfxMessageBox(m_UnitName,MB_ICONINFORMATION);  
          m_WordInlineShape=m_WordInlineShapes.AddPicture(m_UnitName,vFalse,vTrue,vOptional); 
           /*  
           //2003.11.29  Add By  DigitalTitan[
设置图元重叠属性 ]  
           Shape_Word m_WordShape;  
          m_WordShape=m_WordInlineShape.ConvertToShape();  
           WrapFormat_Word m_WordWrapFormat;  
          m_WordWrapFormat=m_WordShape.GetWrapFormat();  
           //m_WordWrapFormat.SetAllowOverlap(TRUE);3 
          m_WordWrapFormat.ReleaseDispatch();  
          m_WordShape.ReleaseDispatch();  
           //  
           */


#45 如何使用 MFC 插入图片到 excel



#import  "G:/Program  Files/Common  Files/Microsoft  Shared/OFFICE11/MSO.DLL" 
#import  "G:/Program  Files/Common  Files/Microsoft Shared/VBA/VBA6/VBE6EXT.OLB"  
#import  "G:/Program  Files/Microsoft Office/OFFICE11/EXCEL.EXE"  rename("RGB", "ExcelRGB") rename("Delete","ExcelDelete") rename("DialogBox","ExcelDialogBox")    
void  Cexcel_picDlg::OnBnClickedOk()  
{  
           //  insert picture  to  excel  
           //  environment: vc2003,  excel2003,  windows2003  
           using  namespace Excel;  
           _variant_t covOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);  
           DWORD  dwStartTime =  GetTickCount();  
           _ApplicationPtr app;//("Excel.Application");  
           HRESULT  hr  = S_OK;  
           CLSID  clsid;  
          CLSIDFromProgID(L"Excel.Application",  &clsid);   
           app.CreateInstance(clsid); 
           WorkbooksPtr  books; 
           _WorkbookPtr  book; 
           long  lcid =LOCALE_USER_DEFAULT;  
           SheetsPtr  sheets; 
          app->get_Workbooks(&books);  
           book  = books->Add(covOptional,lcid/*,&book*/);  
          book->get_Worksheets(&sheets);  
           IDispatchPtr  pDisp; 
          sheets->get_Item(_variant_t((short)1),&pDisp);  
           _WorksheetPtr sheet(pDisp);  
           RangePtr  range  = sheet->GetRange(_variant_t("A1"),_variant_t("A1")); 
           range->Value2  = "aaa";  
           LPCSTR  m_Path  = _T("C://flower8.jpg");  
           PicturesPtr  pics =  sheet->Pictures();  
           pics->Insert(m_Path,VARIANT_FALSE); 
          app->put_AskToUpdateLinks(lcid,VARIANT_FALSE);  
          app->put_AlertBeforeOverwriting(lcid,VARIANT_FALSE);  
          app->put_UserControl(VARIANT_FALSE);  
          app->put_DisplayAlerts(0,VARIANT_FALSE);                        
           LPCTSTR  file_name =  _T("c://aa.xls");  
           _variant_t varfilename(file_name);  
           DeleteFile(file_name); 
           HRESULT  hrMethod =  book->SaveAs(varfilename,covOptional,covOptional,covOptional,covOptional,covOptional,xlNoChange); 
          book->put_Saved(0,VARIANT_TRUE);                                               
          book->Close(COleVariant(VARIANT_FALSE));  
           books->Close();             
           app->Quit();  
}


#46 关闭 IE 时,销毁 ACTIVEX 控件的问题


我用 VC 向导创建  MFC  ACTIVEX  CONTROL,    
默认生成 CMyTestApp,  CMyTestCtrl,  CMyTestPropPage 三个类。  
 
然后,我在 IE 中调入该控件。当关闭 IE 时, CMyTestCtrl OnDestroy 和析构函数  
都不会被执行,在 Container 中调试时,却都会被执行。请问这是为什么?  
(注: CMyTestApp ExitInstance 在关闭 IE 时,会被执行)  
 
那么,我在 CMyTestCtrl 的构造函数或 OnCreate 中分配的资源,在哪儿释放才好呢?  
---------------------------------------------------------------  
 
重载 WM_CLOSE 或者 WM_DESTROY  
---------------------------------------------------------------  
重载 COleControl::OnClose  
对应于 IOleControl::Close  
---------------------------------------------------------------  
WM_DESTROY


#47 ActiveX 控件键盘输入问题



自己开发了一个 ActiveX 控件,从 COleControl 继承,在 VB Control  Test  Container 中使用均没有问题,但是在 MFC 的基于对话框的程序中使用,能响应鼠标输入但不能响应键盘输入。跟踪发现在控件的 PreTranslateMessage 中有 WM_CHAR 消息但没有响应 OnChar 函数,不知哪位大侠知道如何解决。  
---------------------------------------------------------------  
Accelerator  keys,  such  as  ARROW  keys,  are first  received  by  the  message  pump  of the  ActiveX  control's  container.  Even  if the  control  has  the  focus,  it  does not  receive  messages  for  keystrokes  that have  special  meaning  to  control  containers, such  as  ARROW  and  TAB  keys.  MFC ActiveX  controls  have  a  chance  to intercept  these  messages  by  overriding their  PreTranslateMessage  function.    
 
However,  PreTranslateMessage  is  not  always  called for  an  MFC  ActiveX  control.  
 
RESOLUTION  
Install  a  Windows  WH_GETMESSAGE  hook  for  the modeless  dialog  box/propertysheet  derived  class to  allow  it  to  intercept  keystrokes and  handle  accelerators.    
...  
see  Knowledge  Base  articles  for  more information  
Q168777  PRB:  MFC  ActiveX  Control  in  IE Doesn't  Detect  Keystrokes  
Q180402  PRB:  MFC  ActiveX  Control  Ignores ARROW  Keys  on  VB  Container  
Q187988  PRB:  ActiveX  Control  Is  the  Parent Window  of  Modeless  Dialog  
Q199431  PRB:  Enabling  Menu  Mnemonics  in  an MFC  ActiveX  Control  
Q194294  HOWTO:  Add  Toolbars  and  Tooltips  to ActiveX  Controls  


#48 ASP 里如何调试写的( ATL 写的) COM 呢?


首先在管理工具 -> 组件服务里面的 “COM+ 应用程序项下面添加一下空的应用程序,名字随便,然后把写好的组件注册在这个里面,并记下该应用程序的 ID ,如: {2D62D611-4A90-4196-AA9B-2055AD3A12E7}  
 
接下来在 VC 里面 project->setting->debug executable  for  debug  session 选项里面填写系统目录 +DLLHOST.exe“ ,如我的系统填写的是 “C:/windows/system32/DLLHOST.EXE”  
program  arguments 选项里面填写刚才的应用程序 ID  {2D62D611-4A90-4196-AA9B-2055AD3A12E7}  ,设置断点,调试运行,你会发现 COM 程序会中断下来,这样就表示你设置成功了,然后用 ASP 调用就可以了 ~  
---------------------------------------------------------------  
以下摘自  程序调试 >  Addison  -Wesley  
     
如果你写的基 com  dll 要被在一个 asp 中运行的脚本调用,你的代码将从一个配定组件中被调用。   MS internet 信息服务器处理一个来自 ASP 的请求时,他激活一个叫做网络应用管理器( web  application  manager )的配定组件。   这个组件执行该 ASP 中的脚本发出的激活和方法调用请求。因为网络应用管理组件是在 MTS/COM +配置的。你的 com  dll 会在一个代理进程的环境中执行。  
   
在这个情况下,将调试器附到正确的代理进程的工作就变成了确定哪个服务器包是你的组件宿主的问题。   。。。  
if  on  windows  2000  
     
WIN2000 中,管理工具 ->Internet   服务管理器- > 查看你的虚拟站点的属性- > 在指定虚拟目录的 tab 页中,包含一个叫应用程序保护的选项。(在最下面)  
     
这个选项可设置为:  
1
LOW   (低, IID 进程)  
2
Medium (中等,对象池)  
3
high (高,独立)  
这个设置决定组件的宿主服务器包。  
 
if  1
),   COM +浏览器中找到 in-process  apllication 包的 GUID  
if  2
),在 com +浏览器中找到 IIS  out-of-process  pooled apllication 包的 GUID  
if  3
),在 com +浏览器中找到 IIS -{ website// 你的虚拟目录名}包的 GUID  
 
然后,你可以参考上面的答复,将 VC 附到已运行的代理进程,进行调试了。


#49 初学者应该知道的一些COM 的基本概念


小弟接触 COM 也有一段时间了,对很多 COM 相关的概念还没弄清楚,查了许多资料,资料上也讲得迷迷糊糊的,所以向大家请教一下:  
       
一、经常看到 DLL OLE ActiveX COM 等技术资料,他们之间到底是些什么关系,有什么区别与联系?小弟只知道 ActiveX COM 有密切的联系,而 COM 又以 DLL 文件形式存在, ActiveX 又以 .ocx 形式存在,对 OLE 到底是什么东西有什么用也没弄明白。  
       
二、对于 GUID CLSID 经常弄不清楚,(他们的样子很相象),作用也不太明白。好象 COM 一定要在注册表注册,不知 OLE COM 组件是否一定需要注册啊?不注册能用吗?  
 
 
望各位高手能详细指教一下(不要笑话小弟白痴啊,我真的没弄明白)  
---------------------------------------------------------------  
 
COM
是一个二进制标准,它是以 DLL 形式存储的,由于传统 DLL 没有解决一些诸如内存分配,卸载等关键性问题,所以它不足以成为一个组件模型,而 COM 定义了这些标准。 OLE 算是 COM 的前身,到 OLE2 后就正式出现为 COM 标准,它已经在 OLE( 对象连接与嵌入 ) 上作了极大的改进,成为一个独立的标准,而 ActiveX 是微软提出的一个专有名词,目的是实现网上的一些应用,它的本质就是 COM  
CLSID
是用来标志每个 COM 组件的, CLSID 是属于 GUID 的,格式一样,只是 CLSID 有特定用途,换了个名字而已, COM 一定要注册,不注册的话就找不到他的具体位置,好像 .net 以后就不需要注册了。  
我也不太懂,一起学习  
---------------------------------------------------------------  
上面的说的很好!我补充一点吧!  
CLSID
可以这么解释 class  id,GUID 是全局唯一 ID,CLSID 也是属于 GUID 的,例如 IID Interface  ID 。多看看 COM 本质!  
DLL
OLE->COM->ActiveX,ATL->COM+,DCOM-> 现在出现 .NET 中的公共语言环境。  
这是我自己的理解,希望对你有帮助!  
---------------------------------------------------------------  
COM
是一种标准,巧妙的利用了运行时绑定的技术。可以 DLL 形式存在,也可以 exe 形式存在。  
ActiveX
COM 标准的一个具体应用而已。 ocx 文件就是动态连接库文件,扩展名不同而已。 OLE 也是一种技术的名称,叫对象连接与嵌入,主要目的是通过一套约定,来使得不同程序能相互传递数据。 ole2 以后都是通过的 COM 技术来实现 OLE 了。  
 
GUID
是一种常量,这种常量能保证世界上任何地方的任何人在任何时候都能产生一个不同于其他人的值。 CLSID 就是 GUID ,只不过看起来明确一点,他是指代 class 的。类似的还有 IID ,是指代 interface 的。  
就好像 UINT   就是  unsigned  long 。只不过看起来舒服些。   


#50 如何修改 DCOM 应用程序的运行位置(在哪台机器上运行)?


DCOMCNFG.exe 中某应用程序位置项,选了在这台计算机上运行应用程序在下列计算机上运行应用程序的设置就不生效了。  
请问如何不用 DCOMCNFG.exe 而通过修改程序将在这台计算机上运行应用程序项前面的对号去掉(如操作注册表,改哪一项的制值)?  
---------------------------------------------------------------  
 
CoCreateInstanceEx ,在参数 pServerInfo 中,写入服务器的地址  
---------------------------------------------------------------  
当然有 , 通过 dcom 的管理接口  
---------------------------------------------------------------  
在注册表中你的服务器 AppID 下加一个 RemoteServerName 字符串值 "www.xxx.com" IP 地址  
也可以使用 dcomcnfg 设置

你可能感兴趣的:(ATL问题集)