I have an issue with counting the right number of opened workbooks.
Concretely, I have a big excel file with my database. I have other data (sports results) from a software, and I export them. So I have my database opened, and dozens of other workbooks ("Workbook1", "Workbook2", ...), with one workbook per tournament.
My final goal is to loop workbook by workbook to copy/paste the data in my database.
But when I use MsgBox(workbooks.counts), it only returns "1".
Do you have any idea why ? What am I missing ?
Thanks in advance,
I don't know if you're still interested but I took some code from the web many moons ago (apologies, I don't have the reference any more) and modified it for the purposes of acquiring all workbooks in all Excel instances.
It may be of use to you or others. APIs are declared for 32 bit so you'll have to modify them if necessary.
Option Explicit
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 AccessibleObjectFromWindow Lib "oleacc" _
(ByVal hWnd As Long, ByVal dwId As Long, ByRef riid As GUID, _
ByRef ppvObject As Object) As Long
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Public Sub RunMe()
Dim bks As Collection
Set bks = GetAllWorkbooks
MsgBox bks.Count
End Sub
Private Function GetAllWorkbooks() As Collection
Dim id As GUID
Dim hWnd As Long
Dim hDesk As Long
Dim hXL As Long
Dim obj As Object
Dim app As Application
Dim wb As Workbook
Set GetAllWorkbooks = New Collection
'Define GUID values
'hard codes IDispatch {00020400-0000-0000-C000-000000000046}
With id
.Data1 = 132096
.Data4(0) = 192
.Data4(7) = 70
End With
'Loop through excel window handles to find applications
hWnd = FindWindowEx(0&, 0&, "XLMAIN", vbNullString)
Do While hWnd > 0
hDesk = FindWindowEx(hWnd, 0&, "XLDESK", vbNullString)
hXL = FindWindowEx(hDesk, 0&, "EXCEL7", vbNullString)
If AccessibleObjectFromWindow(hXL, &HFFFFFFF0, id, obj) = &H0 Then
'Populate return collection with the workbooks
Set app = obj.Application
For Each wb In app.Workbooks
GetAllWorkbooks.Add wb
Next
End If
hWnd = FindWindowEx(0&, hWnd, "XLMAIN", vbNullString)
Loop
End Function
Related
Switching to another application via the system ALT-Tab hotkey, while working in MS Excel on MS-Windows causes Excel to lose the keyboard focus. How to detect this?
The Deactivate or WindowDeactivate events for the objects: Application or Workbook or Worksheet objects do not fire when MS Excel loses focus this way (of course, because losing the focus is not synonymous with Deactivating the window)
Try this code, please. I found it somewhere to the internet, three years ago and only adapted to serve my needs. For instance, it could not be stopped because of a wrong declaration of UnhookWinEvent API. Take care to not monitor the focus lost or got by using a MsgBox. In this way, pressing 'OK' the focus will be received again and you will be in an infinite loop. The focus status will be returned in the active sheet, range "A1" (received focus), respectively, "A2" (lost focus):
Copy the next code on top of a module (in the declarations area):
Option Explicit
Private Const EVENT_SYSTEM_FOREGROUND = &H3&
Private Const WINEVENT_OUTOFCONTEXT = 0
Private Declare PtrSafe Function SetWinEventHook Lib "user32.dll" (ByVal eventMin As Long, _
ByVal eventMax As Long, ByVal hmodWinEventProc As LongLong, ByVal pfnWinEventProc As LongLong, _
ByVal idProcess As Long, ByVal idThread As Long, ByVal dwFlags As Long) As Long
Private Declare PtrSafe Function GetCurrentProcessId Lib "kernel32" () As Long
Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
Private Declare PtrSafe Function UnhookWinEvent Lib "user32.dll" (ByVal hWinEventHook As Long) As Long
Private handlColl As Collection
Copy the next code inside the module:
Public Sub StartMonitoring() 'it can be called from a Workbook/Worksheet event
StartFocusMonitoring
End Sub
Public Function StartFocusMonitoring() As Long
If handlColl Is Nothing Then Set handlColl = New Collection
StartFocusMonitoring = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, 0&, _
AddressOf WinEventFunc, 0, 0, WINEVENT_OUTOFCONTEXT)
handlColl.aDD StartFocusMonitoring
End Function
Public Sub StopEventHook(lHook As Long)
Dim LRet As Long
If lHook = 0 Then Exit Sub`
LRet = UnhookWinEvent(lHook)
End Sub
Public Sub StopMonitoring() 'it must be called manualy or by an event when need to stop monitoring...
'it did not work until I changed the StopEventHook declaration, using ByVal instead of ByRef
Dim vHook As Variant, lHook As Long
For Each vHook In handlColl
lHook = vHook
StopEventHook lHook
Next vHook
End Sub
Public Function WinEventFunc(ByVal HookHandle As Long, ByVal LEvent As Long, _
ByVal hWnd As Long, ByVal idObject As Long, ByVal idChild As Long, _
ByVal idEventThread As Long, ByVal dwmsEventTime As Long) As Long
'In case of an error the application will crush. So, bypassing the error is good to be done...`
On Error Resume Next
Dim thePID As Long`
If LEvent = EVENT_SYSTEM_FOREGROUND Then
GetWindowThreadProcessId hWnd, thePID
If thePID = GetCurrentProcessId Then
'Do not use here a MsgBox, because after pressing OK Excel application
'will receive focus and you will stay in an infinite loop...
Application.OnTime Now, "Event_GotFocus"
Else
Application.OnTime Now, "Event_LostFocus"
End If
End If
On Error GoTo 0
End Function
Public Sub Event_GotFocus()
Range("a1").value = "Received Focus"
Range("a2").value = ""
End Sub
Public Sub Event_LostFocus()
Range("a2").value = "Lost focus"
Range("a1").value = ""
End Sub
You must start monitoring from StartMonitoring Sub which can be called directly or through an event (Workbook_Open, for instance).
The monitoring can be stopped calling StopMonitoring Sub...
I'm trying to create a VBA script on an instance A for copying basic stuff on an instance B of Word generated by a tier program with a temporary and unpredictable name , so I'm not able to use the GetObject(Path,) to get this instance with the Path because I don't have it.
My temporary solution is a PowerShell running this command from the Instance A to get the name of all Windows with "Word" in the Title... and store it in a VBA variable to detect if the name is from an other Instance than Instance A :
Get-Process |Where-Object {$_.mainWindowTitle -like "*Word*"} |format-table mainwindowtitle
It works but I can't believe there is no way to detect all running instances of an Application directly from VBA even with an unknown path.
I tried ugly stuff like this in VBA to cross over different Instances without success:
Sub GetAllInstance()
Dim WordApp As Word.Application, wordInstance As Object
Set WordApp = GetObject(, "Word.Application")
For Each wordInstance In WordApp
MsgBox (wordInstance)
Next wordInstance
End Sub
And the Immediate Command show me that the GetObject only have information about my Instance A, resulting only 1 documents even if 3 are opened on separates instance:
?WordApp.Documents.Count
1
EDIT 20/02:
With the good advices of Cindy, I changed my approch trying to work with process, I successfully detected differents PID of my running instances with the code below:
Sub IsProcessRunning()
Dim process As String
Dim objList As Object
Dim xprocess As Variant
Dim wdApp As Word.Application
process = "Word.exe"
Set objList = GetObject("winmgmts:") _
.ExecQuery("select ProcessID from win32_process where name='" & process & "'")
For Each xprocess In objList
Debug.Print xprocess.ProcessID
AppActivate (xprocess.ProcessID)
Set wdApp = GetObject(, "Word.Application")
Debug.Print wdApp.Workbooks(1).Name
Next xprocess
End Sub
Unfortunatly, activate an application do not clear the ROT, I'm now trying to find a way to clear it and refresh it to register the new activated application in the ROT and use the GetObject with the good instance.
Finally found a solution !
With the code below, because I know where my third-software generate the temporary file with the new instance, I search the name of the file using HWND and the GetWindowText from user32 lib. It permit to me to assign the GetObject using the full path and make interaction between my two documents from two separated instances. Thanks to Cindy and Mathieu for their help:
' API declaration
Const GW_HWNDNEXT = 2
Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Sub CommandButton1_Click()
Dim openDoc As Document, sourceDoc As Document, targetDoc As Object
Dim hWndThis As Long
Dim sTitle As String
Dim xTable As Table
''' INITIALIZATION '''
'Assign the source Document
Set sourceDoc = ActiveDocument
'Detect each instance by Window Name, then assign it to different object
hWndThis = FindWindow(vbNullString, vbNullString)
While hWndThis
sTitle = Space$(255)
sTitle = Left$(sTitle, GetWindowText(hWndThis, sTitle, Len(sTitle)))
If sTitle Like "*tmp*.DOC*" Then
FileToOpen = Left(sTitle, Len(sTitle) - 8)
Set targetDoc = GetObject("C:\Users\xxxxx\AppData\Local\Temp" & "\" & FileToOpen)
GoTo EndLoop:
End If
hWndThis = GetWindow(hWndThis, GW_HWNDNEXT)
Wend
EndLoop:
End Sub
I have an application that is opened overnight by a batch job. The Workbook_Open event triggers a series of other workbooks to be updated using this code:
arrUpdateList = Array(DOWNLOAD_A, _
TRDUPDATE, _
CITUPDATE, _
FVUPDATE, _
FSUPDATE)
ThisWorkbook.Worksheets("Start").Activate
For i = LBound(arrUpdateList) To UBound(arrUpdateList)
Call UpdateItem(arrUpdateList(i))
Stop
Next i
Note: the variables in the array are simply file paths to excel documents.
Since approx. a week ago the process gets hung up because the first workbook that is opened doesn't close itself anymore. The first workbook (DOWNLOAD_A) contains the following code in its Workbook_Open event, which if i open the file manually, works perfectly.
Private Sub Workbook_Open()
Call DownloadFileAPI
DoEvents
Application.DisplayAlerts = False
Application.Quit
End Sub
How can I fix this problem? I have only been able to narrow the problem down to the fact that somehow excel doesn't close the workbook, because it either goes into an infinite loop, or the calling application somehow looses the reference. What can I do to further debug this?
For completeness's sake, here is the code in the calling workbook (the one being called up by the batchjob to kick off the process):
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessID As Long
dwThreadID As Long
End Type
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal _
hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function CreateProcessA Lib "kernel32" (ByVal _
lpApplicationName As String, ByVal lpCommandLine As String, ByVal _
lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As String, _
lpStartupInfo As STARTUPINFO, lpProcessInformation As _
PROCESS_INFORMATION) As Long
Private Declare Function CloseHandle Lib "kernel32" _
(ByVal hObject As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" _
(ByVal hProcess As Long, lpExitCode As Long) As Long
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const INFINITE = -1&
Public Function ExecCmd(cmdline$)
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
Dim ret&
' Initialize the STARTUPINFO structure:
start.cb = Len(start)
' Start the shelled application:
ret& = CreateProcessA(vbNullString, cmdline$, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, vbNullString, start, proc)
'Shelled application needs to have an applicatin.quit command to close itself
' Wait for the shelled application to finish:
ret& = WaitForSingleObject(proc.hProcess, INFINITE)
Call GetExitCodeProcess(proc.hProcess, ret&)
Call CloseHandle(proc.hThread)
Call CloseHandle(proc.hProcess)
ExecCmd = ret&
End Function
As requested the other relevant procedures:
Public Sub UpdateItem(ByVal sItem As String)
Dim arr As Variant
Dim retval As Long
Dim CurrentDay As String
CurrentDay = DateValue(Now()) & " "
'Arr is split to check if there is a time value transferred
arr = Split(sItem, "|")
If UBound(arr) > 0 Then
'CDate converts the date time into a date; then if the time of the same day has already
'expired, there will be no wait. If the date time is still to come, the process will wait
Application.Wait CDate(CurrentDay & arr(1))
End If
'Log start
Call Writelog("Start: " & arr(0))
'Start process with shellwait (what if error occurs?)
retval = ExecCmd("excel.exe " & arr(0))
DoEvents
'Log end
Call Writelog("End: " & arr(0))
Erase arr
End Sub
The problem was caused by an add-in (Thomson Reuters EIKON). It could only be resolved by completely removing the COM add-in from Excel.
I was able to resolved the issue together with a colleague. The question of how I could have further debugged this is probably best answered remains.
I'll take a stab at it though, and say, I should have continued to strip away other code that also ran ontop of excel, until reaching a completely prestine Excel Version.
I am writing a VBA code that will save a PDF file from SAP. I've reached the place where SAP asks me where I would like to save my pdf file (opens windows explorer "save as" window).At this point, VBA code stops and I need to manually input the name of the file I want to save. Then, vba continues to run...
I need help to find a way to automate this step.
A possible solution that I am thinking of(but don't know how to actually do it) is to tell vba to run a VB script that ends at save as window. Then I would send a "application.sendkeys(" ") to input the save as path.
Please advise if this is feasible. If it is, next step is I will have to dynamically modify specific lines of the vb script file (I need to loop through a list and change some values every time)
Thank you
So, it has been quite a challenge....Here is my solution to Handle a "Save as" window. It can be way simpler if you would only want to click on "Save" Button. My solution is more complicated because I specify where the file needs to be saved. To do that you need to find the right combobox, which takes a lot of iteration.
WinAPI necessary declarations:
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 GetClassName Lib "user32" Alias "GetClassNameA" _
(ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias _
"SendMessageW" (ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Any) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public Declare Function SendNotifyMessage Lib "user32" Alias "SendMessageA" ( _
ByVal hWnd As Long, _
ByVal Msg As Integer, _
ByVal ByValByValwParam As Integer, _
ByVal lParam As String) As Integer
Actual VBA Code:
Sub SaveAsWindow()
Dim Winhwnd As Long
Dim prev As Long
Dim abc As Long
Dim strText As String
Dim rty As Variant
Dim Parent As Long
Dim child As Long
Winhwnd = FindWindow(vbNullString, "Save As")
For i = 1 To 20
strText = String$(100, Chr$(0))
abc = GetClassName(Winhwnd, strText, 100)
If Left$(strText, 12) = "DirectUIHWND" Then GoTo here1
Winhwnd = FindWindowEx(Winhwnd, 0&, vbNullString, vbNullString)
Next i
here1:
Parent = Winhwnd
child = FindWindowEx(Parent, 0&, vbNullString, vbNullString)
GoTo skip 'avoid this part for the 1st run
here2:
'fix child3 and child2
If child2 = 0 Then
rty = "0&"
Else
rty = 0
End If
If child3 = 555 Then
rty = "0&"
child3 = ""
End If
skip:
For i = 1 To 20
child = FindWindowEx(Parent, child, vbNullString, vbNullString)
For x = 1 To 20
If child3 = "" Then rty = 0
child2 = FindWindowEx(child, rty, vbNullString, vbNullString)
abc = GetClassName(child2, strText, 100)
If Left$(strText, 8) = "ComboBox" Then
child3 = FindWindowEx(child2, 0&, vbNullString, vbNullString)
If child3 = 0 Then
child3 = 555
GoTo here2
Else
GoTo here3
End If
End If
Next x
Next i
here3:
'this is te filepath. will be pasted into combobox. to adapt to your needs.
SendNotifyMessage child3, &HC, 0&, "C:\Users\username\abc.pdf"
'Get again the Save button
Winhwnd = FindWindow(vbNullString, "Save As")
buttn = FindWindowEx(Winhwnd, 0, "Button", "&Save")
'click on the save button
SendMessage buttn, &HF5&, 0, 0
End Sub
2nd VBA Code : For SAP, as it turns out to be simpler due to ComboboxEx32 being used instead of Combobox.
Sub test()
Dim Winhwnd As Long
Dim strText As String
Winhwnd = FindWindow(vbNullString, "Save As")
combo = FindWindowEx(Winhwnd, 0, vbNullString, vbNullString)
For i = 1 To 20
combo = FindWindowEx(Winhwnd, combo, vbNullString, vbNullString)
strText = String$(100, Chr$(0))
abc = GetClassName(combo, strText, 100)
If Left$(strText, 12) = "ComboBoxEx32" Then GoTo here
Next i
here:
SendNotifyMessage combo, &HC, 0&, "C:\Users\username\abc.pdf"
buttn = FindWindowEx(Winhwnd, 0, "Button", "&Open")
SendMessage buttn, &HF5&, 0, 0
End Sub
Bottom line, this is not the most perfect code, but I couldn't find anything else on the web. I hope this will benefit anyone with the same problem.
I have following code to get title of current opened excel file this code working fine. I use timer to every 10 seconds if title change then add new title in list1.
So question is there any method or event to detect if title change then my code work otherwise it not work not check. timer check every 10 seconds my pc work slow if I run this code
Private Const GW_HWNDNEXT = 2
Private Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, _
ByVal wCmd As Long) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
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 GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Sub ListWins(Optional Title = "*", Optional Class = "*")
Dim hWndThis As Long
hWndThis = FindWindow(vbNullString, vbNullString)
While hWndThis
Dim sTitle As String, sClass As String
sTitle = Space$(255)
sTitle = Left$(sTitle, GetWindowText(hWndThis, sTitle, Len(sTitle)))
sClass = Space$(255)
sClass = Left$(sClass, GetClassName(hWndThis, sClass, Len(sClass)))
If sTitle Like Title And sClass Like Class Then
Debug.Print sTitle, sClass
List1.AddItem (sTitle)
End If
hWndThis = GetWindow(hWndThis, GW_HWNDNEXT)
Wend
End Sub
Private Sub Timer1_Timer()
ListWins "*.xls*"
End Sub
The answer is No. AFAIK, No there is no event as such in vb6 which will trap the title change in Excel or any other window. Also unfortunately 10 second timer might not be good. What happens if the title changes every 2 seconds? It will not retrieve all the titles
However try this alternative which does not use the Timer Control. See if your pc is still slow...
Sub Sample()
'
' ~~> Rest of your code
'
Wait 2 '<~~ Wait for 2 seconds
'
' ~~> Rest of your code
'
End Sub
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer
DoEvents
Wend
End Sub
You can use the Excel COM API to do this. Unfortunately, there is no way of getting the Excel window title - but you could easily manufacture it by appending " - Microsoft Excel". Use the FullName property if you want the complete path.
Option Explicit
Private WithEvents m_oApplication As Excel.Application
Private Sub Command_Click()
' Get a reference to the FIRST instance of the Excel application.
Set m_oApplication = GetObject(, "Excel.Application")
End Sub
Private Sub m_oApplication_NewWorkbook(ByVal Wb As Excel.Workbook)
List1.AddItem Wb.Name
End Sub
Private Sub m_oApplication_WorkbookAfterSave(ByVal Wb As Excel.Workbook, ByVal Success As Boolean)
'List1.AddItem "WorkbookAfterSave: " & Wb.FullName
List1.AddItem Wb.Name
End Sub
Private Sub m_oApplication_WorkbookOpen(ByVal Wb As Excel.Workbook)
List1.AddItem Wb.Name
End Sub