首 页 | 新 闻 | 技术中心 | 第二书店 | 《程序员》 | 《开发高手》 | 社 区 | 黄 页 | 人 才
移 动专 题SUNIBM微 软微 创精 华Donews人 邮
我的技术中心 
我的分类 我的文档
全部文章 发表文章
专栏管理 使用说明



 RSS 订阅 
最新文档列表
Windows/.NET
.NET  (rss)    
Visual C++  (rss)    
Delphi  (rss)    
Visual Basic  (rss)    
ASP  (rss)    
JavaScript  (rss)    
Java/Linux
Java  (rss)    
Perl  (rss)    
综合
其他开发语言  (rss)    
文件格式  (rss)    
企业开发
游戏开发  (rss)    
网站制作技术  (rss)    
数据库
数据库开发  (rss)    
软件工程
其他  (rss)    

积极原创作者 
softj (78)
iiprogram (69)
qdzx2008 (50)
goodboy1881 (14)
wangchinaking (58)
fancyhf (1)
harrymeng (41)
yjz0065 (113)
coofucoo (105)
Drate (69)
CSDN - 文档中心 - Visual C++ 阅读:10649   评论: 3    参与评论
标题   如何: 通过HTML文档对象模型访问文档中的ActiveX控件的属性     选择自 jiangsheng 的 Blog
关键字   MFC Internet Explorer MSHTML IHTMLElement IHTMLElementCollection IHTMLDocument2 IHTMLObjectElement
出处  

如何: 通过HTML文档对象模型访问文档中的ActiveX控件的属性


此文章的信息应用于:

  • Microsoft Internet Explorer (编程) 版本4.0, 4.01, 4.01 SP1, 4.01 SP2, 5, 5.01, 5.5,6.0

概要

CSDN文档中心文章

用 MSHTML 的一点经验 111222(原作)

说明了如何访问在HTML文档对象模型中的网页的元素、内容。但是,有时候开发者实际上需要访问的是网页中ActiveX控件的属性、方法和事件。例如,你在网页载入之后需要修改/获取MediaPlayer的媒体源,以及控制MediaPlayer的播放。

更多信息

为获得ActiveX控件的接口,我们需要访问文档对象模型。获得文档接口的方法多种多样,比如CHtmlView::GetHtmlDocument,IWebBrowser2::get_Document,IHTMLWindow2::get_document等等,参见111222的文档。这里我直接用一个函数GetDHtmlDocument表示获得这个接口的函数。你可以自己实现这个函数。

通常,我们给控件一个在文档中唯一的ID以便于访问。首先我们要在文档里面找到这个元素,使用ID作为参数。

示例代码: (参考了MFC7.0的源代码)

// Document modified at : Sunday, August 18, 2002 11:04:50 AM , by user : Jiangsheng , from computer : KFB
//通过名字访问元素接口

