I use 'Close_by_Caption' to close open .PDFs before they are regenerated. Previously this was easy because I could assume that always 'Foxit Reader' opened the file and that each file was opened in a separate instance of Foxit.
Newly, 'PDF-XChange Editor' should also be used to open .PDF. Now I don't know if I have to close the file with 'Close_By_Caption 'Demo - PDF-XChange Editor' or 'Demo.pdf - Foxit Reader'.
Of course I can run both commands one after the other - but surely someone will come soon who wants to use another viewer....
Is there a way to find all programs that have the word 'Demo' in 'AppCaption'? Of course, it would be even better if I would knew that either the program is a .PDF-viewer or the opened file is a .PDF...
'API Find application by full caption
Private Declare Function FindWindow _
Lib "user32" _
Alias "FindWindowA" _
( _
ByVal lpClassName As String, _
ByVal lpWindowName As String _
) _
As Long
'*****************************************
'API Bring Window to foreground
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As Long) As Long
'*****************************************
'API Send message to application
Private Declare Function PostMessage _
Lib "user32" _
Alias "PostMessageA" _
( _
ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any _
) _
As Long
'*****************************************
Const WM_CLOSE = &H10
'*****************************************
Function Close_By_Caption(AppCaption As String)
Dim hWnd As Long
hWnd = FindWindow(vbNullString, AppCaption)
If hWnd Then
'Bring to Front
SetForegroundWindow hWnd
'Close the app nicely
PostMessage hWnd, WM_CLOSE, 0&, 0&
End If
End Function
'*****************************************
Sub Test_Close()
Close_By_Caption "demo.pdf - Foxit Reader"
End Sub
'*****************************************```
I'm trying to make a Net IME keyboard application. This app will suggest me relevant topics when typing or searching. But I can't get the location of an active textbox control from other applications.
The FindWindowEx function works though, but I'm looking for the location of an active textbox so i can change the location of the suggestion control to that textbox. Thanks!
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String,
ByVal lpsz2 As String) As Integer
So I have a method I am using to which I can integrate powerpoint into a panel. I use the FindWindow and SetParent functions to achieve this:
Dim proc as integer
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As Long) As Long
Private Declare Function SetParent Lib "user32" Alias "SetParent" (ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As Integer
Public Sub embed_Window()
Do Until proc <> 0
proc = FindWindow(vbNullString, window_name)
Loop
SetParent(proc, Panel1.Handle)
End Sub
This part works fine for embedding another window into my panel control. My question is, how can I close the window that is now in my panel? I can no longer use the FindWindow method as it is not a window in the task bar anymore.
In order to close an opened window you need to use PostMessage:
Private Declare Auto Function PostMessage Lib "user32" (ByVal hwnd As Integer, ByVal message As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As Boolean
Public Const WM_CLOSE = &H10
Public Sub CloseWindow()
PostMessage(proc, WM_CLOSE, 0, 0)
End Sub
I have an MS Access form that contains Button to open an application. The application is created using c#. I want to get the TextBox in the Form so that I will set a value on it using the MS Access project.
I am using the following code:
hwndParent = FindWindow(vbNullString, "Form1")
If hwndParent <> 0 Then
TextBoxHandle = FindWindowEx(hwndParent, 0&, "WindowsForms10.EDIT.app.0.3cb5890_r6_ad1", vbNullString)
SendMessage TextBoxHandle, WM_SETTEXT, 0&, ByVal strText
End If
Above code is working on my workstation: Windows 10 Pro.
When I open the MS Access in windows 8. it can't find the TextBox.
TextBoxHandle always return 0 in Windows 8. I am sure that the issue is with 3rd parameter in FinWindowEx. I used spy++ from Microsoft to get the value WindowsForms10.EDIT.app.0.3cb5890_r6_ad1 cause when I try to just enter "Edit", it does not work.
Edit: Adjusted answer using information about dynamic name of class from Hans Passant.
First, we're going to declare WinAPI functions to be able to iterate through all windows and get their class name.
Declare PtrSafe Function FindWindowExW Lib "user32" (ByVal hWndParent As LongPtr, Optional ByVal hwndChildAfter As LongPtr, Optional ByVal lpszClass As LongPtr, Optional ByVal lpszWindow As LongPtr) As LongPtr
Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameW" (ByVal hWnd As LongPtr, ByVal lpClassName As LongPtr, ByVal nMaxCount As Long) As Long
Then, we're going to declare a helper function to get the class name from a hWnd:
Public Function GetWindowClass(hWnd As LongPtr) As String
Dim buf(512) As Byte
GetClassName hWnd, varPtr(buf(0)), 255
GetWindowClass = Replace(CStr(buf), Chr(0), "")
End Function
Then, we're going to iterate through all top-level windows, and return the hWnd from the one matching that class name:
Public Function getThehWnd(hWndParent) As LongPtr
Dim hWnd As LongPtr
hWnd = FindWindowExW(hWndParent)
Do While hWnd <> 0
If GetWindowClass(hWnd) Like "WindowsForms10.EDIT.app.0.*" Then
getThehWnd = hWnd
Exit Function
End If
hWnd = FindWindowExW(hWndParent, hWnd)
Loop
End Function
Old answer:
There are numerous things that can go wrong when calling WinAPI functions from VBA with strings. These include passing a string that's not terminated by a null string, and passing a string that's in the wrong encoding.
For that first case, you get unstable behavior. If the string happens to be stored somewhere where there are a lot of zero's in memory, it works. Else, it continues reading bytes from memory and appending them to the string until it finds two bytes that happen to both be 0.
The first case is easily fixed by appending a null character to the end of your string:
TextBoxHandle = FindWindowEx(hwndParent, 0&, "WindowsForms10.EDIT.app.0.3cb5890_r6_ad1" & Chr(0), vbNullString)
Note that you should probably also make that last argument optional. Entering vbNullString there passes a pointer to a zero-length string, that might also not be delimited by a null character, causing WinAPI to read subsequent characters till it finds 2 null bytes. Setting the type to LongPtr and passing 0 (the default value) passes an actual null pointer, which WinAPI expects when no string gets put in.
The second code is more difficult. I tend to use bytearrays to make sure VBA doesn't do weird things
Dim className As Byte(1024)
className = "WindowsForms10.EDIT.app.0.3cb5890_r6_ad1" 'Yes, this is valid, and assigns the first part of the bytearray to a string
FindWindowExW(hwndParent, 0&, VarPtr(className(0)))
The corresponding declaration of FindWindowExW:
Declare PtrSafe Function FindWindowExW Lib "user32" (ByVal hWndParent As LongPtr, Optional ByVal hwndChildAfter As LongPtr, Optional ByVal lpszClass As LongPtr, Optional ByVal lpszWindow As String) As LongPtr
To debug problems and identify specific windows, I use the following function to iterate through all top and child windows, instead of Spy++. This one has the advantage of running in VBA, so you can set breakpoints and watches, which means you can very easily determine the class name and parent window of all open windows:
Public Sub IterateAllWindows(Optional hWnd As LongPtr, Optional EnumLevel = 0)
Dim hwndChild As LongPtr
If hWnd <> 0 Then
Debug.Print String(EnumLevel, "-");
Debug.Print hWnd & ":";
Debug.Print GetWindowName(hWnd);
Debug.Print "(" & GetWindowClass(hWnd) & ")"
hwndChild = FindWindowExW(hWnd)
Do While hwndChild <> 0
IterateAllWindows hwndChild, EnumLevel:=EnumLevel + 1
hwndChild = FindWindowExW(hWnd, hwndChild)
Loop
Else
Dim hWndTopLevel As LongPtr
hWndTopLevel = GetTopWindow
Do While hWndTopLevel <> 0
Debug.Print String(EnumLevel, "-");
Debug.Print hWndTopLevel & ":";
Debug.Print GetWindowName(hWndTopLevel);
Debug.Print "(" & GetWindowClass(hWndTopLevel) & ")"
hwndChild = FindWindowExW(hWndTopLevel)
Do While hwndChild <> 0
IterateAllWindows hwndChild, EnumLevel:=EnumLevel + 1
hwndChild = FindWindowExW(hWndTopLevel, hwndChild)
Loop
hWndTopLevel = GetWindow(hWndTopLevel, 2)
Loop
End If
End Sub
This uses the following 2 helper functions:
Public Function GetWindowName(hWnd As LongPtr) As String
Dim buf(512) As Byte
GetWindowText hWnd, varPtr(buf(0)), 255
GetWindowName = Replace(CStr(buf), Chr(0), "")
End Function
Public Function GetWindowClass(hWnd As LongPtr) As String
Dim buf(512) As Byte
GetClassName hWnd, varPtr(buf(0)), 255
GetWindowClass = Replace(CStr(buf), Chr(0), "")
End Function
Corresponding WinAPI declarations for that sub:
Declare PtrSafe Function GetTopWindow Lib "user32" (Optional ByVal hWnd As LongPtr) As LongPtr
Declare PtrSafe Function GetWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal wCmd As Integer) As LongPtr
Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextW" (ByVal hWnd As LongPtr, ByVal lpString As Any, ByVal nMaxCount As Long) As Long
Declare PtrSafe Function FindWindowExW Lib "user32" (ByVal hWndParent As LongPtr, Optional ByVal hwndChildAfter As LongPtr, Optional ByVal lpszClass As LongPtr, Optional ByVal lpszWindow As LongPtr) As LongPtr
Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameW" (ByVal hWnd As LongPtr, ByVal lpClassName As LongPtr, ByVal nMaxCount As Long) As Long
Running this function with a watch on that class name should help you identify if it's top-level or a child window, and if it's a child window, which class it belongs to. You can also modify it to return the hWnd independent of nesting (by using an If getWindowClass = "WindowsForms10.EDIT.app.0.3cb5890_r6_ad1" Then or by checking the title).
I think you should use Spy to conduct the same investigations on Windows 8 as you (presumably) did on Windows 10. Something must be different there, else your code would work.
Sidenote (because it bit me in the past): make sure you run the version of Spy whose 'bitness' (32 bit / 64 bit) matches the application you're interested in, otherwise message logging doesn't work.
Also, sorry for my previous post, it was a load of cr#p.
Edit Ah ha! Hans comments above that the class name is dynamically generated, so that's your problem. So now we know.
im a complet noob in vb . i have been trying to figure out how to send keystrokes back to my own application , while minimized/ Or focused on an other window.
i think i need to use PostMessageA . i read about it on forums. But its like chinese for me.
my goal is to run these little programs by the 100's on 1 pc. and they just press a key in their own application , over and over.
can someone help me out please.
thanks
i was thinking something like this
Private Declare Function PostMessage Lib "user32.dll" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Const WM_CHAR As Long = &H102
PostMessage("notepad", WM_CHAR, "T", 1)
as a test. what am i doing wrong
A few things, for one you have found your code in a VB6 Forum. The size of integers has changed since then. A Long in VB6 is equivalant to an Integer in VB.Net(see this Msdn link). The second issue is that you are supplying a String to PostMessage where it is expecting the handle to your window. I would suggest that you look at this CodeProject article about how to Send strings to another application by using Windows messages
Your PostMessage declaration should look like this.
Private Declare Function PostMessage Lib "user32.dll" Alias "PostMessageA" (ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer