Goal
Have an Excel file with a "Search" button that opens a custom program. This program is used for researches. If the program is already opened when the user clicks on the button, make it popup and focus on that given program.
Current Situation
Here's the code I'm trying to use to make it work:
Search Button
Private Sub btnSearch_Click()
Dim x As Variant
Dim Path As String
If Not IsAppRunning("Word.Application") Then
Path = "C:\Tmp\MyProgram.exe"
x = Shell(Path, vbNormalFocus)
End If
End Sub
IsAppRunning()
Function IsAppRunning(ByVal sAppName) As Boolean
Dim oApp As Object
On Error Resume Next
Set oApp = GetObject(, sAppName)
If Not oApp Is Nothing Then
Set oApp = Nothing
IsAppRunning = True
End If
End Function
This code will work only when I put "Word.Application" as the executable. If I try to put "MyProgram.Application" the function will never see the program is running. How can I find that "MyProgram.exe" is currently opened?
Further more, I'd need to put the focus on it...
You can check this more directly by getting a list of open processes.
This will search based on the process name, returning true/false as appropriate.
Sub exampleIsProcessRunning()
Debug.Print IsProcessRunning("MyProgram.EXE")
Debug.Print IsProcessRunning("NOT RUNNING.EXE")
End Sub
Function IsProcessRunning(process As String)
Dim objList As Object
Set objList = GetObject("winmgmts:") _
.ExecQuery("select * from win32_process where name='" & process & "'")
IsProcessRunning = objList.Count > 0
End Function
Here's how I brought the search window to front:
Private Const SW_RESTORE = 9
Private Declare Function BringWindowToTop Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As Any, ByVal lpWindowName As Any) As Long
Private Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Private Sub btnSearch_Click()
Dim x As Variant
Dim Path As String
If IsProcessRunning("MyProgram.exe") = False Then
Path = "C:\Tmp\MyProgram.exe"
x = Shell(Path, vbNormalFocus)
Else
Dim THandle As Long
THandle = FindWindow(vbEmpty, "Window / Form Text")
Dim iret As Long
iret = BringWindowToTop(THandle)
Call ShowWindow(THandle, SW_RESTORE)
End If
End Sub
Now if the window was minimized and the user clicks the search button again, the window will simply pop up.
Just want to point out that the Window Text may change when documents are open in the application instance.
For example, I was trying to bring CorelDRAW to focus and everything would work fine so long as there wasn't a document open in Corel, if there was, I would need to pass the complete name to FindWindow() including the open document.
So, instead of just:
FindWindow("CorelDRAW 2020 (64-Bit)")
It would have to be:
FindWindow("CorelDRAW 2020 (64-Bit) - C:\CompletePath\FileName.cdr")
As that is what would be returned from GetWindowText()
Obviously this is an issue as you don't know what document a user will have open in the application, so for anyone else who may be coming here, years later, who may be experiencing the same issue, here's what I did.
Option Explicit
Private Module
Private Const EXE_NAME As String = "CorelDRW.exe"
Private Const WINDOW_TEXT As String = "CorelDRAW 2020" ' This is common with all opened documents
Private Const GW_HWNDNEXT = 2
Private Const SW_RESTORE = 9
Private Declare PtrSafe Function ShowWindow Lib "user32" (ByVal hWnd As Long, ByVal nCmdShow As Long) As Long
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 Any, ByVal lpWindowName As Any) 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
Public Sub FocusIfRunning(parAppName as String, parWindowText as String)
Dim oProcs As Object
Dim lWindowHandle As Long
Dim sWindowText As String
Dim sBuffer As String
' Create WMI object and execute a WQL query statement to find if your application
' is a running process. The query will return an SWbemObjectSet.
Set oProcs = GetObject("winmgmts:").ExecQuery("SELECT * FROM win32_process WHERE " & _
"name = '" & parAppName & "'")
' The Count property of the SWbemObjectSet will be > 0 if there were
' matches to your query.
If oProcs.Count > 0 Then
' Go through all the handles checking if the start of the GetWindowText()
' result matches your WindowText pre-file name.
' GetWindowText() needs a buffer, that's what the Space(255) is.
lWindowHandle = FindWindow(vbEmpty, vbEmpty)
Do While lWindowHandle
sBuffer = Space(255)
sWindowText = Left(sBuffer, GetWindowText(lWindowHandle, sBuffer, 255))
If Mid(sWindowText, 1, Len(parWindowText)) Like parWindowText Then Exit Do
' Get the next handle. Will return 0 when there are no more.
lWindowHandle = GetWindow(lWindowHandle, GW_HWNDNEXT)
Loop
Call ShowWindow(lWindowHandle , SW_RESTORE)
End If
End Sub
Private Sub btnFocusWindow_Click()
Call FocusIfRunning(EXE_NAME, WINDOW_TEXT)
End Sub
Hopefully somebody gets use from this and doesn't have to spend the time on it I did.
Just wanted to say thank you for this solution. Only just started playing around with code and wanted to automate my job a bit. This code will paste current selection in excel sheet into an already open application with as single click. Will make my life so much easier!!
Thanks for sharing
Public Const SW_RESTORE = 9
Public Declare Function BringWindowToTop Lib "user32" (ByVal hwnd As Long) As Long
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As Any, ByVal lpWindowName As Any) As Long
Public Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Public Sub updatepart()
'
' updatepart Macro
' copies current selection
' finds and focuses on all ready running Notepad application called Test
' pastes value into Notepad document
' Keyboard Shortcut: Ctrl+u
'
Dim data As Range
Set data = Application.Selection
If data.Count <> 1 Then
MsgBox "Selection is too large"
Exit Sub
End If
Selection.Copy
If IsProcessRunning("Notepad.EXE") = False Then
MsgBox "Notepad is down"
Else
Dim THandle As Long
THandle = FindWindow(vbEmpty, "Test - Notepad")
Dim iret As Long
iret = BringWindowToTop(THandle)
Call ShowWindow(THandle, SW_RESTORE)
End If
waittime (500)
'Call SendKeys("{F7}")
Call SendKeys("^v", True) '{F12}
Call SendKeys("{ENTER}")
End Sub
Function waittime(ByVal milliseconds As Double)
Application.Wait (Now() + milliseconds / 24 / 60 / 60 / 1000)
End Function
Function IsProcessRunning(process As String)
Dim objList As Object
Set objList = GetObject("winmgmts:") _
.ExecQuery("select * from win32_process where name='" & process & "'")
If objList.Count > 0 Then
IsProcessRunning = True
Else
IsProcessRunning = False
End If
End Function
Related
I have a project that requires user entry form to be printed, renamed and saved to a specific folder
I've gone through numerous subjects relating to .printform command on this platform and usually the solutions requires to PrintScreen, save as bitmap, paste to new workbook and use Activesheet.ExportAsFixedFormat to set as pdf......
but then
My userforms width is 603,
My userforms height is 875,
The userform requires a vertical scrollbar to view all details
So "SnapScreen" wouldn't go a long way
The current code i have uses .printform command
and asks me to choose the printer i want to print to
and if the selected printer isn't active on my computer, it aborts command
Below is the code
Option Explicit
Private Const HWND_BROADCAST As Long = &HFFFF&
Private Const WM_WININICHANGE As Long = &H1A
Private Declare PtrSafe Sub keybd_event Lib "user32" _
(ByVal bVk As Byte, ByVal bScan As Byte, _
ByVal dwFlags As Long, ByVal dwExtraInfo As LongPtr)
Private Declare PtrSafe Function SendNotifyMessage Lib "user32" Alias "SendNotifyMessageA" ( _
ByVal hwnd As LongPtr, _
ByVal Msg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Declare PtrSafe Function SetDefaultPrinter Lib "winspool.drv" Alias "SetDefaultPrinterA" ( _
ByVal pszPrinter As String) As Long
Public Sub ChangePrinter(NewPrinter As String)
SetDefaultPrinter NewPrinter
Call SendNotifyMessage(HWND_BROADCAST, _
WM_WININICHANGE, _
0, ByVal "windows")
End Sub
Private Sub btn_Print_Click()
Dim Msg As String
Dim ireply As Integer
ireply = MsgBox("Select Active-Printer", vbQuestion + vbOKCancel)
Select Case ireply
Case vbOK
GoTo line123456
Case vbCancel
Exit Sub
End Select
line123456:
Dim dir as string
Dim OldPrinter As String
Dim NewPrinter As String
OldPrinter = Left(Application.ActivePrinter, InStrRev(Application.ActivePrinter, "on ") - 2)
Application.Dialogs(xlDialogPrinterSetup).Show
NewPrinter = Left(Application.ActivePrinter, InStrRev(Application.ActivePrinter, "on ") - 2)
''folderpath (created pdf to be renamed based on textbox value)
'dir = "C:\Users\user\Documents\Almadina Related\NHIS RELATED" & Me.txt_pNameDefaultclaimsForm.Value & ".pdf"
ChangePrinter NewPrinter
On Error GoTo Endproc
Me.PrintForm
GoTo SkipExit
Endproc:
MsgBox "Sorry, print command aborted - a default printer is not selected"
Unload Me
Exit Sub
SkipExit:
ChangePrinter OldPrinter
End Sub
Kindly Help.
is it possible to print, rename and save USERFORM to a specific folder as pdf
Thank You!
I have this lovely little procedure that is supposed to either shut down the window with the Acrobat display or just one document in it. Only the design is of my own making, meaning I don't fully understand the code, but I do know that it works only partially. It will quit Adobe Acrobat in full, regardless of how many documents are displayed but it can't close just one (as the original from which it was transcribed claimed that it could and should).
Private Sub CloseReaderDC(Optional ByVal MailIdx As Integer)
Dim WinId As String
Dim Wnd As LongPtr
If MailIdx Then
WinId = AcrobatWindowID(Mail(MailIdx))
Wnd = FindWindow(vbNullString, WinId)
PostMessage Wnd, WM_CLOSE, 0, ByVal 0&
Else
WinId = AcrobatWindowID
Wnd = FindWindow(WinId, vbNullString)
SendMessage Wnd, WM_CLOSE, 0, ByVal 0&
End If
End Sub
The logic is that the parameter MailIdx identifies a file name which is sufficient to find a top window. If no value is given the app should be shut down. This part works. The other part also works, but only if there is a single document open, in which case not the document is closed but the entire application. I believe this shutdown may be caused by Acrobat Reader itself which doesn't see a reason for staying open with no document to display. I also think that the window handle may not be found if there are several documents because FindWindow finds a top window only and the one I want to close would be the second one. In practice, I tried both, to close the existing before opening another one and after. In the one case the app gets shut down, in the other nothing happens.
I don't know why my tutor uses SendMessage in one case and PostMessage in the other. I also don't know if the window I'm after is a Child Window or how to get a handle on it if it is. Any suggestions?
Edit 11 Jan 2021
I used the following code to test #FaneDuru's solution.
Private Sub Test_CloseReaderDC()
ReDim Mail(2)
Mail(0) = ""
Mail(1) = "File1.PDF"
Mail(2) = "File2.PDF"
CloseReaderDC 1
End Sub
Private Sub CloseReaderDC(Optional ByVal MailIdx As Integer)
' NIC 003 ++ 10 Jan 2021
Dim WinTitle As String
Dim WinCap As String
Dim Wnd As LongPtr
WinTitle = AcrobatWindowID
If MailIdx Then
WinCap = AcrobatWindowID(Mail(MailIdx))
Wnd = FindWindow(vbNullString, WinCap)
Debug.Print Wnd
SendMessage Wnd, WM_CloseClick, 6038, ByVal 0&
Else
Wnd = FindWindow(WinTitle, vbNullString)
Debug.Print Wnd
SendMessage Wnd, WM_CLOSE, 0, ByVal 0&
End If
End Sub
Function AcrobatWindowID(Optional ByVal Wn As String)
' NIC 003 ++ 07 Jan 2021
Dim Fun As Boolean
Fun = CBool(Len(Wn))
If Fun Then Wn = Wn & " - "
AcrobatWindowID = Wn & Split("AcrobatSDIWindow,Adobe Acrobat Reader DC", ",")(Abs(Fun))
End Function
The code worked perfectly for both 1 or 2 files, not closing the app until called with a parameter of 0. But on second try it failed to find the window and therefore took no action.
I started Acrobat and selected the 2 previously opened files from its File>Open menu. File1 was in the first tab, File2 in the second, active. Then I attempted to delete File1 which failed. Then I called the code with 2 as parameter which closed the top file. Thereafter the code found the window for File1 and closed it.
I don't think the apparent rule is followed consistently, however. How the files were opened seems to make a difference. In my project the files are opened by hyperlink, one at a time. My above test therefore is not indicative of how FaneDuru's suggestion will work in my project but it proves that the solution works.
You did not say anything about my comment regarding closing the active document by programmatically pressing the File menu "Close File" control...
This way of closing does not make Acrobat application quitting. It stay open, even if only a document was open in the moment of running the code.
So, test the next code line, please. You need the Acrobat Reader DC handler and the necessary arguments, like following:
Const WM_CloseClick = &H111
SendMessage Wnd, WM_CloseClick, 6038, ByVal 0&
6038 is the 'Close File' File menu control ID.
I could determine it using the next function:
Private Function findControlID(mainWHwnd As LongPtr, ctlNo As Long) As Long
Dim aMenu As LongPtr, sMenu As LongPtr
aMenu = GetMenu(mainWHwnd): Debug.Print "Main menu = " & Hex(aMenu)
sMenu = GetSubMenu(aMenu, 0&): Debug.Print "File menu = " & Hex(sMenu)
mCount = GetMenuItemCount(sMenu): Debug.Print "File menu no of controls: " & mCount 'check if it is 28
findControlID = GetMenuItemID(sMenu, ctlNo - 1) 'Menu controls are counted starting from 0
End Function
The above function was called in this way:
Sub testFindCloseControlID()
Dim Wnd As LongPtr
'Wnd = findWindowByPartialTitle("Adobe Acrobat Reader DC") 'you will obtain it in your way
Debug.Print findControlID(Wnd, 15) '15 means the fiftheenth control of the File menu (0)
End Sub
15 has been obtained counting the horizontal controls separators, too.
In order to find "Adobe Acrobat Reader DC" window handler I used the function mentioned above, but this does not matter too much. You may use your way of determining it...
Please, test the above way and send some comments
Edited:
In order to extract the applications menu(s) captions, I use the next declarations:
Option Explicit
'APIs for identify a window handler
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Private Declare PtrSafe Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, _
ByVal lpString As String, ByVal cch As Long) As Long
Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal wCmd As Long) As Long
'____________________________________________________
'Menu related APIs
Private Declare PtrSafe Function GetMenu Lib "user32.dll" (ByVal hwnd As LongPtr) As LongPtr
Private Declare PtrSafe Function GetSubMenu Lib "user32" (ByVal hMenu As LongPtr, ByVal nPos As Long) As LongPtr
Private Declare PtrSafe Function GetMenuItemID Lib "user32" _
(ByVal hMenu As LongPtr, ByVal nPos As Long) As Long
Private Declare PtrSafe Function GetMenuItemCount Lib "user32" (ByVal hMenu As LongPtr) As Long
Private Declare PtrSafe Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As LongPtr, _
ByVal Un As Long, ByVal b As Long, lpMenuItemInfo As MENUITEMINFO) As Long
Private Declare PtrSafe Function GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As LongPtr, _
ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag As Long) As Long
'_____________________________________________________
Private Type MENUITEMINFO
cbSize As Long
fMask As Long
fType As Long
fState As Long
wID As Long
hSubMenu As LongPtr
hbmpChecked As LongPtr
hbmpUnchecked As LongPtr
dwItemData As LongPtr
dwTypeData As String
cch As Long
hbmpItem As LongPtr
End Type
Private Const GW_HWNDNEXT = 2
And the next functions/subs:
To find any window knowing only its partial title:
Sub testFindWindByPartTitle()
Debug.Print findWindowByPartialTitle("Notepad")
End Sub
Private Function findWindowByPartialTitle(ByVal sCaption As String, Optional strSecond As String) As LongPtr
Dim lhWndP As LongPtr
Dim sStr As String
findWindowByPartialTitle = CLngPtr(0)
lhWndP = FindWindow(vbNullString, vbNullString) 'PARENT WINDOW
Do While lhWndP <> 0
sStr = String(GetWindowTextLength(lhWndP) + 1, Chr$(0))
GetWindowText lhWndP, sStr, Len(sStr)
If Len(sStr) > 0 Then sStr = left$(sStr, Len(sStr) - 1)
If InStr(1, sStr, sCaption) > 0 And _
IIf(strSecond <> "", InStr(1, sStr, strSecond) > 0, 1 = 1) Then
findWindowByPartialTitle = lhWndP
Exit Do
End If
lhWndP = GetWindow(lhWndP, GW_HWNDNEXT)
Loop
End Function
A version of extract the necessary ID by control caption, but it works only for Notepad:
Private Sub TestfindMenuItemsByCaption()
Const NotePApp As String = "Notepad"
Debug.Print findMenuIDByString(NotePApp, "Save") 'it does work
Const pdfApp As String = "Adobe Acrobat Reader DC"
Debug.Print findMenuIDByString(pdfApp, "Close") 'it does not work
End Sub
Private Function findMenuIDByString(pdfApp As String, searchString As String) As Long
Dim mainWHwnd As LongPtr, aMenu As LongPtr, mCount As Long
Dim LookFor As Long, sMenu As LongPtr, sCount As Long
Dim LookSub As Long, sID As Long, sString As String
mainWHwnd = findWindowByPartialTitle(pdfApp)
aMenu = GetMenu(mainWHwnd): Debug.Print "Main menu = " & Hex(aMenu)
sMenu = GetSubMenu(aMenu, 0): Debug.Print "File menu = " & Hex(sMenu)
sCount& = GetMenuItemCount(sMenu)
For LookSub& = 0 To sCount& - 1
sID& = GetMenuItemID(sMenu, LookSub&): Debug.Print "ID = " & sID: 'Stop
sString$ = String$(100, " ")
Call GetMenuString(sMenu, sID&, sString$, 100&, 1&) ' 1&)
Debug.Print sString$ ': Stop
If InStr(LCase(sString$), LCase(searchString$)) Then
findMenuIDByString = sID
Exit Function
End If
Next LookSub&
End Function
And a second version, unfortunately working exactly in the same way. I mean, returning the ID only for Notepad:
Private Sub TestfindMenuItemsByCaptionBis()
Const NotePApp As String = "Notepad"
Debug.Print findMenuItemIDByCaption(NotePApp, "Save")
Const pdfApp As String = "Adobe Acrobat Reader DC"
Debug.Print findMenuItemIDByCaption(pdfApp, "Close")
End Sub
Private Function findMenuItemIDByCaption(strApp As String, strCaption As String)
Dim appHwnd As LongPtr, hMenu As LongPtr, fMenu As LongPtr, i As Long
Dim retval As Long, mii As MENUITEMINFO 'mii receives information about each item
Const WM_SaveClick = &H111, MIIM_STATE = &H1, MIIM_STRING = &H40&, MIIM_ID = &H2&, MIIM_CHECKMARKS = &H8&
Const MIIM_SUBMENU = &H4&, MIIM_TYPE = &H10, MIIM_FTYPE = &H100&, MIIM_DATA = &H20&
appHwnd = findWindowByPartialTitle(strApp)
If appHwnd = 0 Then MsgBox "No application window found...": Exit Function
hMenu = GetMenu(appHwnd) 'application window Menu
fMenu = GetSubMenu(hMenu, 0) 'app window 'File' Submenu
For i = 0 To GetMenuItemCount(fMenu)
With mii
.cbSize = Len(mii)
.fMask = MIIM_STATE Or MIIM_SUBMENU Or MIIM_TYPE
.dwTypeData = space(256)
.cch = 256
retval = GetMenuItemInfo(fMenu, i, 1, mii) '2 = the third menu item
Debug.Print left(.dwTypeData, .cch)
If InStr(left(.dwTypeData, .cch), strCaption) > 0 Then
findMenuItemIDByCaption = GetMenuItemID(fMenu, i): Exit Function
End If
End With
Next i
End Function
I tried all constants as I could find, but not success... If we would find a way, a subroutine could also read the recent files list and activate the needed one, if is not the active one is the necessary one.
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 have checked related questions such as this or this one but the solutions there do not seem to solve my problem.
I am running a VBA script on my computer. The script takes a few minutes to execute and while waiting I am checking other things in my computer. To get my attention once the script has finished running, I have included a MsgBox at the end of my script. However, because Excel is not active/selected when the script finishes, I cannot see it - only when I reactivate/select Excel.
How can I bring into focus the MsgBox when Excel is not active? I have already tried the following tweaks but they do not work:
ThisWorkbook.Activate:
...
ThisWorkbook.Activate
MsgBox "..."
...
AppActivate() (this command threw an error):
...
AppActivate("Microsoft excel")
MsgBox "..."
...
How about playing a sound when the program finishes?
Place this declaration at the top of a standard code module, above any procedures existing there.
Public Declare Function Beep Lib "kernel32" _
(ByVal dwFreq As Long, _
ByVal dwDuration As Long) As Long
If you place this procedure in the same module you may not need it to be public. Adjust pitch and duration to your preference.
Sub EndSound()
Beep 500, 1000
End Sub
Then place the procedure call at the end of your program.
Call EndSound
I suppose you might use a more elaborate sound - may I suggest a couple of bars from Beethoven's 5th? Modify the EndSound procedure. Chip Pearson has more on this idea.
Try:
Application.WindowState = xlMaximized
Disclaimer: This is not my code and I do not know who the author is. I had this code in my database.
Put your code in Sub Sample(). I have shown where you can insert your code. Once the code is run, Excel will flash 5 times. you can change this number by changing Private Const NumberOfFlashes = 5
Paste this in a Module.
Option Explicit
Private Type FLASHWINFO
cbSize As Long
Hwnd As Long
dwFlags As Long
uCount As Long
dwTimeout As Long
End Type
Private Const FLASHW_STOP As Long = 0
Private Const FLASHW_CAPTION As Long = &H1
Private Const FLASHW_TRAY As Long = &H2
Private Const FLASHW_ALL As Long = (FLASHW_CAPTION Or FLASHW_TRAY)
Private Const FLASHW_TIMER As Long = &H4
Private Const FLASHW_TIMERNOFG As Long = &HC
Private FLASHW_FLAGS As Long
Private Declare Function LoadLibrary Lib "kernel32" _
Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" _
(ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function FreeLibrary Lib "kernel32" _
(ByVal hLibModule As Long) As Long
Private Declare Function FlashWindowEx Lib "user32" _
(FWInfo As FLASHWINFO) As Boolean
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Const NumberOfFlashes = 5
Private Function APIFunctionPresent(ByVal FunctionName _
As String, ByVal DllName As String) As Boolean
Dim lHandle As Long
Dim lAddr As Long
lHandle = LoadLibrary(DllName)
If lHandle <> 0 Then
lAddr = GetProcAddress(lHandle, FunctionName)
FreeLibrary lHandle
End If
APIFunctionPresent = (lAddr <> 0)
End Function
Sub Sample()
'
' Put your code here. Once that code finishes, Excel will FLASH
'
Dim udtFWInfo As FLASHWINFO
If Not APIFunctionPresent("FlashWindowEx", "user32") Then Exit Sub
With udtFWInfo
.cbSize = Len(udtFWInfo)
.Hwnd = Application.Hwnd
.dwFlags = FLASHW_FLAGS Or FLASHW_TRAY
.uCount = NumberOfFlashes
.dwTimeout = 0
End With
Call FlashWindowEx(udtFWInfo)
MsgBox "Done"
End Sub
The easiest way is to probably to create a userform instead then set the focus to this when it initialises.
Code in the userform to show as modal:
Private Declare Function FindWindow Lib "User32" Alias "FindWindowA" (ByVal lpClassName _
As String, ByVal lpWindowName As String) As Long
Private Declare Function SetWindowPos Lib "User32" (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 Const SWP_NOSIZE = &H1
Private Const SWP_NOMOVE = &H2
Private Const FLAGS As Long = SWP_NOMOVE Or SWP_NOSIZE
Private Const HWND_TOPMOST = -1
Private Sub UserForm_Initialize()
Dim hwnd As Long: hwnd = FindWindow(vbNullString, Me.Caption)
If hwnd > 0 Then SetWindowPos hwnd, HWND_TOPMOST, 0, 0, 0, 0, FLAGS ' Set Modal
End Sub
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