后期绑定 IHTML 元素
Late bind an IHTML element
我正在尝试创建一个后期绑定 VBA 项目来搜索网络。有一次我有以下代码(早期绑定):
Dim currPage as HTMLDocument: Set currPage = objIE.document 'where objIE is set with Set objIE = CreateObject("InternetExplorer.application")
'(late bound as it is dim'd as Object)
Dim myDiv As HTMLDivElement: Set myDiv = currPage.getElementById("fbar")
Dim elemRect As IHTMLRect: Set elemRect = myDiv.getBoundingClientRect
'Scroll until bottom of page is in view
Do Until elemRect.bottom > 0
currPage.parentWindow.scrollBy 0, 10000
Set elemRect = myDiv.getBoundingClientRect
Loop
此代码在后期绑定时变为:(或者我认为)
Dim currPage as Object: Set currPage = objIE.document
Dim myDiv As Object: Set myDiv = currPage.getElementById("fbar")
Dim elemRect As Object: Set elemRect = myDiv.getBoundingClientRect
'Scroll until bottom of page is in view
Do Until elemRect.bottom > 0
currPage.parentWindow.scrollBy 0, 10000
Set elemRect = myDiv.getBoundingClientRect
Loop
我猜问题出在 IHTMLRect
前面的 I
,其中 MSDN tells me 表示网页上没有实际对象的元素与之关联 - 因此将其分配给未指定的 Object
在代码中没有任何意义。 (这是一个完整的猜测)
无论如何,早期绑定代码工作正常,后期绑定代码在 elemRect.bottom
退出执行
为什么会这样,我该如何解决?
VBA 中的对象可以实现多个接口,您可以调用的 methods/properties 取决于您用来访问对象的接口。一个简单的例子:
' This means access the object via the IUnknown interface
' IUnknown is the interface from which all other COM
' interfaces inherit
Dim x As IUnknown
Set x = ThisWorkbook.Worksheets(1)
' Commented out as this won't compile because the
' Name property isn't defined in IUnknown
' MsgBox x.Name
' This means access the object through the default
' interface associated with the Worksheet object type
Dim w As Worksheet
Set w = x
' Now we can get to the name (same object, different interface)
MsgBox w.Name
对于 MSHTML,我猜像 getElementById
这样的方法会返回一个类似于 IHTMLElement
版本之一的接口。这意味着像IHTMLDivElement
这样的接口中定义的methods/properties无法访问。
IUnknown 有一个名为 QueryInterface 的方法,用于获取对象实现的不同接口。然而,这不能在 VBA 中直接调用,因为 VBA 方法是使用 Dim
和适当的接口,然后使用 Set
。只有在设置了必要的引用时才会编译,这反过来又违背了后期绑定的目的。
有一个使用 CallByName 的解决方法。回到工作表示例,这是可行的:
Dim x As IUnknown
Set x = ThisWorkbook.Worksheets(1)
' Commented out as this won't compile because the
' Name property isn't defined in IUnknown
' MsgBox x.Name
' Can get to the property via CallByName
MsgBox CallByName(x, "Name", VbGet)
对于 MSHTML 问题,这个有效(注意调用类型更改为 VbMethod
):
Dim elemRect As Object: Set elemRect = CallByName(myDiv, "getBoundingClientRect",
VbMethod)
stTimer = Timer
'Scroll until bottom of page is in view
Do Until elemRect.bottom > 0 Or tElapsed > timeout 'timeout after n seconds
currPage.parentWindow.scrollBy 0, 10000
Set elemRect = CallByName(myDiv, "getBoundingClientRect", VbMethod)
tElapsed = Timer - stTimer
Loop
我对 COM 对象知之甚少,所以可能还有其他我没有考虑到的问题
完整代码(针对类似问题改编自 ). Running the function repeatedly in quick succession produces errors due to IE taking time to shutdown (see this question)。如果您需要连续 运行 多个查询,请重复使用相同的 IE 对象:
Option Explicit
Public Function GOOGLE_COUNT(searchTerm As String, xRes As Long, yRes As Long, Optional timeout As Long = 10) As Long
Dim url As String
Dim objIE As Object
Dim currPage As Object
Dim stTimer As Double, tElapsed As Single
Dim valueResult As Object
'create URL to page with these image criteria
url = "https://www.google.com/search?q=" & searchTerm & _
"&tbm=isch&source=lnt&tbs=isz:ex,iszw:" & xRes & ",iszh:" & yRes
'initiating a new instance of Internet Explorer and asigning it to objIE
Set objIE = CreateObject("InternetExplorer.Application")
'Google images search
objIE.navigate url
Do While objIE.Busy = True Or objIE.readyState <> 4: DoEvents: Loop
Set currPage = objIE.document
Dim myDiv As Object: Set myDiv = currPage.getElementById("fbar")
Dim elemRect As Object: Set elemRect = CallByName(myDiv, "getBoundingClientRect", VbMethod)
stTimer = Timer
'Scroll until bottom of page is in view
Do Until elemRect.bottom > 0 Or tElapsed > timeout 'timeout after n seconds
currPage.parentWindow.scrollBy 0, 10000
Set elemRect = CallByName(myDiv, "getBoundingClientRect", VbMethod)
tElapsed = Timer - stTimer
Loop
myDiv.ScrollIntoView
'Count the images
Set valueResult = currPage.getElementById("rg_s").getElementsByTagName("IMG")
GOOGLE_COUNT = valueResult.Length
objIE.Quit
End Function
Sub foo()
MsgBox GOOGLE_COUNT("St. Mary", 1366, 768)
End Sub
我正在尝试创建一个后期绑定 VBA 项目来搜索网络。有一次我有以下代码(早期绑定):
Dim currPage as HTMLDocument: Set currPage = objIE.document 'where objIE is set with Set objIE = CreateObject("InternetExplorer.application")
'(late bound as it is dim'd as Object)
Dim myDiv As HTMLDivElement: Set myDiv = currPage.getElementById("fbar")
Dim elemRect As IHTMLRect: Set elemRect = myDiv.getBoundingClientRect
'Scroll until bottom of page is in view
Do Until elemRect.bottom > 0
currPage.parentWindow.scrollBy 0, 10000
Set elemRect = myDiv.getBoundingClientRect
Loop
此代码在后期绑定时变为:(或者我认为)
Dim currPage as Object: Set currPage = objIE.document
Dim myDiv As Object: Set myDiv = currPage.getElementById("fbar")
Dim elemRect As Object: Set elemRect = myDiv.getBoundingClientRect
'Scroll until bottom of page is in view
Do Until elemRect.bottom > 0
currPage.parentWindow.scrollBy 0, 10000
Set elemRect = myDiv.getBoundingClientRect
Loop
我猜问题出在 IHTMLRect
前面的 I
,其中 MSDN tells me 表示网页上没有实际对象的元素与之关联 - 因此将其分配给未指定的 Object
在代码中没有任何意义。 (这是一个完整的猜测)
无论如何,早期绑定代码工作正常,后期绑定代码在 elemRect.bottom
为什么会这样,我该如何解决?
VBA 中的对象可以实现多个接口,您可以调用的 methods/properties 取决于您用来访问对象的接口。一个简单的例子:
' This means access the object via the IUnknown interface
' IUnknown is the interface from which all other COM
' interfaces inherit
Dim x As IUnknown
Set x = ThisWorkbook.Worksheets(1)
' Commented out as this won't compile because the
' Name property isn't defined in IUnknown
' MsgBox x.Name
' This means access the object through the default
' interface associated with the Worksheet object type
Dim w As Worksheet
Set w = x
' Now we can get to the name (same object, different interface)
MsgBox w.Name
对于 MSHTML,我猜像 getElementById
这样的方法会返回一个类似于 IHTMLElement
版本之一的接口。这意味着像IHTMLDivElement
这样的接口中定义的methods/properties无法访问。
IUnknown 有一个名为 QueryInterface 的方法,用于获取对象实现的不同接口。然而,这不能在 VBA 中直接调用,因为 VBA 方法是使用 Dim
和适当的接口,然后使用 Set
。只有在设置了必要的引用时才会编译,这反过来又违背了后期绑定的目的。
有一个使用 CallByName 的解决方法。回到工作表示例,这是可行的:
Dim x As IUnknown
Set x = ThisWorkbook.Worksheets(1)
' Commented out as this won't compile because the
' Name property isn't defined in IUnknown
' MsgBox x.Name
' Can get to the property via CallByName
MsgBox CallByName(x, "Name", VbGet)
对于 MSHTML 问题,这个有效(注意调用类型更改为 VbMethod
):
Dim elemRect As Object: Set elemRect = CallByName(myDiv, "getBoundingClientRect",
VbMethod)
stTimer = Timer
'Scroll until bottom of page is in view
Do Until elemRect.bottom > 0 Or tElapsed > timeout 'timeout after n seconds
currPage.parentWindow.scrollBy 0, 10000
Set elemRect = CallByName(myDiv, "getBoundingClientRect", VbMethod)
tElapsed = Timer - stTimer
Loop
我对 COM 对象知之甚少,所以可能还有其他我没有考虑到的问题
完整代码(针对类似问题改编自
Option Explicit
Public Function GOOGLE_COUNT(searchTerm As String, xRes As Long, yRes As Long, Optional timeout As Long = 10) As Long
Dim url As String
Dim objIE As Object
Dim currPage As Object
Dim stTimer As Double, tElapsed As Single
Dim valueResult As Object
'create URL to page with these image criteria
url = "https://www.google.com/search?q=" & searchTerm & _
"&tbm=isch&source=lnt&tbs=isz:ex,iszw:" & xRes & ",iszh:" & yRes
'initiating a new instance of Internet Explorer and asigning it to objIE
Set objIE = CreateObject("InternetExplorer.Application")
'Google images search
objIE.navigate url
Do While objIE.Busy = True Or objIE.readyState <> 4: DoEvents: Loop
Set currPage = objIE.document
Dim myDiv As Object: Set myDiv = currPage.getElementById("fbar")
Dim elemRect As Object: Set elemRect = CallByName(myDiv, "getBoundingClientRect", VbMethod)
stTimer = Timer
'Scroll until bottom of page is in view
Do Until elemRect.bottom > 0 Or tElapsed > timeout 'timeout after n seconds
currPage.parentWindow.scrollBy 0, 10000
Set elemRect = CallByName(myDiv, "getBoundingClientRect", VbMethod)
tElapsed = Timer - stTimer
Loop
myDiv.ScrollIntoView
'Count the images
Set valueResult = currPage.getElementById("rg_s").getElementsByTagName("IMG")
GOOGLE_COUNT = valueResult.Length
objIE.Quit
End Function
Sub foo()
MsgBox GOOGLE_COUNT("St. Mary", 1366, 768)
End Sub