WTL8.0 调用 ActiveX 控件

WTL8.0 调用 ActiveX 控件 - (调用 Flash 控件,响应 Flash 控件的事件)


很久没用WTL了,WTL都升级到8.0了,这两天做了个小例子,WTL调用Flash控件。

目标:使用WTL创建对话框的工程,调用Flash控件播放Flash,并响应Flash控件的事件。
环境:WindowsXP, VC++ 2005, WTL8.0, Flash9

1. 首先用WTL Wizard创建对话框工程,如图:
WTL8.0 调用 ActiveX 控件_第1张图片
注意要选中 Enable ActiveX Control Hosting,我习惯于 Generate .CPP Files 这样可以使H文件和CPP文件分开。

工程创建好后,Wizard会为我们在 tWinMain 函数中添加 AtlAxWinInit() 函数,如下:
int  WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE  /*hPrevInstance*/ , LPTSTR lpstrCmdLine,  int  nCmdShow)
{
    HRESULT hRes 
= ::CoInitialize(NULL);
// If you are running on NT 4.0 or higher you can use the following call instead to 
// make the EXE free threaded. This means that calls come in on a random RPC thread.
//    HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
    ATLASSERT(SUCCEEDED(hRes));

    
// this resolves ATL window thunking problem when Microsoft Layer for Unicode (MSLU) is used
    ::DefWindowProc(NULL, 000L);

    AtlInitCommonControls(ICC_BAR_CLASSES);    
// add flags to support other controls

    hRes 
= _Module.Init(NULL, hInstance);
    ATLASSERT(SUCCEEDED(hRes));

    AtlAxWinInit();

    
int nRet = Run(lpstrCmdLine, nCmdShow);

    _Module.Term();
    ::CoUninitialize();

    
return nRet;
}

2. 接着在编辑对话框资源,单击右键添加ActiveX控件,这里选择 ShockwaveFlash 1.0控件。如图:
WTL8.0 调用 ActiveX 控件_第2张图片

WTL8.0 调用 ActiveX 控件_第3张图片

添加好以后,我们需要为这个控件定义一个变量,以便使用控件的方法。我们在CMainDlg类里手工增加ActiveX控件的窗口变量:CAxWindow m_wndFlashPlayer。我们还需要增加ActiveX控件对象的COM接口 CComPtr m_FlashPtr,为了增加这个接口,我们需要导入Flash的控件类型库,在 stdafx.h文件中增加如下行:
# import   " c:/windows/system32/flash9c.ocx "  raw_interfaces_only, raw_native_types, no_namespace, named_guids
raw_interfaces_only 表示以原始接口方式调用Flash类型库里的方法。
no_namespace 表示没有名字空间。
named_guids 表示生成命名的guid变量,如 DIID__IShockwaveFlashEvents等变量。

3. 在对话框的初始化函数 OnInitDialog 里将ActiveX控件与变量绑定,如下:
    m_wndFlashPlayer  =  GetDlgItem(IDC_SHOCKWAVEFLASH1);
//     HRESULT hResult = m_wndFlashPlayer.QueryControl(__uuidof(IShockwaveFlash), reinterpret_cast(&m_FlashPtr));
    HRESULT hResult  =  m_wndFlashPlayer.QueryControl( & m_FlashPtr);
    ATLASSERT(hResult 
==  S_OK);
IDC_SHOCKWAVEFLASH1 是ActiveX控件的资源ID, GetDlgItem 根据资源 ID 得到ActiveX控件的窗口对象,然后窗口对象 m_wndFlashPlayer 使用QueryControl方法得到ActiveX控件的COM对象指针。上面代码中,注释掉的方法也是可用的,但没有注释的使用比较简单。

接着装载一个Flash Movie,调用下面的方法,装载一个swf文件,并让它处于停止状态:
    hResult = m_FlashPtr->put_Movie(_bstr_t("f:\\flashC.swf"));
    ATLASSERT(hResult  ==  S_OK);

    hResult = m_FlashPtr->Stop();
    ATLASSERT(hResult  ==  S_OK);

这时候,我们可以在对话框上增加一个按钮,在Click事件里添加播放的代码,如下:
LRESULT CMainDlg::OnBnPlayClicked(WORD  /*wNotifyCode*/ , WORD  /*wID*/ , HWND  /*hWndCtl*/ , BOOL &   /*bHandled*/ )
{
    HRESULT hResult = m_FlashPtr->Play();
    ATLASSERT(hResult == S_OK);

    
return 0;
}

