So you're creating an Outlook macro that prompts the users to select file(s) - but you can't quite get it right. Hopefully this will help.
There seems to be a number of related questions, but I'm consolidating everything here and showing what worked for me in the end.
Outlook Application.FileDialog not found
Where is Outlook's save FileDialog?
How to implement Application.FileDialog using Outlook VBA?
FilePicker in Macro opens dialogbox in background
The most annoying thing for me was the fact that once you've implemented a workaround, the File Dialog will open in the background whenever you're not running the code from VBE directly.
Right out of the gate, the Outlook Application doesn't support VBA FileDialog object. Theoretically Outlook itself supports this since you can do File > Save As and File > Open & Export...but you can't simply call the object from VBA.
For my project - I have a sub that replaces tokens with user input, but I wanted to give folks the option of picking which Template to open. I'd recommend reading up on the FileDialog object itself as there are several helpful examples in the Microsoft documentation.
There are a number of options, but below are the 2 main workarounds I've found. I prefer the first method as it doesn't require adding a reference - meaning that the macro(s) can be more easily shared without compilation errors.
Method 1: No References Needed (hopefully)
#If VBA7 Then
Private Declare PtrSafe Function SetForegroundWindow Lib "user32" (ByVal win As LongPtr) As LongPtr
#Else
Private Declare Function SetForegroundWindow Lib "user32" (ByVal win As Long) As Long
#End If
Option Explicit
Sub CreateEmailUsingSelectedTemplate()
Dim xlApp As Object
Set xlApp = CreateObject("Excel.Application")
'MsgBox "The top-level window handle is: " & xlApp.hWnd
Dim fd As Office.FileDialog
Set fd = xlApp.FileDialog(msoFileDialogFilePicker)
Dim vrtSelectedItem As Variant
SetForegroundWindow (xlApp.hWnd)
With fd
.InitialFileName = Environ("APPDATA") & "\Microsoft\Templates\"
.Filters.Add "All Files", "*.*", 1
.Filters.Add "Templates", "*.oft", 2
.FilterIndex = 2
If .Show = -1 Then
For Each vrtSelectedItem In .SelectedItems
MsgBox "Selected item's path: " & vrtSelectedItem
'FindAndReplaceTokens CStr(vrtSelectedItem)
Next vrtSelectedItem
Else 'If the user presses Cancel...
MsgBox "Hit cancel instead of Accept"
Exit Sub
End If
End With
End Sub
Method 2: Early Binding
See FilePicker in Macro opens dialogbox in background and File dialog box not displaying on top and not visable
'Set reference to 'Microsoft Excel XX Object Library' in
'Tools > References
#If VBA7 Then
Private Declare PtrSafe Function FindWindowA Lib "user32" (ByVal class As String, ByVal caption As String) As LongPtr
Private Declare PtrSafe Function SetForegroundWindow Lib "user32" (ByVal win As LongPtr) As LongPtr
#Else
Private Declare Function FindWindowA Lib "user32" (ByVal class As String, ByVal caption As String) As Long
Private Declare Function SetForegroundWindow Lib "user32" (ByVal win As Long) As Long
#End If
Option Explicit
Sub ShowDialogBox()
Dim fd As Office.FileDialog
Dim xlApp As Excel.Application
Dim hxl As LongPtr
Dim vrtSelectedItem As Variant
Set xlApp = New Excel.Application
Set fd = xlApp.FileDialog(msoFileDialogFilePicker)
hxl = FindWindowA("XLMAIN", "EXCEL")
If Not IsNull(hxl) Then
SetForegroundWindow (hxl)
End If
If fd.Show = -1 Then
For Each vrtSelectedItem In fd.SelectedItems
MsgBox "Selected item's path: " & vrtSelectedItem
'Put your code here
Next vrtSelectedItem
Else
MsgBox "User hit cancel"
Exit Sub
'Do something different here
End If
End Sub
Related
App Versions:
Outlook: Microsoft 365 Apps for enterprise
Adobe Acrobat Pro DC: version 2022.001.20117
DISCLAIMER:
My company has disabled "save as" functionality in Outlook.
Please do not suggest any VBA methods that involve ".SaveAs".
Question:
I want to print an Outlook email to PDF using the Adobe PDF (driver?) printer:
The basic process flow I want to automate:
I will open/select an email that I want to print to PDF
I will ctrl+P to print and then select the Adobe PDF as the printer
A "Save PDF File As" dialogue box appears
Within the dialogue, set the save location and set the filename and submit
I coded steps 1-3. Step 4 is where my problems begin.
I have not found a way to simulate the dialogue box process.
I tried AppActivate & SendKeys: the code runs but then it shifts the focus back to the VBE and therefore doesn't do what I need within the print dialogue.
I tried finding VBA code that replicates the backend process of the dialogue. I think the dialogue is a function of Adobe so finding VBA to talk to the process is hard.
I am trying with step 4 to set the save location using a variable then set the filename field using a variable then click save to finish the print process.
Dialogue box and relevant fields:
Note: I save attachments using .SaveAsFile as MailItem.SaveAs does not work.
Skip to the code heading titled, "Print/save email as PDF" to get to the good stuff:
Sub saveEmail()
'================================================================================
' Initialize variables
'================================================================================
Dim olSelection As Outlook.selection
Dim myItem As Outlook.MailItem
Dim olAtt As Outlook.Attachment
Dim olTempFolder As String
Dim myDate As String: myDate = Year(Now) & Month(Now) & Day(Now) & _
Hour(Now) & Minute(Now) & Second(Now)
Dim myPrinter As String
' Assign PDF printer to variable
myPrinter = "Adobe PDF"
' Assign the window title of the save as pdf dialogue
myDialogueTitle = "Save PDF File As"
'================================================================================
' Create email download path
'================================================================================
' Get the local temp folder path
tempPath = ""
tempPath = VBA.Environ("temp")
' Add Outlook Attachments subfolder to temp path
olTempFolder = tempPath & "\Outlook Attachments"
Debug.Print olTempFolder ' Print the folder path to immediate window
' If the path exists, check to make sure path is a directory, else create
dirExists = Dir(olTempFolder, vbDirectory)
If dirExists <> "" Then
dirAttr = GetAttr(olTempFolder)
' Check if path is directory (attribute "16")
If dirAttr <> 16 Then
MsgBox "There is an error with the specified path. Check code " & _
"try again."
End If
Else
' If folder does not exist, create
MkDir (olTempFolder)
End If
'================================================================================
' Create unique folder for this run
'================================================================================
olTempFolder = olTempFolder & "\emailToPDF-" & myDate
MkDir (olTempFolder)
'================================================================================
' Save attachments from selected email
'================================================================================
Set olSelection = ActiveExplorer.selection
Set myItem = olSelection.Item(1)
For Each olAtt In myItem.Attachments
attFullPath = olTempFolder & "\" & olAtt.DisplayName
olAtt.SaveAsFile (attFullPath)
Next
'===============================================================================
' Print/save email as
'================================================================================
' Set the default printer
Set mynetwork = CreateObject("WScript.network")
mynetwork.setdefaultprinter myPrinter
' Print the email
myItem.PrintOut
' Send keystrokes to Save As dialogue
AppActivate myDialogueTitle ' Activate the printer dialogue window
SendKeys myDate, True ' Change file name to be saved
SendKeys "{F4}", True ' Activate path text box
SendKeys "^a", True ' Select all contents of path text box
SendKeys "{DEL}", True ' Delete selected contents of text box
SendKeys olTempFolder, True ' Set desired save path in the path location box
SendKeys "{ENTER}", True ' Press enter to set the path
SendKeys "{ENTER}", True ' Press enter to submit/save as
'================================================================================
'
'================================================================================
End Sub
Again, please do not suggest a solution involving the ".SaveAs" method. Our IT administrators disabled this functionality in Outlook so VBA code calling it does not work.
In the code I didn't find a place where Outlook objects declared at the beginning of the functions are declared.
Dim olSelection As Outlook.selection
Dim myItem As Outlook.MailItem
Dim olAtt As Outlook.Attachment
It seems you need to get the currently selected item in the Explorer window. Use the Selection property of the Explorer class which returns a Selection object that contains the item or items that are selected in the explorer window.
Set myOlExp = Application.ActiveExplorer
Set myOlSel = myOlExp.Selection
For x = 1 To myOlSel.Count
If myOlSel.Item(x).Class = OlObjectClass.olMail Then
Then you can deal with a selected item. The Word object model can be used for dealing with message bodies. See Chapter 17: Working with Item Bodies for more information. So, you are free to use the Document.SaveAs2 method which saves the specified document with a new name or format. Some of the arguments for this method correspond to the options in the Save As dialog box (File tab).
Also you may find the Document.ExportAsFixedFormat2 method which saves a document as PDF or XPS format.
In order to handle the Print to pdf dialog proceed in the next way:
Copy the next API functions declaration on top of the module (in the declarations area):
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) 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
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As LongPtr, _
ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As Long
There are declarations for 64 bit systems (VBA 7). It can be adjusted to work for both cases.
Use this way to deal with the dialog handlers, to change the pdf file name and press Save:
Sub handlePrintToPDF()
Dim pdfHwnd As LongPtr, hwnd1 As LongPtr, hwnd2 As LongPtr, hwnd3 As LongPtr
Dim hwndCombo As LongPtr, hwndEdit As LongPtr, hwndSave As LongPtr
Dim tempPath, olTempFolder As String, myDate As String
tempPath = VBA.Environ("temp")
olTempFolder = tempPath & "\Outlook Attachments"
myDate = Year(Now) & Month(Now) & Day(Now) & Hour(Now) & Minute(Now) & Second(Now) & ".pdf"
Do While pdfHwnd = 0
DoEvents
pdfHwnd = FindWindow("#32770", "Save PDF File As"): Debug.Print Hex(pdfHwnd)
Loop
hwnd1 = FindWindowEx(pdfHwnd, 0, "DUIViewWndClassName", vbNullString): Debug.Print Hex(hwnd1)
hwnd2 = FindWindowEx(hwnd1, 0, "DirectUIHWND", vbNullString): Debug.Print Hex(hwnd2)
hwnd3 = FindWindowEx(hwnd2, 0, "FloatNotifySink", vbNullString): Debug.Print Hex(hwnd3)
hwndCombo = FindWindowEx(hwnd3, 0, "ComboBox", vbNullString): Debug.Print Hex(hwndCombo)
hwndEdit = FindWindowEx(hwndCombo, 0, "Edit", vbNullString): Debug.Print Hex(hwndEdit)
Const WM_SETTEXT = &HC
Dim pdfFileFullName: pdfFileFullName = olTempFolder & "\" & myDate
SendMessage hwndEdit, WM_SETTEXT, 0&, ByVal "MyMail pdf" 'use here what you need as pdf docment to be saved name
hwndSave = FindWindowEx(pdfHwnd, 0, vbNullString, "&Save"): Debug.Print Hex(hwndSave)
Const WM_LBUTTON_DOWN = &H201, BM_CLICK = &HF5
SendMessage hwndSave, WM_LBUTTON_DOWN, 0&, 0&
SendMessage hwndSave, BM_CLICK, 0, ByVal 0&
End Sub
Call the above sub after myItem.PrintOut:
handlePrintToPDF
But launching the print window (from Outlook) will block VBA. I used it as an automation from Excel...
I am using below code to open an email item (with specific conditions).
I need after that to maximize the opened outlook email window and set focus for it to be foreground.
Option Explicit
Option Compare Text
Public WithEvents MyItem As Outlook.MailItem
Public EventsDisable As Boolean
Private Sub Application_ItemLoad(ByVal Item As Object)
If EventsDisable = True Then Exit Sub
If Item.Class = olMail Then
Set MyItem = Item
End If
End Sub
Private Sub myItem_Open(Cancel As Boolean)
EventsDisable = True
If MyItem.Subject = "Auto Plan" And Application.ActiveExplorer.CurrentFolder.Name = "MyTemplate" Then
'Code to maximize the opened outlook email window and set focus for it to be foreground
End If
EventsDisable = False
End Sub
the following Windows API function
#If Win64 Then
Private Declare PtrSafe Function SetForegroundWindow Lib "user32" _
(ByVal hWnd As LongPtr) As LongPtr
#Else
Private Declare Function SetForegroundWindow Lib "user32" _
(ByVal hWnd As Long) As Long
#End If
Public Sub Bring_to_front()
Dim setFocus As Long
setFocus = SetForegroundWindow(xxxxxxx.hWnd)
End Sub
thanks for any useful comments and answer.
Call MailItem.Display, then activate the Inspector object by calling Inspector.Activate. Inspector object can be retrieved from MailItem.GetInspector.
One thing to keep in mind is that Windows will not bring a window to the foreground if the parent process is not in the foreground. You would need to use AttachThreadInput function for that - see https://stackoverflow.com/a/17793132/332059
You can use the SetForegroundWindow method which brings the thread that created the specified window into the foreground and activates the window. Keyboard input is directed to the window, and various visual cues are changed for the user. Alternatively you may consider using the Activate method of the Explorer or Inspector classes from the Outlook object model.
To maximize the window you could use the ShowWindow method from Windows API, here is a possible declaration in VBA:
Public Declare Function ShowWindow Lib "user32" _
(ByVal hwnd As Long, ByVal nCmdSHow As Long) As Long
private SW_MAXIMIZE as Long = 3;
private SW_MINIMIZE as Long = 6;
So, you need to pass a window handle and the SW_MAXIMIZE value as the second parameter to maximize the window. See How to minimize/maximize opened Applications for more information.
In order to activate "the opened outlook email message window" you need to "determine its handle". In order to do that you may use its caption.
Please, use the next declarations on top of a standard module (in the declarations area):
Public Const MyApp As String = "myOutlook", Sett As String = "Settings", wHwnd As String = "Wind_Hwnd" 'changed to be `Public` and keeping the handle
1.a Please copy the next API functions in the same standard module:
#If Win64 Then
Public Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Public Declare PtrSafe Function SetForegroundWindow Lib "user32" _
(ByVal hwnd As LongPtr) As LongPtr
Public Declare PtrSafe Function ShowWindow Lib "user32" _
(ByVal hwnd As LongPtr, ByVal nCmdSHow As Long) As Long
#Else
Public Declare Function FindWindow Lib "User32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public Declare Function SetForegroundWindow Lib "user32" _
(ByVal hWnd As Long) As Long
Public Declare Function ShowWindow Lib "user32" _
(ByVal hwnd As Long, ByVal nCmdSHow As Long) As Long
#End If
The above variables are necessary to supply, save and use the necessary window handle (in/from Registry)
Adapt myItem_Open in the next way:
Private Sub myItem_Open(Cancel As Boolean)
EventsDisable = True
If MyItem.Subject = "Auto Plan" And Application.ActiveExplorer.CurrentFolder.Name = "MyTemplate" Then
'Code to maximize the opened outlook email window and set focus for it to be foreground
#If Win64 Then
Dim meHwnd As LongPtr
#Else
Dim meHwnd As Long
#End If
meHwnd = FindWindow(vbNullString, MyItem.GetInspector.Caption) 'find the necessary window handle
SaveSetting MyApp, Sett, wHwnd, CStr(meHwnd) 'memorize it, converted to string
End If
EventsDisable = False
End Sub
3.1 If the mail window must be shown in foreground from VBA of another application, the declarations and API functions from above, must be also copied on top of the module keeping the necessary (following) sub.
3.2 Copy the next adapted Sub and run it (after showing the necessary mail window in Outlook, of course...):
Sub Bring_to_front()
Dim winHwnd As String, i As Long
winHwnd = GetSetting(MyApp, Sett, wHwnd, "No Value")
If winHwnd <> "No Value" Then
#If Win64 Then
Dim mailWindHwnd As LongPtr
mailWindHwnd = CLngPtr(winHwnd)
#Else
Dim mailWindHwnd As Long
mailWindHwnd = CLng(winHwnd)
#End If
SetForegroundWindow mailWindHwnd
ShowWindow mailWindHwnd, 3
End If
End Sub
Please, try it and send some feedback.
I was using sendkey to access Power Query and connect to SharePoint Folder. Everything was smooth until the Power Query Data Preview Dialog appears.
How do I allow sendkey to continue after the dialog appears? I'm using button to start macro and using Excel 2016.
Option Explicit
Sub Button1_Click()
Dim spPath As String
Dim strkeys As String
spPath = "" 'SharePoint Link
strkeys = "%APNFO" & spPath & "{Enter}{TAB 4}{Enter}"
'stops at first{Enter}, {TAB 4}{Enter} for EDIT
Call SendKeys(strkeys)
End Sub
Update
Also tried to sendkey twice with True but same result, Stops at dialog.
Option Explicit
Sub Button1_Click()
Dim spPath As String
Dim strkeys As String
Dim strkeys2 As String
spPath = ""
strkeys = "%APNFO" & spPath & "{Enter}"
strkeys2 = "{TAB 4}{Enter}"
Call SendKeys(Trim(strkeys), True)
Call SendKeys(Trim(strkeys2), True)
Debug.Print strkeys2
End Sub
Update2
I tried what #peh suggested, using sleep() and Application.wait(). I found out that once the macro is initialized, sendkey1 started and stopped by the Application.wait(). Only after the waiting time ends, then sendkey1 is being processed. And once sendkey1 started, sendkey2 also starts.
Also tried adding DoEvents, sendkey1 works perfect. However only after clicking the Cancel button, Application.wait() and sendkey2 will start.
Call SendKeys(Trim(strkeys))
Debug.Print Now & "Send Key 1"
'Do Events
Application.wait (Now + TimeValue("0:00:10"))
Call SendKeys(Trim(strkeys2), True)
Debug.Print Now & "Send Key 2"
Pannel
If the dialogue box is the same every time, or contains a consistent string of text in the caption, you may be able to use it's caption to detect when it appears using this function in a loop with a timer that searches for a reasonable amount of time for the dialogue box:
Private Function GetHandleFromPartialCaption(ByRef lWnd As Long, ByVal sCaption As String) As Boolean
Dim lhWndP As Long
Dim sStr As String
GetHandleFromPartialCaption = False
lhWndP = FindWindow(vbNullString, vbNullString) 'PARENT WINDOW
Do While lhWndP <> 0
sStr = String(GetWindowTextLength(lhWndP) + 1, Chr$(0))
GetWindowText lhWndP, sStr, Len(sStr)
sStr = Left$(sStr, Len(sStr) - 1)
If InStr(1, sStr, sCaption) > 0 Then
GetHandleFromPartialCaption = True
lWnd = lhWndP
Exit Do
End If
lhWndP = GetWindow(lhWndP, GW_HWNDNEXT)
Loop
End Function
Where sCaption is the name of your dialogue box. Then in your main body of code use:
If GetHandleFromPartialCaption(lhWndP, "Your Dialogue Box Caption") = True Then
SendKeys(....
I am on my linux box right now so I can't tinker with this to test, but you might attempt to read other properties of the window with a utility like:
https://autohotkey.com/boards/viewtopic.php?t=28220
Edit: if SendKeys absolutely won't work, and you don't want to go the UI automation route, and you don't mind a dependency, you could install AutoHotkey and script that from VBA (e.g. using the Shell() command). AHK is more robust when it comes to keyboard macro automation.
If you had a unique classname, for example, you could use FindWindowEx to get the window handle:
Module-scoped ~
#If VBA7 Then
'32-bit declare
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
#Else
'64-bit declare
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
#End If
Procedure ~
Dim appcaption as String
appcaption = "Excel"
#If VBA7 Then
Dim parenthandle as Long, childhandle as Long
#Else
Dim parenthandle as LongPtr, childhandle as LongPtr
#End If
parenthandle = FindWindow(vbNullString, appcaption)
If parenthandle Then
childhandle = GetWindow(parenthandle, GW_CHILD)1
Do Until Not childhandle
childhandle = GetWindow(childhandle, GW_HWNDNEXT)
Loop
End If
If childhandle Then
'
End If
This code is only proof of concept, as you could have muliple Excel Windows open, for example. It should give a good starting point, however.
Is there any way to check until file exists in VBA.
what I am trying to do is, making vba call asynch.
Now after I run
wshShell.Run """" & SFilename & """" & s
I want to check until file exists like this
Wait until fso.fileexists("file")
Msgbox "file is now available"
End wait!!!
is there any way in vba?
I am using word vba.
You can do it like this:
Do
If fso.FileExists("file") Then
Exit Do
End If
DoEvents 'Prevents Excel from being unresponsive
Application.Wait Now + TimeValue("0:00:01") 'wait for one second
Loop
MsgBox "file available", vbOKOnly, ""
Although this is surely not the best method
Instead of using Application.Wait, you can use sleep:
Sleep 1000 '1 Second
but you need to add this to your code to be able to use it:
#If VBA7 Then
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr) 'For 64 Bit Systems
#Else
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds as Long) 'For 32 Bit Systems
#End If
You need a timeout to avoid an endless loop. This function returns true if it found the file and false if timeout was reached.
Option Explicit
Const timeoutSeconds As Long = 128
Private localFSO As Object
Public Function FSO() As Object
If localFSO Is Nothing Then Set localFSO = CreateObject("Scripting.FileSystemObject")
Set FSO = localFSO
End Function
Public Function WaitForFileToExist(ByVal theFileName As String) As Boolean
Dim timeElapsed As Single
Dim startTime As Single
startTime = Timer
Do
If FSO.FileExists(theFileName) Then
WaitForFileToExist = True
Exit Do
End If
DoEvents
Application.Wait Now + TimeValue("0:00:01")
timeElapsed = Timer - startTime
Loop Until timeElapsed > timeoutSeconds
End Function
I'm using the following code to open a folder in min szie
Call Shell("explorer.exe" & " " & "D:\Archive\", vbMinimizedFocus)
Call Shell("explorer.exe" & " " & "D:\Shortcuts\", vbMinimizedFocus)
I would however love to to let pop up next to each other. One on the left size and one on the right. Like this
Anybody know whether there is a way to move screens after opening?
Tried And Tested [Win 7 / Excel 2010 - VBA / 1920 X 1080 (Mobile PC Display)]
Here is a very basic example on how to achieve what you want. We will be using four API's for this.
FindWindow
SetParent
SetWindowPos
GetDesktopWindow
I will not individually cover these APIs. To understand what do they do, simply click on the respective links.
LOGIC:
The newer explorer do not have Titles as I mentioned in my comments above. For example see this
However playing with Spy++, I was able to see that they had captions but were not displayed on the folder's title bar. See screenshot below.
Use FindWindow API to locate the window using it's Caption
Using SetParent, we are assigning the parent window i.e Desktop for the specified child window (Folder Window).
Reposition the window using SetWindowPos API
CODE:
Paste this code in a module and change the folder as applicable. This is a very basic code and I am not doing any error handling. I am sure you will take care of it.
Private Declare Function FindWindow Lib "user32.dll" Alias _
"FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function SetParent Lib "user32.dll" _
(ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long
Private Declare Function SetWindowPos Lib "user32.dll" _
(ByVal hwnd As Long, ByVal hWndInsertAfter As Long, _
ByVal x As Long, ByVal y As Long, ByVal cx As Long, _
ByVal cy As Long, ByVal wFlags As Long) As Long
Private Declare Function GetDesktopWindow Lib "user32" () As Long
Private Const SWP_NOZORDER As Long = &H4
Private Const SWP_SHOWWINDOW As Long = &H40
Private Sub Sample()
Dim lHwnd As Long
Dim Fldr1Path As String, Fldr2Path As String
Dim winName As String
Dim Flder1X As Long, Flder1Y As Long
Dim FlderWidth As Long, FlderHeight As Long
'~~> Folder one X,Y screen position
Flder1_X = 50: Flder1_Y = 50
'~~> Folder Width and Height. Keepping the same for both
FlderWidth = 200: FlderHeight = 200
'~~> Two Folders you want to open
Fldr1Path = "C:\Temp1"
Fldr2Path = "C:\Temp2"
'~~> The Top most folder name which is also the caption of the window
winName = GetFolderName(Fldr1Path)
'~~~> Launch the folder
Shell "explorer.exe" & " " & Fldr1Path, vbMinimizedFocus
'~~> wait for 2 seconds
Wait 2
'~~> Find the Window.
'~~> I am using `vbNullString` to make it compatible with XP
lHwnd = FindWindow(vbNullString, winName)
'~~> Set the parent as desktop
SetParent lHwnd, GetDesktopWindow()
'~~> Move the Window
SetWindowPos lHwnd, 0, Flder1_X, Flder1_Y, FlderWidth, _
FlderHeight, SWP_NOZORDER Or SWP_SHOWWINDOW
'~~> Similary for Folder 2
winName = GetFolderName(Fldr2Path)
Shell "explorer.exe" & " " & Fldr2Path, vbMinimizedFocus
Wait 2
lHwnd = FindWindow(vbNullString, winName)
SetParent lHwnd, 0
SetWindowPos lHwnd, 0, Flder1_X + FlderWidth + 10, Flder1_Y, _
FlderWidth, FlderHeight, SWP_NOZORDER Or SWP_SHOWWINDOW
MsgBox "Done"
End Sub
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer
DoEvents
Wend
End Sub
Function GetFolderName(sPath As String)
Dim MyAr
MyAr = Split(sPath, "\")
GetFolderName = MyAr(UBound(MyAr))
End Function
SCREENSHOT:(Folders arranged)
EDIT
Tried And Tested [Win XP / Excel 2003 - VBA / on VM]
Special Thanks to Peter Albert for testing this for me.
If you are working with the same 2 folders, you may can easily do this.
1- Open the two folders manually and then set the desired size and location. Close the folder.
2- Then next time you call the script, do the following
Set oShell = WScript.CreateObject("WScript.Shell")
oShell.Run "Explorer /n, D:\Archive\", 4, False
oShell.Run "Explorer /n, D:\Shortcuts\", 4, False
This will open the folder with last saved position and size.
NOTE Just tested it on my Win7 machine and it doesnt work. Turns out that Win 7 doesnt remember folder position any more (it only remembers the size). Read more about it here.