HRESULT CDHtmlDialog::GetElement(LPCTSTR szElementId, IHTMLElement **pphtmlElement)
{
    return GetElementInterface(szElementId, __uuidof(IHTMLElement), (void **) pphtmlElement);
}
//通过名字访问元素接口的辅助函数,用于返回指定类型的接口
HRESULT CDHtmlDialog::GetElementInterface(LPCTSTR szElementId, REFIID riid, void **ppvObj)
{
    HRESULT hr = E_NOINTERFACE;
    *ppvObj = NULL;
    CComPtr<IDispatch> spdispElem;

    hr = GetElement(szElementId, &spdispElem);

    if (spdispElem)
        hr = spdispElem->QueryInterface(riid, ppvObj);
    return hr;
}
//通过名字访问元素接口的辅助函数,用于访问指定ID的元素接口。如果pBCollection返回TRUE,则返回的是一个IHtmlElementCollection集合,表示指定ID/名称的网页元素不止一个。
HRESULT CDHtmlDialog::GetElement(LPCTSTR szElementId, IDispatch **ppdisp,
                                 BOOL *pbCollection /*= NULL*/)
{
    CComPtr<IHTMLElementCollection> sphtmlAll;
    CComPtr<IHTMLElementCollection> sphtmlColl;
    CComPtr<IDispatch> spdispElem;
    CComVariant varName;
    CComVariant varIndex;
    HRESULT hr = S_OK;
    CComPtr<IHTMLDocument2> sphtmlDoc;
    USES_CONVERSION;

    *ppdisp = NULL;

    if (pbCollection)
        *pbCollection = FALSE;

    hr = GetDHtmlDocument(&sphtmlDoc);
    if (sphtmlDoc == NULL)
        return hr;

    varName.vt = VT_BSTR;
    varName.bstrVal = T2BSTR(szElementId);
    if (!varName.bstrVal)
    {
        hr = E_OUTOFMEMORY;
        goto Error;
    }

    hr = sphtmlDoc->get_all(&sphtmlAll);
    if (sphtmlAll == NULL)
        goto Error;
    hr = sphtmlAll->item(varName, varIndex, &spdispElem);
    if (spdispElem == NULL)
    {
        hr = E_NOINTERFACE;
        goto Error;
    }

    spdispElem->QueryInterface(__uuidof(IHTMLElementCollection), (void **) &sphtmlColl);
    if (sphtmlColl)
    {
        if (pbCollection)
            *pbCollection = TRUE;
#ifdef _DEBUG
        else
        {
            TRACE(traceHtml, 0, "Warning: duplicate IDs or NAMEs.\n");
            ATLASSERT(FALSE);
        }
#endif

    }
Error:
    if (SUCCEEDED(hr))
    {
        *ppdisp = spdispElem;
        if (spdispElem)
            (*ppdisp)->AddRef();
    }
    return hr;
}
然后我们要访问对象的属性、方法和事件,这就需要从IHtmlElement接口获得对象的接口,这里通过IHtmlObjectElement来访问
//获得ActiveX控件接口,注意ActiveX控件接口和HTML对象元素接口不是同一个接口,你不能直接使用IHtmlObjectElement接口来访问控件
HRESULT CDHtmlDialog::GetControlDispatch(LPCTSTR szId, IDispatch **ppdisp)
{
    HRESULT hr = S_OK;
    CComPtr<IDispatch> spdispElem;

    hr = GetElement(szId, &spdispElem);

    if (spdispElem)
    {
        CComPtr<IHTMLObjectElement> sphtmlObj;

        hr = spdispElem.QueryInterface(&sphtmlObj);
        if (sphtmlObj)
        {
            spdispElem.Release();
            hr = sphtmlObj->get_object(ppdisp);
        }
    }
    return hr;
}
有了Active控件的接口,下面的工作就简单多了,举例来说,如果要访问控件的指定名字的无参数属性,只需简单的调用IDispatch接口的GetIDsOfNames获得属性的DispID,然后调用Invoke方法取得属性
//获得控件属性,通过名字访问
VARIANT CDHtmlDialog::GetControlProperty(LPCTSTR szId, LPCTSTR szPropName)
{
    CComVariant varEmpty;
    CComPtr<IDispatch> spdispElem;

    GetControlDispatch(szId, &spdispElem);
    if (!spdispElem)
        return varEmpty;

    DISPID dispid;
    USES_CONVERSION;
    LPOLESTR pPropName = (LPOLESTR)T2COLE(szPropName);
    HRESULT hr = spdispElem->GetIDsOfNames(IID_NULL, &pPropName, 1, LOCALE_USER_DEFAULT, &dispid);
    if (SUCCEEDED(hr))
        return GetControlProperty(spdispElem, dispid);
    return varEmpty;
}
//设置控件属性,通过名字访问
void CDHtmlDialog::SetControlProperty(LPCTSTR szElementId, LPCTSTR szPropName, VARIANT *pVar)
{
    CComPtr<IDispatch> spdispElem;
    GetControlDispatch(szElementId, &spdispElem);
    if (!spdispElem)
        return;
    DISPID dispid;
    USES_CONVERSION;
    LPOLESTR pPropName = (LPOLESTR)T2COLE(szPropName);
    HRESULT hr = spdispElem->GetIDsOfNames(IID_NULL, &pPropName, 1, LOCALE_USER_DEFAULT, &dispid);
    if (SUCCEEDED(hr))
        SetControlProperty(spdispElem, dispid, pVar);
}
//获得控件属性的辅助函数,通过DispID访问
VARIANT CDHtmlDialog::GetControlProperty(LPCTSTR szId, DISPID dispid)
{
    CComPtr<IDispatch> spdispElem;

    GetControlDispatch(szId, &spdispElem);
    return GetControlProperty(spdispElem, dispid);
}
//设置控件属性的辅助函数,通过DispID访问
void CDHtmlDialog::SetControlProperty(LPCTSTR szElementId, DISPID dispid, VARIANT *pVar)
{
    CComPtr<IDispatch> spdispElem;
    GetControlDispatch(szElementId, &spdispElem);

    SetControlProperty(spdispElem, dispid, pVar);
}

//获得控件属性的实现函数
VARIANT CDHtmlDialog::GetControlProperty(IDispatch *pdispControl, DISPID dispid)
{
    VARIANT varRet;
    varRet.vt = VT_EMPTY;
    if (pdispControl)
    {
        DISPPARAMS dispparamsNoArgs = { NULL, NULL, 0, 0 };
        pdispControl->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT,
            DISPATCH_PROPERTYGET, &dispparamsNoArgs, &varRet, NULL, NULL);
    }
    return varRet;
}