到此,我们可以编译一下工程,如果没有意外,程序可以正常运行,点击Play按钮,可以播放Flash文件。

4. 下面我们关注如何响应Flash的事件,我们以FSCommand事件为例。
首先编辑对话框资源,右键单击前面添加的Flash控件,选择Add Event Handler,如图:
WTL8.0 调用 ActiveX 控件_第4张图片

我们选择添加FSCommand事件的响应处理,响应函数为OnFSCommand,响应的处理放在CMainDlg类中,如图:
WTL8.0 调用 ActiveX 控件_第5张图片

添加好后,Wizard会为我们生成事件响应的代码,主要在CMainDlg类中,我们看代码:
//  MainDlg.h : interface of the CMainDlg class
//
////////////////////////////////////////////////////////////////////////////
/

#pragma once

class  CMainDlg :  public  CAxDialogImpl < CMainDlg > public  CUpdateUI < CMainDlg > ,
    
public  CMessageFilter,  public  CIdleHandler,
    
public IDispEventImpl
{
public :
    
enum  { IDD  =  IDD_MAINDLG };

    virtual BOOL PreTranslateMessage(MSG
*  pMsg);
    virtual BOOL OnIdle();

    BEGIN_UPDATE_UI_MAP(CMainDlg)
    END_UPDATE_UI_MAP()

    BEGIN_MSG_MAP(CMainDlg)
        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
        COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
        COMMAND_ID_HANDLER(IDOK, OnOK)
        COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
    END_MSG_MAP()

//  Handler prototypes (uncomment arguments if needed):
//     LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//     LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//     LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

    LRESULT OnInitDialog(UINT 
/* uMsg */ , WPARAM  /* wParam */ , LPARAM  /* lParam */ , BOOL &   /* bHandled */ );
    LRESULT OnDestroy(UINT 
/* uMsg */ , WPARAM  /* wParam */ , LPARAM  /* lParam */ , BOOL &   /* bHandled */ );
    LRESULT OnAppAbout(WORD 
/* wNotifyCode */ , WORD  /* wID */ , HWND  /* hWndCtl */ , BOOL &   /* bHandled */ );
    LRESULT OnOK(WORD 
/* wNotifyCode */ , WORD wID, HWND  /* hWndCtl */ , BOOL &   /* bHandled */ );
    LRESULT OnCancel(WORD 
/* wNotifyCode */ , WORD wID, HWND  /* hWndCtl */ , BOOL &   /* bHandled */ );

    
void  CloseDialog( int  nVal);

public :
    CAxWindow m_wndFlashPlayer;
    CComPtr
< IShockwaveFlash >  m_FlashPtr;

    BEGIN_SINK_MAP(CMainDlg)
        SINK_ENTRY(IDC_SHOCKWAVEFLASH1, 150, OnFSCommand)
    END_SINK_MAP()

    void __stdcall OnFSCommand(BSTR command, BSTR args);
};

红色部分的代码是Wizard为我们生成的,注意如下几点:
(1)CMainDlg增加了父类 IDispEventImpl, 其中IDC_SHOCKWAVEFLASH1是ActiveX控件的资源ID。
(2)增加了BEGIN_SINK_MAP(事件接受器映射),SINK_ENTRY(IDC_SHOCKWAVEFLASH1, 150, OnFSCommand)是事件接收的进入点,IDC_SHOKEWAVEFLASH1是控件的资源ID,通常这里定义的是事件源的ID,需要和继承的IDispEventImpl模版的第一个参数一致,表示同一个事件源。150 是Flash控件的FSCommand事件的ID,这是固定的,在Flash控件的idl文件已经写死了,我们可以用 OleView 工具察看 Flash 的事件接口里的方法 FSCommand 的ID, 0x96 即为 150。OnFSCommand是我们前面数据的事件响应的函数名,Wizard也为我们生成了函数的声明和框架,void __stdcall OnFSCommand(BSTR command, BSTR args)。
我们在该函数里添加我们的处理代码。

是否这样就可以了吗?答案是否定的,虽然程序编译没有问题,但事件的响应没有触发,我们忘了 DispEventAdvice 了。在 CMainDlg 的初始化函数 OnInitDialog 里加入:DispEventAdvice,如下:
LRESULT CMainDlg::OnInitDialog(UINT  /* uMsg */ , WPARAM  /* wParam */ , LPARAM  /* lParam */ , BOOL &   /* bHandled */ )
{
    
//  center the dialog on the screen
    CenterWindow();

    
//  set icons
    HICON hIcon  =  (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME), 
        IMAGE_ICON, ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
    SetIcon(hIcon, TRUE);
    HICON hIconSmall 
=  (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME), 
        IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
    SetIcon(hIconSmall, FALSE);

    
//  register object for message filtering and idle updates
    CMessageLoop *  pLoop  =  _Module.GetMessageLoop();
    ATLASSERT(pLoop 
!=  NULL);
    pLoop
-> AddMessageFilter( this );
    pLoop
-> AddIdleHandler( this );

    UIAddChildWindowContainer(m_hWnd);

    m_wndFlashPlayer
=  GetDlgItem(IDC_SHOCKWAVEFLASH1);
//     HRESULT hResult = m_wndFlashPlayer.QueryControl(__uuidof(IShockwaveFlash), reinterpret_cast(&m_FlashPtr));
    HRESULT hResult  =  m_wndFlashPlayer.QueryControl( & m_FlashPtr);
    ATLASSERT(hResult 
==  S_OK);

//     AtlAdviseSinkMap(this, true);
    DispEventAdvise(m_FlashPtr);

    hResult 
=  m_FlashPtr -> put_Movie(_bstr_t( " f:\\flashC.swf " ));
    ATLASSERT(hResult 
==  S_OK);

    hResult 
=  m_FlashPtr -> Stop();
    ATLASSERT(hResult 
==  S_OK);

    
return  TRUE;
}

注释掉的代码 AtlAdviseSinkMap(this, true)也是可以用的。
到此,我们的目标算是实现了。

WTL8.0对 ActiveX的调用可算是比较简单的,回顾一下之前的做法,特别是接受事件的代码,相对还是比较不同的。不过原理都一样。
之前,事件接受的类要继承 IDispEventImpl
如下:
//  MainDlg.h : interface of the CMainDlg class
//
////////////////////////////////////////////////////////////////////////////
/

#pragma once

#define SOURCEID    
1

class CMainDlg;

typedef IDispEventImpl CFlashEventSink;

class  CMainDlg : 
    
public  CAxDialogImpl < CMainDlg >
    
public  CUpdateUI < CMainDlg > ,
    
public  CMessageFilter,  public  CIdleHandler,
    public CComObjectRoot,
    public CFlashEventSink
{
public :
    
enum  { IDD  =  IDD_MAINDLG };

    virtual BOOL PreTranslateMessage(MSG
*  pMsg);
    virtual BOOL OnIdle();

    BEGIN_UPDATE_UI_MAP(CMainDlg)
    END_UPDATE_UI_MAP()

    BEGIN_MSG_MAP(CMainDlg)
        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
        COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
        COMMAND_ID_HANDLER(IDOK, OnOK)
        COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
        COMMAND_HANDLER(IDC_BUTTON1, BN_CLICKED, OnBnClickedButton1)
    END_MSG_MAP()

//  Handler prototypes (uncomment arguments if needed):
//     LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//     LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//     LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

    LRESULT OnInitDialog(UINT 
/* uMsg */ , WPARAM  /* wParam */ , LPARAM  /* lParam */ , BOOL &   /* bHandled */ );
    LRESULT OnDestroy(UINT 
/* uMsg */ , WPARAM  /* wParam */ , LPARAM  /* lParam */ , BOOL &   /* bHandled */ );
    LRESULT OnAppAbout(WORD 
/* wNotifyCode */ , WORD  /* wID */ , HWND  /* hWndCtl */ , BOOL &   /* bHandled */ );
    LRESULT OnOK(WORD 
/* wNotifyCode */ , WORD wID, HWND  /* hWndCtl */ , BOOL &   /* bHandled */ );
    LRESULT OnCancel(WORD 
/* wNotifyCode */ , WORD wID, HWND  /* hWndCtl */ , BOOL &   /* bHandled */ );

    
void  CloseDialog( int  nVal);
    LRESULT OnBnClickedButton1(WORD 
/* wNotifyCode */ , WORD  /* wID */ , HWND  /* hWndCtl */ , BOOL &   /* bHandled */ );

public :
    CAxWindow m_wndFlashPlayer;
    CComPtr
< IShockwaveFlash >  m_FlashPtr;
    
    BEGIN_SINK_MAP(CMainDlg)
        SINK_ENTRY_EX(SOURCEID, DIID__IShockwaveFlashEvents, 150, OnFSCommand)
    END_SINK_MAP()

    void __stdcall OnFSCommand(BSTR command, BSTR args);
};

