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



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

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

    1. 首先用WTL Wizard创建对话框工程,如图:

    注意要选中 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控件。如图:




    添加好以后,我们需要为这个控件定义一个变量,以便使用控件的方法。我们在CMainDlg类里手工增加ActiveX控件的窗口变量:CAxWindow m_wndFlashPlayer。我们还需要增加ActiveX控件对象的COM接口 CComPtr<IShockwaveFlash> 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<void**>(&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,如图:


    我们选择添加FSCommand事件的响应处理,响应函数为OnFSCommand,响应的处理放在CMainDlg类中,如图:


    添加好后,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<IDC_SHOCKWAVEFLASH1,CMainDlg>
    {
    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, CMainDlg>, 其中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<void**>(&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<SOURCEID,CMainDlg,&DIID__IShockwaveFlashEvents,&LIBID_ShockwaveFlashObjects,1,0>
    如下:
    // MainDlg.h : interface of the CMainDlg class
    //
    ////////////////////////////////////////////////////////////////////////////
    /

    #pragma once

    #define SOURCEID    
    1

    class CMainDlg;

    typedef IDispEventImpl<SOURCEID,CMainDlg,&DIID__IShockwaveFlashEvents,&LIBID_ShockwaveFlashObjects,1,0> 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(thistrue);

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

    以上简单总结了 WTL 使用 ActiveX 控件的相关方法,欢迎拍砖,:-P

  • 相关阅读:
    STM8s在利用库配置端口的小问题
    ABAP调试
    READ TABLE 的用法
    人在低谷
    力扣 两数之和
    未来选择
    选择
    室友问题该如何解决呢?
    力扣 两数之和
    谈谈自己
  • 原文地址:https://www.cnblogs.com/kylindai/p/974797.html
Copyright © 2020-2023  润新知