关键字:IHTMLOptionElementFactory,IHTMLImageElementFactory,createElement
1、概述
在《FAQ:操纵下拉列表》中我曾写到如何调用IHTMLDocument2::createElement和IHTMLSelectElement::add动态为IHTMLSelectElement添加表项:
HRESULT createElement(
BSTR eTag, //标签名,可以是img, area, option(ie4), frame, iframe(ie5)
IHTMLElement **newElem //返回对象指针
);
再调用IHTMLSelectElement::add将创建的option对象添加到列表中
HRESULT add(
IHTMLElement * element,
VARIANT before //添加到哪个位置,VT_I4类型的VARIANT
);
方法来自
var oOption = document.createElement("OPTION");
oOption.text="Ferrari";
oOption.value="4";
oSelect.add(oOption);
</SCRIPT>
但事实上类似的代码在C++中却不能工作。
2、发现
写文章时测试过脚本调用之后就放在一边了,而真正写程序用到时才发现上面的问题。MFC中有个很好用的类CDHtmlDialog,提供了类似于CDialog的行为用以实现以HTML为表现形式的对话框,像Visual Studio 7/8的的Wizard,添加类等对话框就使用了类似的技术。CDHtmlDialog的核心在于MFC中被用得出神入化的宏,再加上一系列DDX函数来实现HTML content和应用程序的数据的数据交换。比如下面的函数可以实现读取某个能够匹配szId的IHTMLSelectElement中被选中的值,或选中value所指定的那个IHTMLOptionElement。
... {
CComPtr<IHTMLDocument2> sphtmlDoc;
GetDHtmlDocument(&sphtmlDoc);
if (sphtmlDoc == NULL)
return;
COleVariant varEmpty, varIndex;
CComPtr<IHTMLSelectElement> spSelect;
CComPtr<IDispatch> spdispOption;
CComPtr<IHTMLOptionElement> spOption;
CComBSTR bstrText;
HRESULT hr = S_OK;
long lIndex=-1;
hr = GetElementInterface(szId, __uuidof(IHTMLSelectElement), (void **) &spSelect);
if (spSelect == NULL)
return;
if (bSave)
...{
// get the selected item
value.Empty();
spSelect->get_selectedIndex(&lIndex);
if (lIndex >= 0)
...{
varIndex = lIndex;
spSelect->item(varIndex, varEmpty, &spdispOption);
if (spdispOption)
...{
spdispOption->QueryInterface(__uuidof(IHTMLOptionElement), (void **) &spOption);
if (spOption)
...{
spOption->get_text(&bstrText);
if (bstrText)
value = bstrText;
}
}
}
}
else
...{
bstrText.Attach(value.AllocSysString());
lIndex = Select_FindString(spSelect, bstrText, FALSE);
spSelect->put_selectedIndex(lIndex);
}
}
但MFC只提供了比较基本的交换函数,更复杂的的数据交换则需要自己实现了。比如我有一个CStringArray,希望能以CStringArray中的每个字符串为Value动态创建IHTMLOptionElement并添加到IHTMLSelectElement,或者将所有IHTMLSelectElement中IHTMLOptionElement的Value作为字符串保存到CStringArray中,该怎么办呢?
很简单啊,用文章开头说到的方法动态创建IHTMLOptionElement不就行了。但实际上虽然程序运行不保错,但MSHTML似乎没有任何反应。看来createElement只能用脚本调用了,《FAQ: 如何动态创建并访问网页元素》一文中讨论的问题也证明了这一点。同时,在那篇文章中能解决问题的insertAdjacentHTML对于IHTMLSelectElement和IHTMLOptionElement也没有用。
3、方案
第一反应当然是上网搜了。我于是发现有个朋友在《FAQ:操纵下拉列表》的CSDN文档中心版本作了如下的回复:
See the detail in codeproject
codeproject 的这篇文章,指的是《How to operate controls in an HTML file, using C++》,其中说到应该使用IHTMLOptionElementFactory来创建IHTMLOptionElement。而IHTMLOptionElementFactory需要从IHTMLWindow2(该文说应该从Document的Script来获得IHTMLWindow2,其实从Document的parentWindow更为直接)的Option来得到,再看MSDN,我们发现同样还有一个IHTMLImageElementFactory接口(从IHTMLWindow2的Image得到)用于创建IHTMLImgElement。看来要么这两个接口有特殊的地方,要么就是历史遗留问题了。因为令人感到奇怪的是MSDN中关于IHTMLDocument2::createElement方法的说明对这两个接口只字不提:
Attributes can be included with the eTag as long as the entire string is valid HTML.
4、示例
下面的函数演示了如何使用IHTMLOptionElementFactory来实现前面我们希望完成的功能,
... {
CComPtr<IHTMLDocument2> sphtmlDoc;
GetDHtmlDocument(&sphtmlDoc);
if (sphtmlDoc == NULL)
return;
COleVariant varEmpty, varIndex;
HRESULT hr = S_OK;
CComPtr<IHTMLSelectElement> spSelect;
hr = GetElementInterface(szId, __uuidof(IHTMLSelectElement), (void **) &spSelect);
if (spSelect == NULL)
return;
if (pDX->m_bSaveAndValidate)
...{
// get the selected item
value.RemoveAll();
long length = 0;
spSelect->get_length(&length);
for (long lIndex = 0; lIndex < length; lIndex++)
...{
varIndex = lIndex;
CComPtr<IDispatch> spdispOption;
spSelect->item(varIndex, varEmpty, &spdispOption);
if (spdispOption)
...{
CComPtr<IHTMLOptionElement> spOption;
spdispOption->QueryInterface(__uuidof(IHTMLOptionElement), (void **) &spOption);
if (spOption)
...{
CComBSTR bstrText;
spOption->get_text(&bstrText);
if (bstrText)
...{
value.Add(bstrText);
}
}
}
}
}
else
...{
long length = 0;
spSelect->get_length(&length);
for (long lIndex = 0; lIndex < length; lIndex++)
...{
spSelect->remove(0);
}
CComQIPtr<IHTMLWindow2> spWindow;
if ( FAILED(sphtmlDoc->get_parentWindow(&spWindow)) || !spWindow )
return;
CComQIPtr<IHTMLOptionElementFactory> spOptionFactory;
if ( FAILED(spWindow->get_Option(&spOptionFactory)) || !spOptionFactory )
return;
// Add each items to selection
for (long lIndex = 0; lIndex < value.GetCount(); lIndex++)
...{
CString strOption = value[lIndex];
// Get current item in the dictionary
IHTMLOptionElement * pOption;
VARIANT_BOOL vt_b = lIndex == 0 ? VARIANT_TRUE : VARIANT_FALSE;
if ( FAILED(spOptionFactory->create(CComVariant(strOption), CComVariant(strOption),
CComVariant(vt_b), CComVariant(vt_b), &pOption)) || !pOption )
continue;
// Add to selection tag
if ( FAILED(spSelect->add((IHTMLElement*)pOption, CComVariant(lIndex))) )
continue;
}
}
}
参考文献:
《How to operate controls in an HTML file using C++》