在ATL3.0时,还需要增加COM的映射宏。
    BEGIN_COM_MAP(CMainDlg)
    END_COM_MAP()  

更早些的方法,SINK_MAP 使用 SINK_ENTRY_INFO 的方式映射事件接受函数
    BEGIN_SINK_MAP(CMainDlg)
        SINK_ENTRY_INFO(SOURCEID, DIID__IShockwaveFlashEvents, 
150 , OnFSCommand,  & FSCommandInfo)
    END_SINK_MAP()

其中 FSCommandInfo 的定义如下:
__declspec(selectany) _ATL_FUNC_INFO FSCommandInfo  =
    { CC_STDCALL, VT_EMPTY, 
2 , { VT_BSTR, VT_BSTR } };

这样 CMainDlg 需要这样派生:
class  CMainDlg :  public  CAxDialogImpl < CMainDlg > public  CUpdateUI < CMainDlg > ,
                 
public  CMessageFilter,  public  CIdleHandler,
                 
public  CComObjectRootEx < CComSingleThreadModel > ,
                 
public  CComCoClass < CMainDlg > ,
                 
public  IDispEventSimpleImpl < SOURCEID, CMainDlg,  & DIID__IShockwaveFlashEvents >

最后说说事件的订阅的方法:
有这么几种:
1. 写一个Sink类,继承 IDispEventImpl,如下:
#pragma once

#define DISPID_ONSTARTADD    
1
#define DISPID_ONSTOPADD    
2

#define SOURCEID            
1

class  CTestSink;

typedef IDispEventImpl
< SOURCEID,CTestSink, & DIID__ITestOBJEvents, & LIBID_TestCOMLib, 1 , 0 >  CTestEventSink;

// typedef IDispatchImpl<_ITestOBJEvents, &__uuidof(_ITestOBJEvents), &LIBID_TestCOMLib, /* wMajor = */ 1, /* wMinor = */ 0> CEventSink;

class  ATL_NO_VTABLE CTestSink :
    
public  CComObjectRoot,
    
public  CTestEventSink
{
public:
    CTestSink(
void);
    
~CTestSink(void);

    BEGIN_COM_MAP(CTestSink)
    END_COM_MAP()  

    BEGIN_SINK_MAP(CTestSink)
        SINK_ENTRY_EX(SOURCEID,DIID__ITestOBJEvents,DISPID_ONSTARTADD,OnStartAdd)
        SINK_ENTRY_EX(SOURCEID,DIID__ITestOBJEvents,DISPID_ONSTOPADD,OnStopAdd)
    END_SINK_MAP()

    
void __stdcall OnStartAdd();
    
void __stdcall OnStopAdd(LONG result);
}
;

事件的订阅可以使用如下代码:
    ITestOBJPtr m_TestOBJPtr;
    HRESULT hResult 
=  m_TestOBJPtr.CreateInstance( " TestCOM.TestOBJ " );

    CTestSink 
*  m_pSink  =  NULL;
    m_pSink 
=   new  CComObject < CTestSink > ;
    m_pSink
-> AddRef();

    m_pSink
-> DispEventAdvise(m_TestOBJPtr);
//   hResult = AtlAdvise(m_TestOBJPtr, (IUnknown *)m_pSink, DIID__ITestOBJEvents, &m_dwCookie);

上面注释的代码也是可以使用的。在最开始的例子里,还有比较简单的事件订阅的方法:
    AtlAdviseSinkMap( this true );

最后记住在合适的时候取消事件订阅:DispEventUnadvise 或者 AtlUnadvise 或者 AtlAdviseSinkMap(this, false)。


