I have a window that I need to activate and the window name does not work in AppActivate("WindowName") because this does not work with partial captions etc... and the window name will be different depending on user. That being said I am able to use "GetwindowhandlefromPartialCaption" to retrieve the # value of the window name or handle. Is there a way to convert this or extract the name from the handle ID to use with AppActivate?
The code I'm using to get the handle ID is as follows:
Public Class Form1
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function GetWindowText(ByVal hwnd As IntPtr, ByVal lpString As StringBuilder, ByVal cch As Integer) As Integer
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function GetWindowTextLength(ByVal hwnd As IntPtr) As Integer
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function SetWindowText(ByVal hwnd As IntPtr, ByVal lpString As String) As Boolean
End Function
Declare Auto Function GetWindow Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal uCmd As UInt32) As IntPtr
Private Function GetHandleFromPartialCaption(ByRef lWnd As Long, ByVal sCaption As String) As Boolean
Dim lhWndP As Long
GetHandleFromPartialCaption = False
lhWndP = FindWindow(vbNullString, vbNullString) 'PARENT WINDOW
Do While lhWndP <> 0
Dim length As Integer = GetWindowTextLength(lhWndP)
If length > 0 Then
Dim sStr As New StringBuilder("", length + 1)
GetWindowText(lhWndP, sStr, sStr.Capacity)
If sStr.ToString.Contains(sCaption) Then
GetHandleFromPartialCaption = True
lWnd = lhWndP
Exit Do
End If
End If
lhWndP = GetWindow(lhWndP, GetWindow_Cmd.GW_HWNDNEXT)
Loop
End Function
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim lhWndP As Long
If GetHandleFromPartialCaption(lhWndP, "Navilink") = True Then
MsgBox("Found Window Handle: " & lhWndP, vbOKOnly + vbInformation)
Else
MsgBox("Window 'Target App -'", vbOKOnly + vbExclamation)
End If
End Sub
Private Function GetAllHandleCaptions(ByRef lWnd As Long) As Boolean
Dim lhWndP As Long
lhWndP = GetWindow(lWnd, GetWindow_Cmd.GW_CHILD)
Do While lhWndP <> 0
Dim length As Integer = GetWindowTextLength(lhWndP)
If length > 0 Then
Dim sStr As New StringBuilder("", length + 1)
GetWindowText(lhWndP, sStr, sStr.Capacity)
TextBox1.Text = TextBox1.Text + sStr.ToString() + " - " + lhWndP.ToString(+System.Environment.NewLine)
End If
lhWndP = GetWindow(lhWndP, GetWindow_Cmd.GW_HWNDNEXT)
Loop
End Function
End Class
The simple code I would love to get working is as follows:
' Grab the text highlighted in the other program.
Private Sub Command1_Click()
' Activate the other program.
AppActivate ("Applicationname")
' Clear the clipboard.
Clipboard.Clear
' Press Control.
keybd_event VK_CONTROL, 0, 0, 0
DoEvents
' Press C.
keybd_event VK_C, 1, 0, 0
DoEvents
' Release Control.
keybd_event VK_CONTROL, 0, KEYEVENTF_KEYUP, 0
DoEvents
' Get the text from the clipboard.
Text1.Text = Clipboard.GetText
I think it should work if i can somehow use the code to get the window text again and pass
that to the AppActivate. Just not sure how to do it.
Thanks!
AppActivate calls SetForegroundWindow , so you can try calling it yourself.
Related
So I tried this code to gradually lower the volume of background music that's on slide 1 (and playing in the background for the rest of the presentation), but I cannot get it to affect the volume. What am I doing wrong?
Sub OnSlideShowPageChange()
Dim i As Integer
Dim bkgMusic As Shape
Dim bkgVol As Long
Dim inc As Long
i = ActivePresentation.SlideShowWindow.View.CurrentShowPosition
Set bkgMusic = ActivePresentation.Slides(1).Shapes("Opening Theme")
If i = 1 Then
'Do nothing
ElseIf i <> 1 Then
inc = 0.05
For bkgVol = 1 To 0.5
With bkgMusic.MediaFormat
.Volume = bkgVol
End With
bkgVol = bkgVol - inc
Application.Wait(Now + TimeValue("00:00:01"))
Next bkgVol
End If
End Sub
So you want to imitate the 'fade-out' effect in PowerPoint Slide show. Even changing the volume of the sound in the first slide doesn't affect the current volume of the background music.
Here I suggest a different way - using the Windows API called 'MCISendString.' MCISendString provides a variety of ways to control the audio-play.(link) Especially the command(MCISendString setaudio [AUDIO] volume to xxx) can change the volume in real time. Following is an example where the slide show starts playing on the 2nd slide and, on the 6th slide, fades out the audio and stops playing in 3 seconds.
Please take a look and try the following snippet:
Option Explicit
#If VBA7 Then
Public Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, _
ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Public Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
Public Declare PtrSafe Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" _
(ByVal lpstrCommand As String, ByVal lpstrReturnString As Any, _
ByVal uReturnLength As Long, ByVal hwndCallback As LongPtr) As Long
Public TimerID As LongPtr
#Else
Public Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, _
ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
Public Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" _
(ByVal lpstrCommand As String, ByVal lpstrReturnString As Any, _
ByVal uReturnLength As Long, ByVal hwndCallback As Long) As Long
Public TimerID As Long
#End If
'audio file name(should exist in the current folder)
Const FileName As String = "bensound.mp3"
'where the audio starts to play
Const StartSlide As Long = 2
'where the audio stops playing
Const EndSlide As Long = 6
'how long the fade-out effect will take
Const FadeDuration As Long = 30 '3 sec * 10 interval
'the default volume
Const DefaultVolume As Integer = 150
'timer count
Dim TimerCount As Long
Dim Playing As Boolean
'Show the current position
Private Sub myTimer()
On Error Resume Next
Dim vol As Single
TimerCount = TimerCount + 1
vol = DefaultVolume * (FadeDuration - TimerCount) / FadeDuration
'Debug.Print vol
mciSendString "setaudio SoundFile volume to " & CStr(vol), vbNullString, 0, 0&
If TimerCount >= FadeDuration Then StopAudio
End Sub
Sub PlayAudio()
mciSendString "close all", vbNullString, 0, 0&
'enclose the FileName with Chr(34)s for a long filename with blank characters
mciSendString "Open " & Chr(34) & FileName & Chr(34) & " alias SoundFile", vbNullString, 0, 0&
mciSendString "Play SoundFile", vbNullString, 0, 0&
mciSendString "setaudio SoundFile volume to " & DefaultVolume, vbNullString, 0, 0&
Playing = True
End Sub
Sub FadeOutAudio()
If Playing Then startTimer
End Sub
Sub StopAudio()
mciSendString "Stop SoundFile", vbNullString, 0, 0&
stopTimer
Playing = False
End Sub
Private Sub startTimer()
If TimerID = 0& Then
TimerID = SetTimer(0&, 0&, 100&, AddressOf myTimer) ' 1000 = 1sec, 500 = 0.5sec
End If
End Sub
Private Sub stopTimer()
On Error Resume Next
KillTimer 0&, TimerID
TimerID = 0&
TimerCount = 0
End Sub
Sub onSlideShowTerminate(SSW As SlideShowWindow)
stopTimer
mciSendString "close all", vbNullString, 0, 0&
Playing = False
End Sub
'fires whenever the show page changes
Sub onSlideShowPageChange(SSW As SlideShowWindow)
If SSW.View.Slide.SlideIndex = StartSlide Then
PlayAudio
ElseIf SSW.View.Slide.SlideIndex = EndSlide Then
FadeOutAudio
Else
'
End If
End Sub
I have search lot on the internet but I didn't find the solution. I need to get all API handles of particular window by caption/title of that window.
I have a code but it captures all handle of all open windows.
Public Sub GetWindows()
x = 0
winOutputType.winHandle = 0
winOutputType.winClass = 1
winOutputType.winTitle = 2
winOutputType.winHandleClass = 3
winOutputType.winHandleTitle = 4
winOutputType.winHandleClassTitle = 5
GetWinInfo 0&, 0, winOutputType.winHandleClassTitle
End Sub
I need code that will ask me window name and then capture handles of that particular window.
I was looking for a duplicate for Close all VBE windows (VB for Aplications) but couldn't find a link. I ended up here. For future users looking for a similar query, this should help you/them.
Option Explicit
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 GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hWnd As LongPtr, ByVal lpString As String, ByVal cch As LongPtr) As LongPtr
Public Sub GetWindows()
'~~> Pass Full Name or Partial Name. This is not case sensitive
Debug.Print GetAllWindowHandles("Microsoft Visual Basic")
End Sub
Private Function GetAllWindowHandles(partialName As String)
Dim hWnd As Long, lngRet As Long
Dim strText As String
hWnd = FindWindowEx(0&, 0&, vbNullString, vbNullString)
While hWnd <> 0
strText = String$(100, Chr$(0))
lngRet = GetWindowText(hWnd, strText, 100)
If InStr(1, strText, partialName, vbTextCompare) > 0 Then
Debug.Print "The Handle of the window is " & hWnd & " and " & vbNewLine & _
"The title of the window is " & Left$(strText, lngRet) & vbNewLine & _
"----------------------"
End If
'~~> Find next window
hWnd = FindWindowEx(0&, hWnd, vbNullString, vbNullString)
Wend
End Function
Posting this question do I can provide a full example for anyone like me who needed to figure this out...
Occasionally when automating IE you may be faced with a pop-up dialog which you need to interact with: I'm specifically talking here about the modal dialog which is IE-specific, and opened using showModalDialog
https://msdn.microsoft.com/en-us/library/ms536759(v=vs.85).aspx
These dialogs are different from the typical "pop-over" dialogs or ones based on window.open() - although they contain HTML, there's no easy way to get a reference to the document contained within the dialog. For example iterating through the windows under the Windows shell does not find this type of dialog.
I figured there must be some way to solve this problem using the Windows API, and I found a bunch of relevant pieces via Google, but no complete and self-contained example.
See my answer for how I solved my specific use case - should be easily re-used if you need something similar.
Here's what I ended up with (apologies for not including the various links where I found the key parts - will add later if I can re-find them)
Edit: https://social.msdn.microsoft.com/Forums/en-US/baf3cb64-8858-4d2d-9d7b-eaee76919256/modify-the-code-obtained-from-the-internet-explorerserver-hwnd-handle?forum=vbgeneral
Declarations (if you have 64-bit Office installed you will need to make some adjustments)
Option Explicit
' Requires: VBA project reference to "Microsoft HTML Object Library"
Private Const SMTO_ABORTIFHUNG = &H2
Private Const GW_CHILD = 5
Private Const GW_HWNDNEXT = 2
Private Type UUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName 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 GetWindow Lib "user32" _
(ByVal hWnd As Long, ByVal wCmd As Long) As Long
Private Declare Function IsWindowVisible Lib "user32" _
(ByVal hWnd As Long) As Boolean
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 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 Declare Function ObjectFromLresult Lib "oleacc" ( _
ByVal lResult As Long, _
riid As UUID, _
ByVal wParam As Long, _
ppvObject As Any) As Long
Example usage:
'An example of how to use this approach - other subs below should not need adjusting
Sub DialogDemo()
Const DLG_TITLE = "User Info -- Webpage Dialog" '<< the dialog title
Dim doc As IHTMLDocument
Set doc = GetIEDialogDocument(DLG_TITLE)
If Not doc Is Nothing Then
'Debug.Print doc.body.innerHTML
doc.getElementById("password_id").Value = "password"
doc.getElementById("Notes_id").Value = "notes go here"
doc.getElementById("b_Ok_id").Click '<< click OK
Else
MsgBox "Dialog Window '" & DLG_TITLE & "' was not found!", vbOKOnly + vbExclamation
End If
End Sub
'Given an IE dialog window title, find the window and return a reference
' to the embedded HTML document object
Function GetIEDialogDocument(dialogTitle As String) As IHTMLDocument
Dim lhWndP As Long, lhWndC As Long, doc As IHTMLDocument
'find the IE dialog window given its title
If GetHandleFromPartialCaption(lhWndP, dialogTitle) Then
Debug.Print "Found dialog window - " & dialogTitle & "(" & TheClassName(lhWndP) & ")"
lhWndC = GetWindow(lhWndP, GW_CHILD) 'Find Child
If lhWndC > 0 Then
If TheClassName(lhWndC) = "Internet Explorer_Server" Then
Debug.Print , "getting the document..."
Set doc = IEDOMFromhWnd(lhWndC)
End If
End If
Else
Debug.Print "Window '" & dialogTitle & "' not found!"
End If
Set GetIEDialogDocument = doc
End Function
' IEDOMFromhWnd
' Returns the IHTMLDocument interface from a WebBrowser window
' hWnd - Window handle of the control
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
lMsg = RegisterWindowMessage("WM_HTML_GETOBJECT") ' Register the message
SendMessageTimeout hWnd, lMsg, 0, 0, SMTO_ABORTIFHUNG, 1000, lRes ' Get the object pointer
If lRes Then
With IID_IHTMLDocument ' Initialize the interface ID
.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 (note - returns the object via the last parameter)
hr = ObjectFromLresult(lRes, IID_IHTMLDocument, 0, IEDOMFromhWnd)
End If
End If 'hWnd<>0
End Function
'utilty function for getting the classname given a window handle
Function TheClassName(lhWnd As Long)
Dim strText As String, lngRet As Long
strText = String$(100, Chr$(0))
lngRet = GetClassName(lhWnd, strText, 100)
TheClassName = Left$(strText, lngRet)
End Function
Private Function GetHandleFromPartialCaption(ByRef lWnd As Long, _
ByVal sCaption As String) As Boolean
Dim lhWndP As Long, 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 Len(sStr) > 2 Then
If UCase(sStr) Like "*ARG*" Then Debug.Print sStr
End If
If InStr(1, sStr, sCaption) > 0 Then
GetHandleFromPartialCaption = True
lWnd = lhWndP
Exit Do
End If
lhWndP = GetWindow(lhWndP, GW_HWNDNEXT)
Loop
End Function
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 have a string (msg) that is pretty much a very long list of items. I need to put this in a msgbox but it is not long enough to show the whole text. Is there an alternative to this?
Thank you!
The Message Box function is a built-in function of VBA and cannot exceed 1024 Characters. You are limited to creating your own UserForm or some other alternative... Such as opening and writing to an unsaved instance of notepad...
An ALL API solution to open Notepad and Write your message to it...
NOTE: If your running VBA 7.0 (Office 2010) then you'll have to add PtrSafe just after each Declare Statement...
At the top of your module paste the API Declarations and Global Variables
Option Explicit
Public Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessID As Long
dwThreadID As Long
End Type
Public 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
'Miscellaneous API Constants
Public Const NORMAL_PRIORITY_CLASS As Long = &H20&
Public Const INFINITE As Long = -1&
'Window Message Constants
Public Const WM_GETTEXT = &HD
Public Const WM_GETTEXTLENGTH = &HE
Public Const WM_SETTEXT As Long = &HC
'GetWindow Constants
Public Const GW_CHILD = 5
Public Const GW_HWNDFIRST = 0
Public Const GW_HWNDLAST = 1
Public Const GW_HWNDNEXT = 2
Public Const GW_HWNDPREV = 3
Public Const GW_OWNER = 4
'Keybd_event Constants
Public Enum enumKBE
KBE_KeyDown = 0
KBE_KeyUp = 2
KBE_ExtKeyDown = 1
KBE_ExtKeyUp = 3
End Enum
'Keyboard Control Key Constants
Public Const VK_CONTROL = &H11
Public Const VK_HOME = &H24
'Keyboard Control Action Constants
Public Const WM_KEYDOWN = &H100
Public Const WM_KEYUP = &H101
'Create a new process
Public Declare Function CreateProcessA _
Lib "kernel32.dll" _
(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, _
ByRef lpStartupInfo As STARTUPINFO, _
ByRef lpProcessInformation As PROCESS_INFORMATION) As Long
'Waits until the specified process has finished processing its initial input
'and is waiting for user input with no input pending, or until the time-out
'interval has elapsed.
Public Declare Function WaitForInputIdle _
Lib "user32.dll" (ByVal hProcess As Long, ByVal dwMilliseconds As Long) As Long
'Closes Handles Created and referenced from the CreateProcess API
Public Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Long
'Returns the Window Handle of the Window that is accepting User input.
Public Declare Function GetForegroundWindow Lib "user32.dll" () As Long
'Desktop Window handle
Public Declare Function GetDesktopWindow Lib "user32.dll" () As Long
'Retrieves Window handle
Public Declare Function GetWindow Lib "user32.dll" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
'Get the length of a Window's caption
Public Declare Function GetWindowTextLength Lib "user32.dll" Alias "GetWindowTextLengthA" (ByVal hwnd As Long) As Long
'Get the caption of a Window as a string
Public Declare Function GetWindowText Lib "user32.dll" Alias "GetWindowTextA" _
(ByVal hwnd As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long
'Returns the Class or catagory name of an Window handle
Public Declare Function GetClassName Lib "user32.dll" Alias "GetClassNameA" _
(ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
'You can use the GetDlgItem function with any parent-child window pair, not just with
'dialog boxes. As long as the hDlg (hWnd) parameter specifies a parent window and the
'child window has a unique identifier (as specified by the hMenu parameter in the
'CreateWindow or CreateWindowEx function that created the child window),
'GetDlgItem returns a valid handle to the child window.
Public Declare Function GetDlgItem Lib "user32.dll" (ByVal hDlg As Long, ByVal nIDDlgItem As Long) As Long
'Send messages to windows
Public Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
'Finds a window with the name, returns the handle.
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
'Gets a controls window handle. The form window handle must be specified to get a decent control.
Public 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
'Translates (maps) a virtual-key code into a scan code or character value
Public Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long
'Synthesizes a keystroke. The system can use such a synthesized keystroke to generate a WM_KEYUP or WM_KEYDOWN message.
Public Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
'Sets Keyboard control and focus to the provided Window handle
Public Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
'Computer will wait for x number of milliseconds
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Write2Notepad function opens a new instance of Notepad and writes to it. If it succeeds, then it will return the Process ID of the Notepad instance.
Public Function Write2Notepad(strInText As String) As Long
Const nEditID = 15 'Identifier ID to Notepad's Edit Control
Dim PI As PROCESS_INFORMATION
Dim SI As STARTUPINFO
Dim RetVal As Long, hWndNote As Long, chWnd As Long, LngVal As Long, PID As Long
Dim strCaption As String, strClassName As String
'Initialize the STARTUPINFO structure
SI.cb = Len(SI)
'Start the application
RetVal = CreateProcessA(lpApplicationName:=vbNullString, _
lpCommandLine:="Notepad.exe", _
lpProcessAttributes:=0&, _
lpThreadAttributes:=0&, _
bInheritHandles:=1&, _
dwCreationFlags:=NORMAL_PRIORITY_CLASS, _
lpEnvironment:=0&, _
lpCurrentDirectory:=vbNullString, _
lpStartupInfo:=SI, _
lpProcessInformation:=PI)
'Wait for the application to finish loading
While WaitForInputIdle(PI.hProcess, INFINITE) <> 0
DoEvents
Wend
'Get the Process ID of the newly opened Notepad application
PID = PI.dwProcessID
'Close all Threads and handles for the Startup Process Information
' (This is not the Window Handle and is highly recommended)
Call CloseHandle(PI.hThread)
Call CloseHandle(PI.hProcess)
'Get the Active Application's Window Handle
'Note: when stepping through code in debugger this Will Return the VB Editor's Window Handle,
' Set a break point below GetForegroundWindow instead.
hWndNote = GetForegroundWindow()
If hWndNote = 0 Then '
'If the ForegroundWindow Handle isn't available Get the first Child Window to the Desktop
hWndNote = GetWindow(GetDesktopWindow, GW_CHILD)
End If
'Do While loop to verify the hWndNote Window Handle belongs to an Empty Untitled Notepad Window
Do
chWnd = 0
'Get Window Caption
LngVal = GetWindowTextLength(hWndNote) + 1
strCaption = String(LngVal, Chr$(0))
LngVal = GetWindowText(hWndNote, strCaption, LngVal)
strCaption = IIf(LngVal > 0, Left(strCaption, LngVal), "")
'Get the Window Class name
LngVal = GetWindowTextLength(hWndNote) + 1
strClassName = String(LngVal, Chr$(0))
LngVal = GetClassName(hWndNote, strClassName, LngVal)
strClassName = IIf(LngVal > 0, Left(strClassName, LngVal), "")
If strCaption Like "Untitled - Notepad" And strClassName = "Notepad" Then
'Get the window handle of the Edit Control which is a child window of Notepad
chWnd = GetDlgItem(hWndNote, nEditID)
'Get the character count of the notepad text to ensure it is empty (Should return 0)
If SendMessage(chWnd, WM_GETTEXTLENGTH, 0, 0) = 0 Then
Exit Do
End If
End If
'Get the next Window
hWndNote = GetWindow(hWndNote, GW_HWNDNEXT)
'Process Windows events.
DoEvents
Loop While hWndNote <> 0
If hWndNote = 0 Then
MsgBox "Cannot find Notepad's Window Handle."
Write2Notepad = 0
Exit Function
End If
If chWnd = 0 Then
'Returns child Window Hwnd - Similar to GetDlgItem
chWnd = FindWindowEx(hWndNote, ByVal 0&, vbNullString, vbNullString)
End If
DoEvents
'Sends the Text Value to Notepad
RetVal = SendMessage(chWnd, WM_SETTEXT, Len(strInText) + 1, ByVal strInText)
'To ensure the cursor position is at the top left the Keyboard Control forces the "Ctrl" Key is pressed
keybd_event VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), KBE_KeyDown, 0
'Sends the "Home" input to Notepad (Simulates the CTRL + Home action to bring the cursor to the top of Notepad
SendMessage chWnd, WM_KEYDOWN, VK_HOME, 0
SendMessage chWnd, WM_KEYUP, VK_HOME, 0
'Simulates the Key up or unpressing of the "Ctrl" Key
keybd_event VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), KBE_KeyUp, 0
'Ensures the Notepad window has the Cursor Focus
SetForegroundWindow (hWndNote)
'Returns the Process ID if the Value of the Settext SendMessage call equals a value of 1 (True) = successful
If CBool(RetVal) = True And PID > 0 Then
Write2Notepad = PID
Else
Write2Notepad = 0
End If
End Function
Routine to Test the Write2Notepad Function
Sub TestWriting2Notepad()
Dim strTestText As String
Dim lngProcID As Long
Dim oNotepad As Object
strTestText = "This" & vbCrLf & "is" & vbCrLf & "a Test" & vbCrLf & "to see if" & vbCrLf & "I can" & vbCrLf & _
vbCrLf & vbCrLf & "Write" & vbCrLf & vbCrLf & "2" & vbCrLf & vbCrLf & "Notepad!!!"
lngProcID = Write2Notepad(strTestText)
If lngProcID = 0 Then
Debug.Print "Something went wrong... It was probably your fault!"
Else
Debug.Print "You Successfully Wrote to Notepad... API Style!"
Do
DoEvents
Sleep 500
Set oNotepad = Nothing
On Error Resume Next
Set oNotepad = GetObject("winmgmts:root\cimv2:Win32_Process.Handle='" & lngProcID & "'")
On Error GoTo 0
Loop While Not oNotepad Is Nothing
' For Example only - Delete Below Line
MsgBox "You Closed Notepad"
End If
End Sub
The above code might look like a lot of trouble or more complicated but it will likely work much more reliably and efficiently then any other method.
The below function will copy your message to the clipboard using the MS clip tool, open notepad, and then paste the clipboard contents (your message) into Notepad... This way you don't have to save anything to a file and its easily closed... Or you can save it if you choose.
Option Explicit
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub Print2Notepad(strMessage)
Dim oShell As Object, oExec As Object, oIn As Object
Set oShell = CreateObject("WScript.Shell")
Set oExec = oShell.Exec("clip")
Set oIn = oExec.StdIn
oIn.WriteLine strMessage
oIn.Close
Do While oExec.Status = 0
Sleep 100
Loop
Set oIn = Nothing
Set oExec = Nothing
oShell.Run "Notepad", 1, False
Sleep 250
oShell.SendKeys "^v"
End Sub
Sub test()
Call Print2Notepad("This is a test message")
End Sub
You can also add an additional routine to "Sleep" while notepad is open to halt code if you need... See Below
Sub Print2Notepad_WaitTillClose(strMessage)
Dim oShell As Object, oExec As Object, oIn As Object
Dim iPID As Variant, oNotepad As Object
Set oShell = CreateObject("WScript.Shell")
Set oExec = oShell.Exec("clip")
Set oIn = oExec.StdIn
oIn.WriteLine strMessage
oIn.Close
Do While oExec.Status = 0
Sleep 100
Loop
Set oIn = Nothing
Set oExec = Nothing
iPID = oShell.Exec("Notepad").ProcessID
Sleep 500
oShell.SendKeys "^v"
Do
Sleep 500
Set oNotepad = Nothing
On Error Resume Next
Set oNotepad = GetObject("winmgmts:root\cimv2:Win32_Process.Handle='" & iPID & "'")
On Error GoTo 0
Loop While Not oNotepad Is Nothing
' For Example only - Delete Below Line
MsgBox "You Closed Notepad"
End Sub
EDIT:
I just realized that I wrote the above code to work for VBScript... Since this is Excel, if you want to look into other methods to copy contents to the Clipboard without using the WshShell.Exec method; you can also try:
Dim DataObj As New MSForms.DataObject
Dim S As String
S = "Hello World"
DataObj.SetText S
DataObj.PutInClipboard
To use the DataObject in your code, you must set a reference to the Microsoft Forms 2.0 Object Library. This can also be done by creating a UserForm and then Deleting it... The reference will remain (Excel 2007).
For additional Clipboard API's and code take a look at:
1) http://www.cpearson.com/excel/Clipboard.aspx
2) http://msdn.microsoft.com/en-us/library/office/ff192913.aspx
3) http://msdn.microsoft.com/en-us/library/windows/desktop/ms648709%28v=vs.85%29.aspx
There are other possible methods but I think these are the most stable and reliable. I will leave the code the way it is so that it will work for both VBA and VBScript
Use a TextBox. I know ActiveX TextBoxes can even be assigned scrollbars.