本文转载自我2006年在csdn发布的博客
1、 概述
在《FAQ:操纵下拉列表》中我曾写到如何调用IHTMLDocument2::createElement
和IHTMLSelectElement::add
动态为IHTMLSelectElement
添加表项:
1
2
3
4
5
6
7
8
9
10
11
12
13
//先调用IHTMLDocument2::createElement创建一个option对象,
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
);
方法来自
1
2
3
4
var oOption = document.createElement("OPTION");
oOption.text="Ferrari";
oOption.value="4";
oSelect.add(oOption);
但事实上类似的代码在C++
中却不能工作。
2、 发现
写文章时测试过脚本调用之后就放在一边了,而真正写程序用到时才发现上面的问题。MFC
中有个很好用的类CDHtmlDialog
,提供了类似于CDialog
的行为用以实现以HTML
为表现形式的对话框,像Visual Studio 7/8
的Wizard
,添加类等对话框就使用了类似的技术。CDHtmlDialog
的核心在于MFC
中被用得出神入化的宏,再加上一系列DDX
函数来实现HTML content
和应用程序的数据的数据交换。比如下面的函数可以实现读取某个能够匹配szId
的IHTMLSelectElement
中被选中的值,或选中value
所指定的那个IHTMLOptionElement
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void CDHtmlDialog::DDX_DHtml_SelectString(LPCTSTR szId, CString& value, BOOL bSave)
{
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
文档中心版本作了如下的回复:
According to MSDN, “Before you can add an element to a collection, you must create it first by using the IHTMLDocument2::createElement method”. Actually, it does not work.
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
方法的说明对这两个接口只字不提:
In Microsoft Internet Explorer 4.0, the only new elements you can create are img, area, and option. As of Internet Explorer 5, you can create all elements programmatically, except for frame and iframe. In addition, the properties of these created elements are read/write and can be accessed programmatically. Before you use new objects, you must explicitly add them to their respective collections or to the document. To insert new elements into the current document, use the
IHTMLDOMNode::insertBefore
orIHTMLDOMNode::appendChild
methods.Attributes can be included with the eTag as long as the entire string is valid HTML.
4、 示例
下面的函数演示了如何使用IHTMLOptionElementFactory
来实现前面我们希望完成的功能,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
void CMyDHtmlDialog::DDX_DHtml_SelectOptions(CDataExchange* pDX, LPCTSTR szId, CMIEStringArray& value)
{
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;
}
}
}
参考资料
《CSDN技术中心 FAQ:操纵下拉列表》(原链接已失效)
《How to operate controls in an HTML file using C++》