//设置控件属性的实现函数
void CDHtmlDialog::SetControlProperty(IDispatch *pdispControl, DISPID dispid, VARIANT *pVar)
{
    if (pdispControl != NULL)
    {
        DISPPARAMS dispparams = {NULL, NULL, 1, 1};
        dispparams.rgvarg = pVar;
        DISPID dispidPut = DISPID_PROPERTYPUT;
        dispparams.rgdispidNamedArgs = &dispidPut;

        pdispControl->Invoke(dispid, IID_NULL,
                LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT,
                &dispparams, NULL, NULL, NULL);
    }
}
实际上,这样的方法效率比较低,因为每次访问都要调用GetIDsOfNames,而GetIDsOfNames是一个很慢的调用。为了优化程序效率,你可以缓存得到的名字->DispID映射,但是推荐的方法是使用类向导(Class Wizard)的从类型库添加类(New Class->From a type library)的功能把控件导入到工程,通过类向导自动生成的COleDispatchDriver派生类来访问属性和方法。这种方法直接使用类型库中生成的DispID来访问属性、方法和事件,所以速度比前面的每次都要调用GetIDsOfNames的方法要快得多。

下面是生成的COleDispatchDriver派生类部分代码示例:

CString CSomeObject::GetText()
{
 CString result;
 InvokeHelper(0x18, DISPATCH_PROPERTYGET, VT_BSTR, (void*)&result, NULL);
 return result;
}

void CSomeObject::SetText(LPCTSTR lpszNewValue)
{
 static BYTE parms[] =
  VTS_BSTR;
 InvokeHelper(0x18, DISPATCH_PROPERTYPUT, VT_EMPTY, NULL, parms,
   lpszNewValue);
}

LPDISPATCH CSomeObject::createNode(const VARIANT& type, LPCTSTR name, LPCTSTR namespaceURI)
{
 LPDISPATCH result;
 static BYTE parms[] =
  VTS_VARIANT VTS_BSTR VTS_BSTR;
 InvokeHelper(0x36, DISPATCH_METHOD, VT_DISPATCH, (void*)&result, parms,
  &type, name, namespaceURI);
 return result;
}

另外一个好处是显而易见的,你可以把麻烦的工作(查找DispID并且调用Invoke)扔给类向导来做,你只需要使用类向导自动生成的类就可以了。

如果你还要处理控件的事件,你应该参考MSDN中的这篇文章

Handling HTML Element Events

捕获ActiveX控件的事件的方法基本和文章中一样,只是你需要捕获事件对象的接口应该是控件接口,而不是元素接口。获得控件的IDispatch接口的方法前面已经说过了。

顺便说一下,在HTML编程中容易犯的错误是混用不同类型的接口,比如

IHTMLElement *pElem = NULL;
if(pAllElem->item(name, name, (LPDISPATCH*)&pElem)==S_OK)
    ......
注意,虽然微软的文档说IHTMLElement是从IDispatch派生的(Inherits from IDispatch),但是这并不代表一些返回IDispatch的方法返回的就是派生的接口,上面的代码就是犯了这个错误,把返回的接口直接当IHTMLElement接口用,肯定会出错。正确的访问方式应该是调用返回的IDispatch的QueryInterface接口以获得指定类型的接口指针。参见CDHtmlDialog::GetElement的代码。

参考

要更多信息,单击下面的连接查看CSDN文档库中的文章

用 MSHTML 的一点经验 111222(原作)

如何 :在ActiveX控件中获得顶层IWebBrowser2接口    jiangsheng(翻译)

在对话框中使用网页输入数据 (jiangsheng原创)

单击下面的连接查看MSDN文档库中的文章

Handling HTML Element Events (英文站点)

要更多关于开发基于Web的Internet Explorer解决方案,请访问下列站点:

http://msdn.microsoft.com/workshop/entry.asp(英文站点)

http://msdn.microsoft.com/ie/(英文站点)

http://support.microsoft.com/highlights/iep.asp?FR=0&SD=MSDN(英文站点)

额外的查询关键字:MFC Internet Explorer MSHTML IHTMLElement IHTMLElementCollection IHTMLDocument2 IHTMLObjectElement

关键字: kbActiveX kbCtrl kbIE kbIE400 kbGrpDSInet kbie500 kbDSupport kbie501 kbie550 
文章类型 : kbhowto 
技术 : kbIEsearch kbAudDeveloper kbSDKIESearch kbIE500Search kbSDKIE400 kbSDKIE401 kbSDKIE401SP1 kbSDKIE401SP2 kbSDKIE500 kbSDKIE501 kbSDKIE550 kbIE550Search


上次更新: 2002年8月18日


相关文章
对该文的评论
MuseIn ( 2004-03-25)
看不懂
jiangsheng ( 2003-02-26)
//设置控件属性的辅助函数,通过DispID访问
void CDHtmlDialog::SetControlProperty(LPCTSTR szElementId, DISPID dispid, VARIANT *pVar)
{
    CComPtr spdispElem;
    GetControlDispatch(szElementId, &spdispElem);

    SetControlProperty(spdispElem, dispid, pVar);
}
tinytot ( 2003-02-26)
请问 怎么得到控件属性的DISPID?