Асинхронные загрузки файлов из VBA (Excel)

Я уже пробовал использовать много разных методов с этим... Тот, который работает довольно хорошо, но все еще связывает код при запуске, использует вызов api:

Private Declare Function URLDownloadToFile Lib "urlmon" _
Alias "URLDownloadToFileA" _
(ByVal pCaller As Long, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As Long, _
ByVal lpfnCB As Long) As Long

и

IF URLDownloadToFile(0, "URL", "FilePath", 0, 0) Then
End If

Я также использовал (успешно) код для записи vbscript из Excel, а затем запускал с ним wscript и ждал обратного вызова. Но опять же это не совсем асинхронно и все еще связывает некоторые из кода.

Я хотел бы, чтобы файлы загружались в случае управляемый класс и код VBA могут делать другие вещи в большом цикле с "DoEvents". Когда один файл сделан, он может вызвать флаг, и код может обработать этот файл, ожидая другого.

это вытягивание файлов excel с сайта интрасети. Если это поможет.

поскольку я уверен, что кто-то спросит, я не могу использовать ничего, кроме VBA. Это будет использоваться на рабочем месте и 90% компьютеров являются общими. Я очень сомневаюсь, что они пойдут на бизнес за счет получения меня Visual Studio тоже. Поэтому я должен работать с тем, что у меня есть.

любая помощь была бы весьма признательна.

3 ответов


вы можете сделать это, используя xmlhttp в асинхронном режиме и класс для обработки его событий:

http://www.dailydoseofexcel.com/archives/2006/10/09/async-xmlhttp-calls/

код там адресует responseText, но вы можете настроить его для использования .responseBody. Вот (синхронный) пример:

Sub FetchFile(sURL As String, sPath)
 Dim oXHTTP As Object
 Dim oStream As Object


    Set oXHTTP = CreateObject("MSXML2.XMLHTTP")
    Set oStream = CreateObject("ADODB.Stream")
    Application.StatusBar = "Fetching " & sURL & " as " & sPath
    oXHTTP.Open "GET", sURL, False
    oXHTTP.send
    With oStream
        .Type = 1 'adTypeBinary
        .Open
        .Write oXHTTP.responseBody
        .SaveToFile sPath, 2 'adSaveCreateOverWrite
        .Close
    End With
    Set oXHTTP = Nothing
    Set oStream = Nothing
    Application.StatusBar = False


End Sub

не уверен, является ли это стандартной процедурой или нет, но я не хотел слишком загромождать свой вопрос, чтобы люди, читающие его, могли понять его лучше.

но я нашел альтернативное решение моего вопроса, которое больше соответствует тому, что я изначально запрашивал. Еще раз спасибо Тиму, когда он поставил меня на правильный путь, и его использование ADODB.Поток-жизненно важная часть моего решения.

Это использует службы Microsoft WinHTTP 5.1 .DLL, которые должны быть включены в windows в той или иной версии, если не легко загрузить.

Я использую следующий код в классе под названием "HTTPRequest"

Option Explicit

Private WithEvents HTTP As WinHttpRequest
Private ADStream As ADODB.Stream
Private HTTPRequest As Boolean
Private I As Double
Private SaveP As String

Sub Main(ByVal URL As String)
HTTP.Open "GET", URL, True
HTTP.send
End Sub

Private Sub Class_Initialize()
Set HTTP = New WinHttpRequest
Set ADStream = New ADODB.Stream
End Sub

Private Sub HTTP_OnError(ByVal ErrorNumber As Long, ByVal ErrorDescription As String)
Debug.Print ErrorNumber
Debug.Print ErrorDescription
End Sub


Private Sub HTTP_OnResponseFinished()
    'Tim's code Starts'
    With ADStream
        .Type = 1
        .Open
        .Write HTTP.responseBody
        .SaveToFile SaveP, 2
        .Close
    End With
    'Tim's code Ends'

HTTPRequest = True
End Sub

Private Sub HTTP_OnResponseStart(ByVal Status As Long, ByVal ContentType As String)
End Sub

Private Sub Class_Terminate()
Set HTTP = Nothing
Set ADStream = Nothing
End Sub

Property Get RequestDone() As Boolean
RequestDone = HTTPRequest
End Property

Property Let SavePath(ByVal SavePath As String)
SaveP = SavePath
End Property

основное различие между этим и тем, что описывал Тим, заключается в том, что WINHTTPRequest имеет свои собственные встроенные события, которые я могу завернуть в один аккуратный маленький класс и повторно использовать везде. Для меня это более элегантное решение, чем вызов XMLHttp, а затем передача его классу, чтобы дождаться его.

имея его, завернутый в класс как будто это означает, что я могу сделать что-то в этом роде..

Dim HTTP(10) As HTTPRequest
Dim URL(2, 10) As String
Dim I As Integer, J As Integer, Z As Integer, X As Integer

    While Not J > I
        For X = 1 To I
            If Not TypeName(HTTP(X)) = "HTTPRequest" And Not URL(2, X) = Empty Then
                Set HTTP(X) = New HTTPRequest
                HTTP(X).SavePath = URL(2, X)
                HTTP(X).Main (URL(1, X))
                Z = Z + 1
            ElseIf TypeName(HTTP(X)) = "HTTPRequest" Then
                If Not HTTP(X).RequestDone Then
                    Exit For
                Else
                    J = J + 1
                    Set HTTP(X) = Nothing
                End If
            End If
        Next
        DoEvents
    Wend 

где я просто перебираю URL () с URL (1,N) - это URL, а URL(2, N) - это место сохранения.

Я признаю, что это, вероятно, может быть немного упрощено, но пока это делает работу за меня. Просто выкидываю свое решение для всех заинтересованных.


@TheFuzzyGiggler: +1: Спасибо за обмен обратно. Я знаю его старый пост, но, возможно, я сделаю кого-то счастливым с этим дополнением к коду TheFuzzyGigglers (работает только в классах):

я добавил два свойства:

Private pCallBack as string
Private pCallingObject as object

Property Let Callback(ByVal CB_Function As String)
 pCallBack = CB_Function
End Property

Property Let CallingObject(set_me As Object)
 Set pCallbackObj = set_me
End Property

'and at the end of HTTP_OnResponseFinished()

CallByName pCallbackObj, pCallback, VbMethod

в моем классе у меня есть

 Private EntryCollection As New Collection

 Private Sub Download(ByVal fromURL As String, ByVal toPath As String)
 Dim HTTPx As HTTPRequest
 Dim i As Integer
  Set HTTPx = New HTTPRequest
  HTTPx.SavePath = toPath
  HTTPx.Callback = "HTTPCallBack"
  HTTPx.CallingObject = Me
  HTTPx.Main fromURL
  pHTTPRequestCollection.Add HTTPx
End Sub

Sub HTTPCallBack()
Dim HTTPx As HTTPRequest
Dim i As Integer
For i = pHTTPRequestCollection.Count To 1 Step -1
  If pHTTPRequestCollection.Item(i).RequestDone Then pHTTPRequestCollection.Remove i
Next
End Sub

вы можете получить доступ к объекту HTTP из HTTPCallBack и сделать здесь много красивых вещей; главное: его совершенно асинхронный сейчас и простой в использовании. Надеюсь, это поможет кому-то, как OP помог мне.

я развил это дальше в класс: check мой блог