I'm using MS Access, and Internet Explorer 10
I'm attempting to automate the download of a series of documents on a daily basis. The file types can differ. Using the code below, I've managed to save the documents to a temporary folder, however I would ultimately like to 'Save As' and save the documents in a pre-determined folder with a specific name based on the file being downloaded.
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Long
Dim IE As InternetExplorer
Dim h As LongPtr
'Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
Sub Download(IE As InternetExplorer)
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Dim h As Long
Dim iCnd As IUIAutomationCondition
Dim Button As IUIAutomationElement
Dim InvokePattern As IUIAutomationInvokePattern
On Error GoTo errorh
Set o = New CUIAutomation
h = IE.hwnd
h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then Exit Sub
Set e = o.ElementFromHandle(ByVal h)
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "Save")
'Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
exitsub:
Exit Sub
errorh:
MsgBox Err.Number & "; " & Err.Description
Resume exitsub
End Sub
I've tried substituting 'Save' with 'Save As', 'SaveAs', etc when creating the IUIAutomationCondition UIA_NamePropertyID, and have tried different iterations of the TreeScope enumeration along with the .FindFirst and .FindAll methods of the IUIAutomationElement (FindAll results in type mismatch error).
My question is: Can this be achieved via the FindAll method of Treewalker? If either, how does one go about doing this? How does one go about finding the 'names' of UI Elements? And if the element is a child element, how does one reference it?
An alternate (and sub-par) solution for excel documents is to initiate the 'Open' of a document and save the active workbook, but the file types can differ, so this solution will only work for a specific file type.
Any help is appreciated.
For lack of a better answer, I'm posting my solution here. The 'Save As' functionality appears to be inaccessible without using SendKeys...which of course is less than optimal given that a user can easily defeat the purpose by actively working on their desktop while the process is running. Regardless, this process is initiated by calling the Download() procedure, passing the browser, the filename, and whether or not they'd care to replace the file if it exists already. If no filename is passed the default 'Save' functionality is called and the default file name will save in the default folder. This data has been accumulated and adapted from various sources both here at StackOverflow and elsewhere and should be a somewhat effective solution in MS Access.
Option Explicit
Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
Declare PtrSafe Sub Sleep Lib "kernel32" _
(ByVal dwMilliseconds As Long)
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Declare PtrSafe Function SetForegroundWindow Lib "user32" _
(ByVal hWnd As LongPtr) As Long
Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" _
(ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Public Const BM_CLICK = &HF5
Public Const WM_GETTEXT = &HD
Public Const WM_GETTEXTLENGTH = &HE
Public Sub Download(ByRef oBrowser As InternetExplorer, _
ByRef sFilename As String, _
ByRef bReplace As Boolean)
If sFilename = "" Then
Call Save(oBrowser)
Else
Call SaveAs(oBrowser, sFilename, bReplace)
End If
End Sub
'https://stackoverflow.com/questions/26038165/automate-saveas-dialouge-for-ie9-vba
Public Sub Save(ByRef oBrowser As InternetExplorer)
Dim AutomationObj As IUIAutomation
Dim WindowElement As IUIAutomationElement
Dim Button As IUIAutomationElement
Dim hWnd As LongPtr
Set AutomationObj = New CUIAutomation
hWnd = oBrowser.hWnd
hWnd = FindWindowEx(hWnd, 0, "Frame Notification Bar", vbNullString)
If hWnd = 0 Then Exit Sub
Set WindowElement = AutomationObj.ElementFromHandle(ByVal hWnd)
Dim iCnd As IUIAutomationCondition
Set iCnd = AutomationObj.CreatePropertyCondition(UIA_NamePropertyId, "Save")
Set Button = WindowElement.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
End Sub
Sub SaveAs(ByRef oBrowser As InternetExplorer, _
sFilename As String, _
bReplace As Boolean)
'https://msdn.microsoft.com/en-us/library/system.windows.automation.condition.truecondition(v=vs.110).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1
Dim AllElements As IUIAutomationElementArray
Dim Element As IUIAutomationElement
Dim InvokePattern As IUIAutomationInvokePattern
Dim iCnd As IUIAutomationCondition
Dim AutomationObj As IUIAutomation
Dim FrameElement As IUIAutomationElement
Dim bFileExists As Boolean
Dim hWnd As LongPtr
'create the automation object
Set AutomationObj = New CUIAutomation
WaitSeconds 3
'get handle from the browser
hWnd = oBrowser.hWnd
'get the handle to the Frame Notification Bar
hWnd = FindWindowEx(hWnd, 0, "Frame Notification Bar", vbNullString)
If hWnd = 0 Then Exit Sub
'obtain the element from the handle
Set FrameElement = AutomationObj.ElementFromHandle(ByVal hWnd)
'Get split buttons elements
Set iCnd = AutomationObj.CreatePropertyCondition(UIA_ControlTypePropertyId, UIA_SplitButtonControlTypeId)
Set AllElements = FrameElement.FindAll(TreeScope_Subtree, iCnd)
'There should be only 2 split buttons only
If AllElements.length = 2 Then
'Get the second split button which when clicked shows the other three Save, Save As, Save and Open
Set Element = AllElements.GetElement(1)
'click the second spin button to display Save, Save as, Save and open options
Set InvokePattern = Element.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
'Tab across from default Open to Save, down twice to click Save as
'Displays Save as window
SendKeys "{TAB}"
SendKeys "{DOWN}"
SendKeys "{ENTER}"
'Enter Data into the save as window
Call SaveAsFilename(sFilename)
bFileExists = SaveAsSave
If bFileExists Then
Call File_Already_Exists(bReplace)
End If
End If
End Sub
Private Sub SaveAsFilename(filename As String)
Dim hWnd As LongPtr
Dim Timeout As Date
Dim fullfilename As String
Dim AutomationObj As IUIAutomation
Dim WindowElement As IUIAutomationElement
'Find the Save As window, waiting a maximum of 10 seconds for it to appear
Timeout = Now + TimeValue("00:00:10")
Do
hWnd = FindWindow("#32770", "Save As")
DoEvents
Sleep 200
Loop Until hWnd Or Now > Timeout
If hWnd Then
SetForegroundWindow hWnd
'create the automation object
Set AutomationObj = New CUIAutomation
'obtain the element from the handle
Set WindowElement = AutomationObj.ElementFromHandle(ByVal hWnd)
'Set the filename into the filename control only when one is provided, else use the default filename
If filename <> "" Then Call SaveAsSetFilename(filename, AutomationObj, WindowElement)
End If
End Sub
'Set the filename to the Save As Dialog
Private Sub SaveAsSetFilename(ByRef sFilename As String, ByRef AutomationObj As IUIAutomation, _
ByRef WindowElement As IUIAutomationElement)
Dim Element As IUIAutomationElement
Dim ElementArray As IUIAutomationElementArray
Dim iCnd As IUIAutomationCondition
'Set the filename control
Set iCnd = AutomationObj.CreatePropertyCondition(UIA_AutomationIdPropertyId, "FileNameControlHost")
Set ElementArray = WindowElement.FindAll(TreeScope_Subtree, iCnd)
If ElementArray.length <> 0 Then
Set Element = ElementArray.GetElement(0)
'should check that it is enabled
'Update the element
Element.SetFocus
' Delete existing content in the control and insert new content.
SendKeys "^{HOME}" ' Move to start of control
SendKeys "^+{END}" ' Select everything
SendKeys "{DEL}" ' Delete selection
SendKeys sFilename
End If
End Sub
'Get the window text
Private Function Get_Window_Text(hWnd As LongPtr) As String
'Returns the text in the specified window
Dim Buffer As String
Dim length As Long
Dim result As Long
SetForegroundWindow hWnd
length = SendMessage(hWnd, WM_GETTEXTLENGTH, 0, 0)
Buffer = Space(length + 1) '+1 for the null terminator
result = SendMessage(hWnd, WM_GETTEXT, Len(Buffer), ByVal Buffer)
Get_Window_Text = Left(Buffer, length)
End Function
'Click Save on the Save As Dialog
Private Function SaveAsSave() As Boolean
'Click the Save button in the Save As dialogue, returning True if the ' already exists'
'window appears, otherwise False
Dim hWndButton As LongPtr
Dim hWndSaveAs As LongPtr
Dim hWndConfirmSaveAs As LongPtr
Dim Timeout As Date
'Find the Save As window, waiting a maximum of 10 seconds for it to appear
Timeout = Now + TimeValue("00:00:10")
Do
hWndSaveAs = FindWindow("#32770", "Save As")
DoEvents
Sleep 200
Loop Until hWndSaveAs Or Now > Timeout
If hWndSaveAs Then
SetForegroundWindow hWndSaveAs
'Get the child Save button
hWndButton = FindWindowEx(hWndSaveAs, 0, "Button", "&Save")
End If
If hWndButton Then
'Click the Save button
Sleep 100
SetForegroundWindow hWndButton
PostMessage hWndButton, BM_CLICK, 0, 0
End If
'Set function return value depending on whether or not the ' already exists' popup window exists
Sleep 500
hWndConfirmSaveAs = FindWindow("#32770", "Confirm Save As")
If hWndConfirmSaveAs Then
SaveAsSave = True
Else
SaveAsSave = False
End If
End Function
'Addresses the case when saving the file when it already exists.
'The file can be overwritten if Replace boolean is set to True
Private Sub File_Already_Exists(Replace As Boolean)
'Click Yes or No in the ' already exists. Do you want to replace it?' window
Dim hWndSaveAs As LongPtr
Dim hWndConfirmSaveAs As LongPtr
Dim AutomationObj As IUIAutomation
Dim WindowElement As IUIAutomationElement
Dim Element As IUIAutomationElement
Dim iCnd As IUIAutomationCondition
Dim InvokePattern As IUIAutomationInvokePattern
hWndConfirmSaveAs = FindWindow("#32770", "Confirm Save As")
Set AutomationObj = New CUIAutomation
Set WindowElement = AutomationObj.ElementFromHandle(ByVal hWndConfirmSaveAs)
If hWndConfirmSaveAs Then
If Replace Then
Set iCnd = AutomationObj.CreatePropertyCondition(UIA_NamePropertyId, "Yes")
Else
Set iCnd = AutomationObj.CreatePropertyCondition(UIA_NamePropertyId, "No")
End If
Set Element = WindowElement.FindFirst(TreeScope_Subtree, iCnd)
Set InvokePattern = Element.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
End If
End Sub
Public Sub WaitSeconds(intSeconds As Integer)
On Error GoTo Errorh
Dim datTime As Date
datTime = DateAdd("s", intSeconds, Now)
Do
Sleep 100
DoEvents
Loop Until Now >= datTime
exitsub:
Exit Sub
Errorh:
MsgBox "Error: " & Err.Number & ". " & Err.Description, , "WaitSeconds"
Resume exitsub
End Sub
References:
SaveasDialog
True Condition
Faidootdoot
Well, I reached this question by googling for FileNameControlHost keyword because save file dialog automation stopped to work in Windows 10 (it worked in Windows 7). And automation code with SendKeys would not work for paths with non-ASCII symbols.
The code would look like:
public void SetSaveDialogFilePath(string filePath)
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
var fileNameElement = app.FindFirst(TreeScope.Subtree, new AndCondition(
new PropertyCondition(AutomationElement.ClassNameProperty, "AppControlHost"),
new PropertyCondition(AutomationElement.AutomationIdProperty, "FileNameControlHost")));
var valuePattern = (ValuePattern)fileNameElement.GetCurrentPattern(ValuePattern.Pattern);
fileNameElement.SetFocus();
valuePattern.SetValue(filePath);
Thread.Sleep(100);
// Even if text value is set we have to select it from drop down as well otherwise it is not applied
var expandPattern = (ExpandCollapsePattern)fileNameElement.GetCurrentPattern(ExpandCollapsePattern.Pattern);
if (expandPattern != null)
{
expandPattern.Expand();
AutomationElement item = null;
while (item == null)
{
Thread.Sleep(10);
item = fileNameElement.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.NameProperty, filePath));
}
((SelectionItemPattern)item.GetCurrentPattern(SelectionItemPattern.Pattern)).Select();
expandPattern.Collapse();
}
var button = app.FindFirst(TreeScope.Subtree, new AndCondition(
new PropertyCondition(AutomationElement.ClassNameProperty, "Button"),
new PropertyCondition(AutomationElement.AutomationIdProperty, "1")));
((TogglePattern)button.GetCurrentPattern(TogglePattern.Pattern)).Toggle();
}
This is working for me.
Add this to top of your function
Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
after your code Add
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Set o = New CUIAutomation
Dim h As Long
h = IE.hWnd
h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then Exit Sub
Set e = o.ElementFromHandle(ByVal h)
Dim iCnd As IUIAutomationCondition
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "Save")
Dim Button As IUIAutomationElement
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
References need :
UIautomationclient
microsoft DAo3.6 object library
UIautomationclientpriv
microsoft html object library
microsoft internet controls
Related
I created a tool that clicks an item on a webpage in IE, then the webpage dialog box pops up, I need to put data in the blank field of the dialog box using VBA.
I can't view the source code of the dialog box manually (right click). The webpage and URL are confidential, so I can't share that. I am using FindWindow function to find a webpage dialog box, and it returns the HWND value successfully. Here is my code:
Sub FindWebDialog()
Dim hwnd As Long
hwnd = FindWindow(vbNullString, "Live Payments -- Webpage Dialog")
If hwnd <> 0 Then
'get the htmldocument
Else
MsgBox "no dialog found"
End If
End Sub
I think that if I get the return value of the FindWindow, from there I can retrieve the source code of the webpage dialog box, then use it to find the exact location of the blank field. I would like to know how to get the source code of the webpage dialog box using the HWND.
It's not clear what actually is that dialog box. Is it a part of the webpage document, or opened within new IE popup window? Is it modal?
Please make a screenshot of the dialog box, so that both the webpage and the dialog box titles will be visible. You may scrub the sensitive characters on the screenshot (e. g. in paint), and then upload it.
Also try to examine opened IE windows using the below code:
Option Explicit
Sub Test()
Dim oWnd As Object
For Each oWnd In CreateObject("Shell.Application").Windows
On Error Resume Next
If TypeName(oWnd.Document) = "HTMLDocument" Then
Wait oWnd
Debug.Print "URL = " & oWnd.Document.Location
Debug.Print "HWND = " & oWnd.Hwnd
Debug.Print "Title = " & oWnd.Document.Title
Debug.Print "Error = " & Err.Number
Debug.Print
End If
Next
Debug.Print "Completed"
End Sub
Sub Wait(oIE As Object)
Do While oIE.Busy Or oIE.readyState <> 4
DoEvents
Loop
Do While oIE.Document.readyState <> "complete"
DoEvents
Loop
End Sub
Do the following steps: make actions the dialog box to appear, find HWND using the code you posted in the question, then run the code shown above and share the output.
Both of them, the screenshot and the output listing + FindWindow's HWND, should make things clearer.
Sorry not responding to you questions above, been searching other sites for help.
Using the code above, did not find the dialog box, the IE dialog box is opened when a link was click inside the IE parent window and it is modal. The problem is only findwindow can find the dialog box and findwindow only returns HWND(windows handle which is a Long Integer). I found some code getting the htmldocument of the dialog box using HWND as the reference. (below)
Private Type UUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
Private Declare Function GetClassName Lib "user32" _
Alias "GetClassNameA" ( _
ByVal hWnd As Long, _
ByVal lpClassName As String, _
ByVal nMaxCount As Long) As Long
Private Declare Function EnumChildWindows Lib "user32" ( _
ByVal hWndParent As Long, _
ByVal lpEnumFunc As Long, _
lParam As Long) As Long
Private Declare Function RegisterWindowMessage Lib "user32" _
Alias "RegisterWindowMessageA" ( _
ByVal lpString As String) As Long
Private Declare Function SendMessageTimeout Lib "user32" _
Alias "SendMessageTimeoutA" ( _
ByVal hWnd As Long, _
ByVal msg As Long, _
ByVal wParam As Long, _
lParam As Any, _
ByVal fuFlags As Long, _
ByVal uTimeout As Long, _
lpdwResult As Long) As Long
Private Const SMTO_ABORTIFHUNG = &H2
Private Declare Function ObjectFromLresult Lib "oleacc" ( _
ByVal lResult As Long, _
riid As UUID, _
ByVal wParam As Long, _
ppvObject As Any) As Long
Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Public Function IEDOMFromhWnd(ByVal hWnd As Long) As IHTMLDocument
Dim IID_IHTMLDocument As UUID
Dim hWndChild As Long
Dim lRes As Long
Dim lMsg As Long
Dim hr As Long
If hWnd <> 0 Then
If Not IsIEServerWindow(hWnd) Then
' Find a child IE server window
EnumChildWindows hWnd, AddressOf EnumChildProc, hWnd
End If
If hWnd <> 0 Then
' Register the message
lMsg = RegisterWindowMessage("WM_HTML_GETOBJECT")
' Get the object pointer
Call SendMessageTimeout(hWnd, lMsg, 0, 0, _
SMTO_ABORTIFHUNG, 1000, lRes)
If lRes Then
' Initialize the interface ID
With IID_IHTMLDocument
.Data1 = &H626FC520
.Data2 = &HA41E
.Data3 = &H11CF
.Data4(0) = &HA7
.Data4(1) = &H31
.Data4(2) = &H0
.Data4(3) = &HA0
.Data4(4) = &HC9
.Data4(5) = &H8
.Data4(6) = &H26
.Data4(7) = &H37
End With
' Get the object from lRes
hr = ObjectFromLresult(lRes, IID_IHTMLDocument, 0, IEDOMFromhWnd)
End If
End If
End If
End Function
Public Function IsIEServerWindow(ByVal hWnd As Long) As Boolean
Dim lRes As Long
Dim sClassName As String
'Initialize the buffer
sClassName = String$(100, 0)
'Get the window class name
lRes = GetClassName(hWnd, sClassName, Len(sClassName))
sClassName = Left$(sClassName, lRes)
IsIEServerWindow = StrComp(sClassName, _
"Internet Explorer_Server", _
vbTextCompare) = 0
End Function
And use it this code to retrieve the htmldocument so I can search for the corresponding element of a text field that I need.
Sub HtmlDocFromHandle()
Dim myHandle As Long, iHtml2 As IHTMLDocument2
Dim ieobj As Object
myHandle = FindWindow(vbNullString, "TITLE OF THE WINDOW")
If myHandle <> 0 Then
Set ihtml2 = IEDOMFromhWnd(hForm)
Set ieobj = ihtml2.activeElement
Debug.Print ieobj.document
Else
MsgBox "Window not found"
End If
End Function
I'm trying to get VBA to automate saving a file from IE. Thanks to various posts on these forums, I can login, navigate pages, and click the download link. The Save prompt appears at the bottom of IE, then I'm stuck:
I've been trying to use the code samples from https://www.mrexcel.com/forum/excel-questions/502298-need-help-regarding-ie-automation-using-vba-post3272730.html#post3272730, but the second FindWindow always returns 0:
hWnd = FindWindowEx(hWnd, 0, "DUIViewWndClassName", vbNullString)
I'm using VBA 7.0 in Excel 14, and IE11.
There is advice at the top of the original post:
'Note - IE may block the download, displaying its Information Bar at
the top of the tab, and preventing this program from 'automatically
downloading the file. To prevent this, add NRLDC to IE's Trusted
sites (Tools - Internet Options - 'Security - Trusted sites - Sites)
I can't access the trusted sites list due to IT policy, but the download prompt appears, so I don't think this is the issue.
The code I've taken is from Doongie's reply, which indicates it's updated for Windows 7:
Private Sub File_Download_Click_Save()
Dim hWnd As Long
Dim timeout As Date
Debug.Print "File_Download_Click_Save"
'Find the File Download window, waiting a maximum of 30 seconds for it to appear
timeout = Now + TimeValue("00:00:30")
Do
hWnd = FindWindow("#32770", "") 'returns various numbers on different runs: 20001h 10440h
DoEvents
Sleep 200
Loop Until hWnd Or Now > timeout
Debug.Print " File Download window "; Hex(hWnd)
If hWnd Then
SetForegroundWindow hWnd
'Find the child DUIViewWndClassName window
hWnd = FindWindowEx(hWnd, 0, "DUIViewWndClassName", vbNullString) 'always returns 0
Debug.Print " DUIViewWndClassName "; Hex(hWnd)
End If
If hWnd Then
'Find the child DirectUIHWND window
hWnd = FindWindowEx(hWnd, 0, "DirectUIHWND", "")
Debug.Print " DirectUIHWND "; Hex(hWnd)
End If
If hWnd Then
'Find the child FloatNotifySink window
hWnd = FindWindowEx(hWnd, 0, "FloatNotifySink", "")
Debug.Print " FloatNotifySink "; Hex(hWnd)
End If
If hWnd Then
'Find the child ComboBox window
hWnd = FindWindowEx(hWnd, 0, "ComboBox", "")
Debug.Print " ComboBox "; Hex(hWnd)
End If
If hWnd Then
SetForegroundWindow hWnd
'Find the child Edit window
hWnd = FindWindowEx(hWnd, 0, "Edit", "")
Debug.Print " Edit "; Hex(hWnd)
End If
If hWnd Then
'Click the Save button
SetForegroundWindow hWnd
Sleep 600 'this sleep is required and 600 milliseconds seems to be the minimum that works
SendMessage hWnd, BM_CLICK, 0, 0
End If
End Sub
Is there any way (that won't get me in trouble with IT!) that I can inspect the handle numbers of the IE elements? Code inspector only shows me the page code, not IE dialogues.
Is there a list of possible element names for lpsz1 defined somewhere, as they apply to elements of IE?
Public Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Have you tried the dreaded sendkeys?
Application.SendKeys "%{S}"
Application.SendKeys "%{O}"
In my IE Automation used below code to save file from IE. Below code requires VBA reference to UIAutomationCore.dll and can be found at
%windir%/sysWow64/UIAutomationCore.dll
and enable trust access to vba by
File -> Options -> Trust Center -> Trust Center Settings -> Macro Settings -> Check Trust access to the VBA
Private Sub InvokeSaveButton(IEHwnd As Long)
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Set o = New CUIAutomation
Dim h As Long
h = IEHwnd
h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then Exit Sub
Set e = o.ElementFromHandle(ByVal h)
Dim iCnd As IUIAutomationCondition
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "Save")
Dim Button As IUIAutomationElement
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
End Sub
You could try the urlmon library. Change the url and file name + extension to what you need to.
It will probably not work on a website where you have to log in to get to the file.
Public 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
Public Function DownloadFile(URL As String, LocalFilename As String) As Boolean
Dim errValue As Long
errValue = URLDownloadToFile(0, URL, LocalFilename, 0, 0)
If errValue = 0 Then
MsgBox "Download Completed, saved at: " & LocalFilename
Else
MsgBox "There was an error downloading the file"
End If
End Function
Sub DoIt()
DownloadFile "http://www.blahblahblah.com/somefolder/somefiles.xlsx", "C:\Users\Public\Documents\SavedFile.xlsx"
End Sub
I am trying to take screenshot of the web page and paste it in Word. Following is the code written
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Private Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)
Private Const VK_SNAPSHOT As Byte = 44
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Private Const SW_SHOWMAXIMIZED = 3
Private Const VK_LCONTROL As Long = &HA2
Private Const VK_V = &H56
Private Const KEYEVENTF_KEYUP = &H2
Sub Sample()
Dim objIE As InternetExplorerMedium
Set objIE = New InternetExplorerMedium
objIE.Visible = True
objIE.navigate "https://staging-site.com/"
Do
DoEvents
Loop Until objIE.readyState = 4
Dim hwnd As Long, IECaption As String
'~~> Get the caption of IE
IECaption = objIE.document.Title
'~~> Get handle of IE
hwnd = FindWindow(IECaption, vbNullString)
If hwnd = 0 Then
MsgBox "IE Window Not found!"
Exit Sub
Else
'~~> Maximize IE
ShowWindow hwnd, SW_SHOWMAXIMIZED
End If
Sleep 3000
DoEvents
'~~> Take a snapshot
Call keybd_event(VK_SNAPSHOT, 0, 0, 0)
Dim SystemDateTime As String
sPath = Environ("USERPROFILE") & "\Desktop"
Set wordobj = CreateObject("Word.Application")
Set objDoc = wordobj.Documents.Add
SystemDateTime = Replace(Replace(Now, "/", ""), ":", "")
objDoc.SaveAs (sPath & "\Student Blue Waiver " & SystemDateTime)
wordobj.Visible = True
Set objSelection = wordobj.Selection
'Paste into Word
objSelection.Paste
objDoc.Save
End Sub
But the below variable 'hwnd' always returns 0 even though IE windows with caption is visible
hwnd = FindWindow(IECaption, vbNullString)
If hwnd = 0 Then
MsgBox "IE Window Not found!"
Exit Sub
I think IECaption is getting only the title of the window that might be an issue to FindWindow with desired title
It looks like you're passing wrong arguments to the FindWindow API function
The first argument should be "IEFrame" and the second argument should be the window name. In your code, you're passing a the doc title as classname and an empty string (vbNullString) as the window name!
NB I used CreateObject to instantiate InternetExplorer.Application class. This may be slightly different from your InternetExplorerMedium, I don't know.
I also found that I have to append " - Internet Explorer" to the caption value, otherwise it can't be found.
Option Explicit
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Sub foo()
Dim objIE As Object
Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = True
objIE.navigate "http://cnn.com"
Do
DoEvents
Loop Until objIE.readyState = 4
Dim hwnd As Long, IECaption As String
'~~> Get the caption of IE
IECaption = objIE.Document.Title & " - Internet Explorer"
'~~> Get handle of IE
hwnd = FindWindow("IEFrame", IECaption)
If hwnd = 0 Then
MsgBox "IE Window Not found!"
Exit Sub
Else
I am trying to download an excel sheet from a website. I have thus far achieved until clicking the download button automatically (web scraping). Now ie9 is popping a save as screen. How do i automate that?
You may try this as it is worked for me on IE9:
Copy file C:\Windows\System32\UIAutomationCore.dll file to users Documents i.e C:\Users\admin\Documents then add reference UIAutomationClient to your macro file.
Paste below code in your module:
Option Explicit
Dim ie As InternetExplorer
Dim h As LongPtr
Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
Sub Download()
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Set o = New CUIAutomation
h = ie.Hwnd
h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then Exit Sub
Set e = o.ElementFromHandle(ByVal h)
Dim iCnd As IUIAutomationCondition
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "Save")
Dim Button As IUIAutomationElement
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
End Sub
Try at your end.
'This is a working code for vba in excel 2007 to open a file
'But you need to add the "UIAutomationCore.dll" to be copied
'from "C:\Windows\System32\UIAutomationCore.dll" into the
'path "C:\Users\admin\Documents"
'The path where to copy may be different and you can find it when you check on
'the box for UIAutomationClient - the location is given under it.
'Tools-references
Option Explicit
Dim ie As InternetExplorer
Dim h As LONG_PTR
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As LONG_PTR, ByVal hWnd2 As LONG_PTR, ByVal lpsz1 As String, ByVal lpsz2 As String) As LONG_PTR
Sub click_open()
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Dim sh
Dim eachIE
Do
Set sh = New Shell32.Shell
For Each eachIE In sh.Windows
' Check if this is the desired URL
' Here you can use your condition except .html
' If you want to use your URL , then put the URL below in the code for condition check.
' This code will reassign your IE object with the same reference for navigation and your issue will resolve.
If InStr(1, eachIE.LocationURL, "<enter your page url>") Then
Set ie = eachIE
Exit Do
End If
Next eachIE
Loop
Set o = New CUIAutomation
h = ie.Hwnd
h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then Exit Sub
Set e = o.ElementFromHandle(ByVal h)
Dim iCnd As IUIAutomationCondition
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "Open")
Dim Button As IUIAutomationElement
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
End Sub
I sent the shortcut keys to IE11.
Note: the code will not run as you expect if IE is not the active window on your machine so it won't work while in debug mode. The shortcut keys and how to send them are below.
Shortcut key:Alt+S
VBA: Application.SendKeys "%{S}"
I'm currently working on a VBA code generator/injector that adds VBA functionality to Excel workbooks by using the VBA Extensibility. This all works fine.
However, the original code that is injected uses conditional compilation, referring to some global conditional compilation arguments:
Is there any way I can programmatically modify/add the conditional compilation arguments of a VBA project?
I checked all properties of the VBProject but couldn't find anything.
Inspired by this approach, shown by SiddharthRout, I managed to find the following solution using SendMessage and FindWindow:
Option Explicit
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetWindowTextLength Lib "user32" Alias _
"GetWindowTextLengthA" (ByVal hwnd As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Const WM_SETTEXT = &HC
Const BM_CLICK = &HF5
Public Sub subSetconditionalCompilationArguments()
Dim strArgument As String
Dim xlApp As Object
Dim wbTarget As Object
Dim lngHWnd As Long, lngHDialog As Long
Dim lngHEdit As Long, lngHButton As Long
strArgument = "PACKAGE_1 = 1"
Set xlApp = CreateObject("Excel.Application")
xlApp.Visible = False
Set wbTarget = xlApp.Workbooks.Open("C:\Temp\Sample.xlsb")
'Launch the VBA Project Properties Dialog
xlApp.VBE.CommandBars(1).FindControl(ID:=2578, recursive:=True).Execute
'Get the handle of the "VBAProject" Window
lngHWnd = FindWindow("#32770", vbNullString)
If lngHWnd = 0 Then
MsgBox "VBAProject Property Window not found!"
GoTo Finalize
End If
'Get the handle of the dialog
lngHDialog = FindWindowEx(lngHWnd, ByVal 0&, "#32770", vbNullString)
If lngHDialog = 0 Then
MsgBox "VBAProject Property Window could not be accessed!"
GoTo Finalize
End If
'Get the handle of the 5th edit box
lngHEdit = fctLngGetHandle("Edit", lngHDialog, 5)
If lngHEdit = 0 Then
MsgBox "Conditional Compilation Arguments box could not be accessed!"
GoTo Finalize
End If
'Enter new argument
SendMessage lngHEdit, WM_SETTEXT, False, ByVal strArgument
DoEvents
'Get the handle of the second button box (=OK button)
lngHButton = fctLngGetHandle("Button", lngHWnd)
If lngHButton = 0 Then
MsgBox "Could not find OK button!"
GoTo Finalize
End If
'Click the OK Button
SendMessage lngHButton, BM_CLICK, 0, vbNullString
Finalize:
xlApp.Visible = True
'Potentially save the file and close the app here
End Sub
Private Function fctLngGetHandle(strClass As String, lngHParent As Long, _
Optional Nth As Integer = 1) As Long
Dim lngHandle As Long
Dim i As Integer
lngHandle = FindWindowEx(lngHParent, ByVal 0&, strClass, vbNullString)
If Nth = 1 Then GoTo Finalize
For i = 2 To Nth
lngHandle = FindWindowEx(lngHParent, lngHandle, strClass, vbNullString)
Next
Finalize:
fctLngGetHandle = lngHandle
End Function
For Access 2000 I used:
Application.GetOption("Conditional Compilation Arguments")
for getting,
Application.SetOption("Conditional Compilation Arguments", "<arguments>")
for setting.
That's all.
The only way to affect anything in that dialog box is through SendMessage API functions, or maybe Application.SendKeys. You'd be better off declaring the constants in code, like this:
#Const PACKAGE_1 = 0
And then have your code modify the CodeModule of all your VBA components:
Dim comp As VBComponent
For Each comp In ThisWorkbook.VBProject.VBComponents
With comp.CodeModule
Dim i As Long
For i = 1 To .CountOfLines
If Left$(.Lines(i, 1), 18) = "#Const PACKAGE_1 =" Then
.ReplaceLine i, "#Const PACKAGE_1 = 1"
End If
Next i
End With
Next comp
This is how to get and set multiple arguments in Access after 2010:
To set them this is the code:
application.SetOption "Conditional Compilation Arguments","A=4:B=10"
To get them:
Application.GetOption("Conditional Compilation Arguments")
They are printed like this:
A = 4 : B = 10
That is how to test it:
Sub TestMe()
#If A = 1 Then
Debug.Print "a is 1"
#Else
Debug.Print "a is not 1"
#End If
End Sub