第六部分 

使用ActiveX控件
在第六章,我将介绍ATL 对在对话框中使用ActiveX控件的支持,由于ActiveX控件就是
ATL 的专业,所以WTL 没有添加其他的辅助类。 不过,在ATL 中使用ActiveX控件与在MFC
中有很大的不同,所以需要重点介绍。我将介绍如何包容一个控件并处理控件的事件,开发 ATL
应用程序相 对于MFC 的类向导来说有点不方便。在WTL 程序中自然可以使用ATL 对包容
ActiveX控件的支持。
例子工程演示如何使用IE的浏览器控件,我选择浏览器控件有两个好处:
每台计算机都有这个控件,并且
它有很多方法和事件,是个用来做演示的好例子。
我当然无法与那些花了大量时间编写基于IE 浏览器控件的定制浏览器的人相比,不过,当你
读完本篇文章之后,你就知道如何开始编写自己定制的浏览器!
从使用向导开始
创建工程
WTL 的向导可以创建一个支持包容ActiveX控件的程序,我将开始一个名为 IEHoster 的新
工程。我们像上一章一样使用无模式对话框,只是这次要选上支持ActiveX控件包容(Enable
ActiveX Control Hosting),如下图:
 
选上这个check box将使我们的对话框从CAxDialogImpl 派生,这样就可以包容ActiveX
控件。在向导的第二页还有一个名为包容 ActiveX控件的check box ,但是选择这个好像对
最后的结果没有影响,所以在第一页就可以点击“Finish” 结束向导。
向导生成的代码
在这一节我将介绍一些以前没有见过的新代码(由向导生成的),下一节介绍ActiveX包容类
的细节。
首先要看的文件是stdafx.h ,它包含了这些文件:
#include
#include
 
extern CAppModule _Module;
 
#include
#include
#include
#include
// .. other WTL headers ...
atlcom.h 和atlhost.h 是很重要的两个,它们含有一些COM 相关类的定义(比如智能指针
CComPtr) ,还有可以包容控件的窗口类。
接下来看看maindlg.h 中声明的CMainDlg 类:
class CMainDlg : public CAxDialogImpl,
                public CUpdateUI,
                public CMessageFilter, public CIdleHandler
CMainDlg 现在是从CAxDialogImpl 类派生的,这是使对话框支持包容 ActiveX控件的第
一步。
最后,看看WinMain()中新加的一行代码:
int WINAPI _tWinMain(...)
{
//...
   _Module.Init(NULL, hInstance);
 
   AtlAxWinInit();
 
    int nRet =Run(lpstrCmdLine, nCmdShow);
 
   _Module.Term();
    returnnRet;
}
AtlAxWinInit()注册了一个类名未AtlAxWin 的窗口类,ATL 用它创建ActiveX控件的
包容窗口。
使用资源编辑器添加控件
和MFC 的程序一样,ATL 也可以使用资源编辑器向对话框添加控件。首先,在对话框编辑器上
点击鼠标右键,在弹出的菜单中选择“Insert ActiveX control”:
 
VC将系统安装的控件显示在一个列表中,滚动列表选择“Microsoft Web Browser”,单击
Insert 按钮将控件加入到对话框中。查看控件的属性,将ID设为IDC_IE 。对话框中的控件
显示应该是这个样子的:
 
如果现在编译运行程序,你会看到对话框中的浏览器控件,它将显示一个空白页,因为我们还
没有告诉它到哪里去。
在下一节,我将介绍与创建和包容 ActiveX控件有关的ATL 类,同时我们也会明白这些类是
如何与浏览器交换信息的。
ATL 中使用控件的类
在对话框中使用ActiveX控件需要两个类协同工作:CAxDialogImpl 和CAxWindow。它们
处理所有控件容器必须实现的接口方法,提供通用的功能函数,例如查询控件的某个特殊的 COM
接口。
CAxDialogImpl
第一个类是CAxDialogImpl ,你的对话框要能够包容控件就必须从 CAxDialogImpl 类派生
而不是从CDialogImpl 类派 生。CAxDialogImpl 类重载了Create() 和DoModal()函数,
这两个函数分别被全局函数AtlAxCreateDialog() 和AtlAxDialogBox() 调用。既然
IEHoster 对话框是由Create() 创建的,我们看看AtlAxCreateDialog()到底 做了什么
工作。
AtlAxCreateDialog()使用辅助类_DialogSplitHelper 装载对话框资源,这个辅助类
遍历所以对话框的控件,查找由资源编辑器创建的特殊的入口,这些特殊的入口表示这是一个
ActiveX控件。例如,下面是 IEHoster.rc文件中浏览器控件的入口:
CONTROL "",IDC_IE,"{8856F961-340A-11D0-A96B-00C04FD705A2}",
       WS_TABSTOP,7,7,116,85
