I have a project that requires me to open a document in Word and wait while the document is open. The user then needs to edit the document, print it or save it and then they will close it manually. When the document (or Word itself) is closed I would like to continue my VBA script in Access.
I have the following code
If Len(Dir(sDocName)) > 0 Then
Dim wordApp As Word.Application
Dim wordDoc As Word.Document
' Launch Word
Set wordApp = New Word.Application
' Open the document
With wordApp
Set wordDoc = .Documents.Open(sDocName, , False)
' Pass data
With wordDoc
.Variables("bmArchitectCompanyName").Value = "Hello"
.Variables("bmArchitectCompanyAddress").Value = "Hello"
.Variables("bmArchitectCompanyPostCode").Value = "Hello"
.Variables("bmArchitectContactFirstName").Value = "Hello"
.Variables("bmProjectTitle").Value = ProjectTitle
.Fields.Update
End With
.Visible = True
.Activate
Do Until wordApp Is Nothing
' Wait for Word to be closed
Loop
' Display a success message
MsgBox "Success!"
End With
End If
However, the do loop never exits.
How do I check if the document I launched through VBA has been closed by the user from my VBA code?
If your are handling the Word Document in a class module or form module, try declaring a Word.Document object this way:
Dim WithEvents w As Word.Document
then you can use the Word.Document object event "Close":
Private Sub w_Close()
'Things you want to do when the Word.Document is closed
'reset object
set w=nothing
End Sub
You must not let then "container" form close or the object terminate unless w is nothing. The form or the object are quiet (or whatever you want them to do) until your Word document is closed.
You must yield to the OS using DoEvents, then check if the document was closed, otherwise coninue the DoEvents loop. The following is outline code that I unfortunately could not test.
Do
DoEvents
For i = 1 to wordApp.Documents.Count
If (wordApp.Documents(i).Name = sDocName) Then Exit For
Next i
If (i > wordApp.Documents.Count) Then Exit Loop
Loop While (True)
Note: you probably don't want to close the application because maybe the user had other documents open. Maybe you should test that.
I have found the solution...
Do While .Visible
' wait until the window is no longer visible
Loop
Does exactly what I was looking for, waits until word is closed and then continues with the MS Access VBA script.
Building on what Paul has entered this uses a different method to test if the document is open.
Sub Test()
Dim wordApp As Word.Application
Dim wordDoc As Word.Document
Dim sDocName As String
sDocName = "<Full path to document>"
Set wordApp = New Word.Application
With wordApp
Set wordDoc = .Documents.Open(sDocName, , False)
.Visible = True
.Activate
Do
DoEvents
Loop While FileIsOpen(sDocName)
End With
MsgBox "Finished"
End Sub
Public Function FileIsOpen(FullFilePath As String) As Boolean
Dim ff As Long
On Error Resume Next
ff = FreeFile()
Open FullFilePath For Input Lock Read As #ff
Close ff
FileIsOpen = (Err.Number <> 0)
On Error GoTo 0
End Function
The issue with checking to see if wordApp Is Nothing is that VBA doesn't know to dereference the object when it is closed externally. In other words, when the document to which wordApp refers is closed, the pointer to the object becomes bogus but not Nothing. To trap for the event, you can do a meaningless call to the document object in the loop, and exit if an error occurs:
On Error Resume Next
With wordDoc
Do
DoEvents
'This will only occur if there is an error:
If .Visible<>.Visible Then Exit Do
Loop
End With
Err.Clear
On Error Goto 0
I just tested with no loop or anything else. The macro code in Access waited until the instance of the Word application was closed before it finished executing. Here's what I tested, and the MsgBox only executed after I quit Word:
' Open the document
With wordApp
Set wordDoc = .Documents.Add
' Pass data
With wordDoc
.Variables("bmArchitectCompanyName").Value = "Hello"
.Variables("bmArchitectCompanyAddress").Value = "Hello"
.Variables("bmArchitectCompanyPostCode").Value = "Hello"
.Variables("bmArchitectContactFirstName").Value = "Hello"
.Content.Text = "test"
'.Variables("bmProjectTitle").Value = ProjectTitle
'.Fields.Update
End With
.Visible = True
.Activate
' Display a success message
MsgBox "Success!"
Set wordDoc = Nothing
Set wordApp = Nothing
End With
Note that you want to be sure to set the objects to Nothing or
you're likely to get error messages the next time you run the code
because it will leave the instance of WinWord open in memory.
Related
Greetings to everyone.
GOAL
I want to open flashscore.com and get data related to soccer matches.
I want all the matches of this season, so the link "Show more matches" will have to be clicked one or more times.
RESTRICTIONS
I need to do this using VBA
This site does not support Internet Explorer
I cannot install anything on the pc that will be used, so Selenium is turned down as an option
Bearing in mind all of the above, the only option left, seems to be the Microsoft XML, v6.0 library.
WHAT I 'VE TRIED
I 've read several possible duplicates to this post and tried their solutions, but nothing seemed to help so far.
Here is the code with comments explaining every situation:
Option Explicit
Sub Get_Matches()
'REFERENCE TO Microsoft XML, v6.0
Dim httpReq As New MSXML2.XMLHTTP60
Dim doc As MSHTML.HTMLDocument
Dim eleCol As Object
Dim ele As MSHTML.HTMLHtmlElement
'Open site and get html. In comments:things proposed by others but seemed to make no change.
httpReq.Open "GET", "https://www.flashscore.com/football/england/premier-league-2019-2020/results/"
'httpReq.setRequestHeader "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"
httpReq.send
Set doc = New MSHTML.HTMLDocument
doc.body.innerHTML = httpReq.responseText
'doc.body.innerHTML = httpReq.responseBody
'Tried to get the div containing the first match:
Set ele = doc.getElementById("g_1_2JDks1o7") '--> returns nothing
'Tried to get the "Show more matches" link:
Set ele = doc.getElementsByClassName("event__more")(0) '--> returns nothing
'Tried to get the first ancestor that has an id:
Set ele = doc.getElementById("live-table") '--> returns nothing
'Tried to get all <a> elements and then narrow them down till I find the "Show more matches" link:
Set eleCol = doc.getElementsByTagName("a")
For Each ele In eleCol
If ele.href Like "*[#]*" And ele.innerText = "Show more matches" Then
Exit For
End If
Next ele
ele.Click '--> I get the "Show more matches" link, but nothing seems to change
'Someone suggested firing onclick event.
ele.FireEvent "onclick" '--> did nothing
'Some people suggested waiting.
'So I tried this:
Do
DoEvents
Loop While doc.readyState = "loading"
If doc.readyState <> "complete" Then GoTo ERROR_END
Set doc = New MSHTML.HTMLDocument
doc.body.innerHTML = httpReq.responseText
'and the Delay_Code_By function below,
Set ele = doc.getElementById("live-table") '--> but still returns nothing
'Some people suggested looping, so the document gets loaded first.
Do: Set ele = doc.getElementById("live-table"): DoEvents: Loop While ele Is Nothing '--> resulted to infinite loop.
Do: Set eleCol = doc.getElementsByClassName("event__more"): DoEvents: Loop Until eleCol.Length > 0 '--> resulted to infinite loop.
'This block extracts html in a txt file in desktop, to help me see it, as it is at runtime.
'------------------------------------------------------------------------------------------
' Dim fso As Scripting.FileSystemObject
' Dim txtFile As Scripting.TextStream
' Set fso = New Scripting.FileSystemObject
' Set txtFile = fso.OpenTextFile(CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\output.txt", 2, True, TristateTrue)
' txtFile.Write httpReq.responseText
' txtFile.Close
' Set txtFile = Nothing
' Set fso = Nothing
'------------------------------------------------------------------------------------------
'Inside the txt file I found html code that onclick calls some kind of function:
'Tried to call this function:
doc.parentWindow.execScript "document.body.classList.toggle('loading', true);", "JScript" '--> throws automation error (probably there's some error with my syntax).
'I also tried to call this function:
doc.parentWindow.execScript "function(){return cjs.Api.loader.get('cjs').call(function(_cjs){loadMoreGames(_cjs);});};" '--> which did not throw error but did nothing.
Exit Sub
ERROR_END:
MsgBox "error"
End Sub
Public Sub Delay_Code_By(seconds As Integer)
Dim endTime As Date
endTime = DateAdd("s", seconds, Now)
Do While Now < endTime
DoEvents
Loop
End Sub
QUESTIONS
If there is any other option that I'm missing, other than using Microsoft XML, v6.0 library, please tell me to try it.
As far as I understand the problem is that the elements I'm trying to get are not present the moment I try to get them. If I am correct, can anyone please explain me why is this happening and a possible workaround this one? If I am wrong please point me to the right direction.Thank you.
UPDATE
I'm posting this screenshot based on #QHarr 's comment about watching the network/xhr tab.
I am attempting to run a macro that clicks on an IE webpage to download a file. When running my macro I encounter Runtime Error 91: Object variable or with block variable not set I removed my url string for security purposes
The line that is highlighted is Element.Click How can I resolve this
Sub Playthissub()
Call Scrape("myurl") '' change the website here and execute this sub.
End Sub
Sub Scrape(url As String)
Dim Browser As InternetExplorer
Dim Document As HTMLDocument
Dim Elements As IHTMLElementCollection
Dim Element As HTMLGenericElement
Set Browser = New InternetExplorerMedium
Browser.Visible = True
Browser.Navigate url
Do While Browser.Busy And Not Browser.readyState = READYSTATE_COMPLETE
DoEvents
Loop
Set Document = Browser.Document
Set Elements = Document.getElementsByTagName("navPipeline")
Element.Click
Browser.Quit
End Sub
I am trying to scrap data out of a section of a webpage. To get into the section I need to fill in a captcha security code and hit a button, but that is alright because the security code is actually written in the html of the page. So, I am creating an IE object, driving it to the webpage, getting the captcha security code, writing it in the proper box, hitting the submit button and then getting the html document so I can scrap data out of it.
Nonetheless I am executing the steps exatcly in the order I mentioned, it seems that the html document that is being gotten is not the one from the page after I pass through the captcha validation, but from the page before the captcha validation.
Would anyone know what must I do to get the correct html document and conseuently be able to scrap the data I really want? Thank you.
The subprocedure's code follows next:
'Getting National fuel prices from ANP
Sub subANPNationalFuelPrices()
'Creating variables for the URL and the HTML files
Dim urlANP As String: urlANP = "http://www.anp.gov.br/preco/prc/Resumo_Semanal_Index.asp"
Dim htmlANP1 As HTMLDocument
'Creating the IE object
Dim IE As InternetExplorer
Set IE = New InternetExplorer
IE.Visible = True
'Making sure that the webpage is fully load
IE.navigate (urlANP)
Do While IE.readyState <> READYSTATE_COMPLETE
Application.StatusBar = "Getting your data"
DoEvents
Loop
Set htmlANP1 = IE.document
'Getting the Captcha Password
Dim strCaptchaPassword As String
Dim colMyCollection As IHTMLElementCollection
Set colMyCollection = htmlANP1.getElementById("divQuadro").all
Dim objLabel As IHTMLElement
For Each objLabel In colMyCollection
strCaptchaPassword = strCaptchaPassword & objLabel.innerText
Next objLabel
'Getting the input box object and getting it the correct password
Dim objInputBox As IHTMLElement
Set objInputBox = htmlANP1.getElementById("txtValor")
objInputBox.Value = strCaptchaPassword
'Getting the submit button object and clicking it
Dim objInputButton As IHTMLElement
Set objInputButton = htmlANP1.getElementById("image1")
objInputButton.Click
'Getting the true rich data HTML
Set htmlANP1 = IE.document
'Extracting the data from the html document
Dim rngValues As range: Set rngValues = Sheet1.range("B17")
Dim strValues(35) As String
Dim dblValues(35) As Double
Dim objElement1 As IHTMLElement
Set objElement1 = htmlANP1.getElementsByTagName("TABLE")(1)
Dim colCollection1 As IHTMLElementCollection
Set colCollection1 = objElement1.all
Dim intTempCount As Integer
Dim objTempElement As IHTMLElement
intTempCount = 32
For Each objTempElement In colCollection1
Sheet1.Cells(intTempCount, 3) = objTempElement.tagName
Sheet1.Cells(intTempCount, 4) = objTempElement.innerText
intTempCount = intTempCount + 1
Next objTempElement
End sub
You are not waiting for the new webpage to load after clicking the button on the captcha. Either check the ready state of IE again or end you code here be starting a timer which starts your code off again in X seconds AND then checks the ready state of IE and Document.
I do scraping on a system using iFrame so using IE.Readystate isn't very reliable. Usually I have to wait for another element to 'exist', but using IsObject(element) hasn't been very reliable either. What I've had to do is use a loop in my main code that calls a function so if I'm waiting for something to load and I know that after the page loads, there's an element with the ID "UserName", then I do this..
...
Do Until IsErr(doc, "UserName") = False: Loop
...
Function IsErr(doc As HTMLDocument, ID As String) As Boolean
IsErr = True
On Error GoTo ExitFunction:
Debug.Print left(doc.getElementById(ID).innerHTML, 1)
IsErr = False
Exit Function
ExitFunction:
End Function
I could just do a loop statement that keeps trying to debug it, but that would be a nightmare with the error handling so if you use a separate function for the printing, it can exit the function after the error, then the loop re-initiates the function and it will do this forever until the next element exists.
I am opening a workbook from Access via VBA using the following code. I need to check whether that workbook is already open: if already open then do nothing, otherwise open it with CreateObject method. Code is as below:
Dim oAppa As Object
Dim filea As String
filea = CurrentProject.Path & "\List.xlsm"
Set oAppa = CreateObject("Excel.Application")
oAppa.Visible = True
Set owb_a = oAppa.workbooks.Open(filea, ReadOnly:=True)
Here while opening it, I need to check whether it's already opened. Please help me out, Thanks in advance.
You could loop through all open workbooks, but this would take a while...
A simple solution, taken from https://stackoverflow.com/a/9373914/2829009
Option Explicit
Sub Sample()
Dim Ret
Ret = IsWorkBookOpen("C:\myWork.xlsx")
If Ret = True Then
MsgBox "File is open"
Else
MsgBox "File is Closed"
End If
End Sub
Function IsWorkBookOpen(FileName As String)
Dim ff As Long, ErrNo As Long
On Error Resume Next
ff = FreeFile()
Open FileName For Input Lock Read As #ff
Close ff
ErrNo = Err
On Error GoTo 0
Select Case ErrNo
Case 0: IsWorkBookOpen = False
Case 70: IsWorkBookOpen = True
Case Else: Error ErrNo
End Select
End Function
I just answered a near identical question about Word. The answer would be the same (or very similar):
' Handle Error In-Line
On Error Resume Next
Set objExcel = GetObject(, "Excel.Application")
If Err.Number = 429 Then
'If we got an error, that means there was no Excel Instance
Set objExcel = CreateObject("Excel.Application")
Else
Msgbox ("You're going to need to close any open Excel docs before you can perform this function.", vbOK)
Exit Sub
End If
'Reset Error Handler
On Error GoTo 0
Of course, you can tweak what you want to do if the instance already exists, rather than just exiting the Sub.
I would like to know how can I export data from VB6 textbox to a HTML textbox? It could be a simple html page or an asp page.
for example, on my VB6 form, i have an name field. Upon clicking of a button on the VB6 form, the data in the name field will be exported to a textbox on the html page.
Thank you all for help and time for reading this.
To see this demo in action, and be able to follow it through and learn how to grab from it what you need:
Create a form with a lable over a textbox, and stick 1 command buttons on the form. Don't rename any of them - the program expects the text1, command1
The following CODE is the complete FORM CODE to copy/paste into it.
Add refernce to your project from (Project=>References)Microsoft Internet Controls,Microsoft HTML Object Library,
Option Explicit
Public TargetIE As SHDocVw.InternetExplorer
Private Sub Command1_Click() ' Send text to first IE-document found
GetTheIEObjectFromSystem
SendTextToActiveElementWithSubmitOptionSet (False)
End Sub
Private Sub Form_Load()
Me.Text1 = "This is a sample text message set and submitted programmatically" 'make text1 multiline in design
Me.Command1.Caption = "Text to the first IE browser document found"
End Sub
Public Sub GetTheIEObjectFromSystem(Optional ByVal inurl As String = ".") ' "." will be found in ALL browser URLs
Dim SWs As New SHDocVw.ShellWindows
Dim IE As SHDocVw.InternetExplorer
Dim Doc As Object
For Each IE In SWs
If TypeOf IE.Document Is HTMLDocument Then ' necessary to avoid Windows Explorer
If InStr(IE.LocationURL, inurl) > 0 Then
Set TargetIE = IE
Exit For
End If
End If
Next
Set SWs = Nothing
Set IE = Nothing
End Sub
Private Sub SendTextToActiveElementWithSubmitOptionSet(ByVal bSubmitIt As Boolean)
Dim TheActiveElement As IHTMLElement
Set TheActiveElement = TargetIE.Document.activeElement
If Not TheActiveElement.isTextEdit Then
MsgBox "Active element is not a text-input system"
Else
TheActiveElement.Value = Me.Text1.Text
Dim directParent As IHTMLElement
If bSubmitIt Then
Dim pageForm As IHTMLFormElement
Set directParent = TheActiveElement.parentElement
' find its parent FORM element by checking parent nodes up and up and up until found or BODY
Do While (UCase(directParent.tagName) <> "FORM" And UCase(directParent.tagName <> "BODY"))
Set directParent = directParent.parentElement
Loop
If UCase(directParent.tagName) = "FORM" Then
Set pageForm = directParent
pageForm.submit 'intrinsic Form-element Method
Else
MsgBox ("Error: No form unit for submitting the text on this page!")
End If
End If
Set pageForm = Nothing
Set directParent = Nothing
End If
Set TheActiveElement = Nothing
Set TargetIE = Nothing
End Sub