第一个参数是窗口文字( 空字符串) ,第二个是控件的ID,第三个是窗口的类名。
_DialogSplitHelper:: SplitDialogTemplate()函数找到以'{' 开始的窗口类名时就
知道这是一个ActiveX控件的入口。它在内存中创建了一个临时对话框 模板,在这个新模板
中这些特殊的控件入口被创建的AtlAxWin 窗口代替,新的入口是在内存中的等价体:
CONTROL"{8856F961-340A-11D0-A96B-00C04FD705A2}",IDC_IE,"AtlAxWin",
       WS_TABSTOP,7,7,116,85
结果就是创建了一个相同ID的AtlAxWin 窗口,窗口的标题是 ActiveX控件的GUID。所以
你调用GetDlgItem(IDC_IE) 返回的值是AtlAxWin 窗口的句柄而不是ActiveX控件本身。
SplitDialogTemplate() 函数完成工作后,AtlAxCreateDialog() 接着调用
CreateDialogIndirectParam()函数使用修改后的模板创建对话框。
AtlAxWin and CAxWindow
正如上面讲到的,AtlAxWin 实际上是ActiveX控件的宿主窗口,AtlAxWin 还会用到一个
特殊的窗口接口类: CAxWindow ,当 AtlAxWin 从模板创建一个对话框后,AtlAxWin 的窗
口处理过程,AtlAxWindowProc(),就会处理 WM_CREATE 消息并创建相应的ActiveX控
件。ActiveX控件还可以在运行其间动态创建,不需要对话框模板,我会在后面介绍这种方法。
WM_CREATE 的消息处理函数调用全局函数 AtlAxCreateControl() ,将AtlAxWin 窗口
的窗口标题作为参数传递给该函数,大家应该记得那实际就是浏览器控件的 GUID。
AtlAxCreateControl() 有会调用一堆其他函数,不过最终会用到
CreateNormalizedObject()函数,这个函数将窗口 标题转换成GUID,并最终调用
CoCreateInstance() 创建ActiveX控件。
由于ActiveX控件是AtlAxWin 的子窗口,所以对话框不能直接访问控件,当然CAxWindow
提供了这些方法通控件通信,最常用的一个 是QueryControl(),这个方法调用控件的
QueryInterface() 方法。例如,你可以使用QueryControl()从浏览器控件 得到
IWebBrowser2接口,然后使用这个接口将浏览器引导到指定的URL 。
调用控件的方法
既然我们的对话框有一个浏览器控件,我们可以使用 COM 接口与之交互。我们做得第一件事情
就是使用IWebBrowser2接口将其引导到一个新URL 处。在OnInitDialog()函数中,我
们将一个CAxWindow 变量与包容控件的AtlAxWin 联系起来。
CAxWindow wndIE = GetDlgItem(IDC_IE);
然后声明一个IWebBrowser2 的接口指针并查询浏览器控件的这个接口,使用
CAxWindow::QueryControl():
CComPtr pWB2;
HRESULT hr;
hr = wndIE.QueryControl ( &pWB2 );
QueryControl()调用浏览器控件的QueryInterface() 方法,如果成功就会返回
IWebBrowser2接口,我们可以调用Navigate():
    if ( pWB2)
       {
       CComVariant v;  // empty variant
 
       pWB2->Navigate ( CComBSTR(" http://www.codeproject.com/"),
                        &v, &v, &v,&v );
       }
响应控件触发的事件
从浏览器控件得到接口非常简单,通过它可以单向的与控件通信。通常控件也会以事件的形式
与外界通信,ATL 有专用的类包装连接点和事件相应,所以我们可以从控件接收到这些事件。
为使用对事件的支持需要做四件事:
将CMainDlg 变成COM 对象
添加IDispEventSimpleImpl到CMainDlg 的继承列表
填写事件映射链,它指示哪些事件需要处理
编写事件响应函数
CMainDlg 的修改
将CMainDlg 转变成COM 对象的原因是事件相应是基于IDispatch 的,为了让CMainDlg
暴露这个接口,它必须是个 COM 对象。 IDispEventSimpleImpl 提供了IDispatch 接口
的实现和建立连接点所需的处理函数,当事件发生时 IDispEventSimpleImpl 还调用我们
想要接收的事件的处理函数。
以下的类需要添加到CMainDlg 的集成列表中,同时 COM_MAP列出了CMainDlg 暴露的接口:
#include     //browser control definitions
#include   // browser event dispatch IDs
 
class CMainDlg : public CAxDialogImpl,
                public CUpdateUI,
                public CMessageFilter, public CIdleHandler,
                public CComObjectRootEx,
                public CComCoClass,
                public IDispEventSimpleImpl<37, CMainDlg,
&DIID_DWebBrowserEvents2>
{
...
  BEGIN_COM_MAP(CMainDlg)
   COM_INTERFACE_ENTRY2(IDispatch, IDispEventSimpleImpl)
  END_COM_MAP()
};
CComObjectRootEx 类CComCoClass 共同使 CMainDlg 成为一个 COM 对象,
IDispEventSimpleImpl的模板参数是事件的ID,我们的类名和连接点接口的IID 。事件
ID可以是任意正数,连接点对象的 IID 是 DIID_DWebBrowserEvents2,可以在浏览器控
件的相关文档中找到这些参数,也可以查看exdisp.h 。
填写事件映射链
下一步是给CMainDlg 添加事件映射链,这个映射链将我们感兴趣的事件和我们的处理函数联
系起来。我们要看的第一个事件是 DownloadBegin ,当浏览器开始下载一个页面时就会触发
这个事件,我们响应这个事件显示“please wait” 信息给用户,让用户知道浏览器正在忙。
在MSDN 中可以查到DWebBrowserEvents2::DownloadBegin 事件的原型
  void DownloadBegin();
这个事件没有参数,也不需要返回值。为了将这个事件的原型转换成事件响应链,我们需要写
一个_ATL_FUNC_INFO 结构,它包含返回值,参数 的个数和参数类型。由于事件是基于
IDispatch 的,所以所有的参数都用VARIANT表示,这个数据结构的描述相当长(支持很多
个数据类型),以下 是常用的几个:
VT_EMPTY: void
VT_BSTR: BSTR 格式的字符串
VT_I4: 4 字节有符号整数,用于long 类型的参数
VT_DISPATCH: IDispatch*
VT_VARIANT>: VARIANT
VT_BOOL: VARIANT_BOOL ( 允许的取值是VARIANT_TRUE和VARIANT_FALSE)
另外,标志VT_BYREF 表示将一个参数转换成相应的指针。例如,VT_VARIANT|VT_BYREF
表示VARIANT* 类型。下面是_ATL_FUNC_INFO 的定义:
#define _ATL_MAX_VARTYPES 8
 
struct _ATL_FUNC_INFO
{
    CALLCONVcc;
   VARTYPE  vtReturn;
   SHORT   nParams;
   VARTYPE  pVarTypes[_ATL_MAX_VARTYPES];
};
参数:
cc
我们的事件响应函数的调用方式约定,这个参数必须是CC_STDCALL,表示是__stdcall 方

vtReturn
事件响应函数的返回值类型
nParams
事件带的参数个数
pVarTypes
相应的参数类型,按从左到右的顺序
了解这些之后,我们就可以填写DownloadBegin 事件处理的_ATL_FUNC_INFO 结构:
_ATL_FUNC_INFO DownloadInfo = { CC_STDCALL, VT_EMPTY, 0 };
现在,回到事件响应链,我们为每一个我们想要处理的事件添加一个 SINK_ENTRY_INFO宏,
下面是处理DownloadBegin 事件的宏:
class CMainDlg : public ...
{
...
  BEGIN_SINK_MAP(CMainDlg)
   SINK_ENTRY_INFO(37, DIID_DWebBrowserEvents2,DISPID_DOWNLOADBEGIN,
          

你可能感兴趣的:(Visual,C++)