Excel VBA : Get hwnd value of a CommandButton - excel

I'm going nuts here...
How do you find the "hwnd" value of a CommandButton, in an Excel 2007 Form ?
I've Googled, I've tried all kinds of suggestions (most of which suggest that a command button has a .hwnd member property - but it doesn't) and haven't found an answer.
I can get the Form's hwnd value, and (in theory) should be able to use a EnumChildWindows to find sub-windows, including my button, but this also doesn't work.
Has anyone managed to do this ?

I'm afraid you can't, MS Forms controls like CommandButtons are not windows at all, they are "Windowless controls" i.e they are drawn by the MS Forms Runtime onto the userform surface as purely graphical abstractions, so no HWND.

' this may format
' in a worksheet have driver buttons for
Option Explicit: Option Compare Text
Private Sub ControlsDet_Click()
LookFrames
End Sub
Private Sub PaintValid_Click()
PaintAll
End Sub
Private Sub ShowForm_Click()
UFS.Show False
End Sub
Private Sub TextON_Click()
DoTextOn
End Sub
' then have a form UFS and put in some controls from the tool box
'put in frames and listboxes and whatever
.
.have a code module as
Option Explicit: Option Compare Text
'
'http://www.tek-tips.com/viewthread.cfm?qid=1394490
'
' to look at the useage of CtrlName.[_GethWnd] function
' VB has a function for hWnd but VBA hides its brother as [_GetwHnd]
' in VBA there are haves and have_nots
' better than finding each control's position in pixels and then using
'Private Declare Function WindowFromPoint& Lib "user32" (ByVal xPoint&, ByVal yPoint&)
'
'
Type RECT ' any type with 4 long int will do
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
'
Type RECTxy
X1 As Long
Y1 As Long
X2 As Long
Y2 As Long
End Type
'
' OK as Private here or public elsewhere
'
Declare Function GetClientRect& Lib "User32.dll" (ByVal hwnd&, ByRef lpRECT As RECTxy)
Declare Function CreateSolidBrush Lib "gdi32" (ByVal crColor As Long) As Long
Declare Function DeleteObject& Lib "gdi32" (ByVal hndobj&)
Declare Function FillRectXY& Lib "User32.dll" Alias "FillRect" (ByVal Hdc&, lpRECT As RECTxy, ByVal hBrush&)
Declare Function GetDC& Lib "user32" (ByVal hwnd&)
Declare Function DeleteDC& Lib "gdi32" (ByVal hwnd&)
Declare Function TextOut& Lib "GDI32.dll" Alias "TextOutA" (ByVal Hdc&, ByVal x&, ByVal y&, _
ByVal lpString$, ByVal nCount&)
Function RndPale&(Optional R% = 150, Optional G% = 170, Optional B% = 140)
RndPale = RGB(R + Rnd() * (250 - R), G + Rnd() * (255 - G), B + Rnd() * (250 - G))
End Function
Sub PaintAll()
Dim Wc As Control
For Each Wc In UFS.Controls
Showrec Wc
Next Wc
End Sub
Sub Showrec(WCtrl As Control)
Dim hBrush&, Outwr As RECTxy, WCtrlhWnd&, WCtrlHDC&
WCtrlhWnd = WCtrl.[_GethWnd]
If WCtrlhWnd <> 0 Then ' has handle
WCtrlHDC = GetDC(WCtrlhWnd)
GetClientRect WCtrlhWnd, Outwr
hBrush = CreateSolidBrush(RndPale)
FillRectXY WCtrlHDC, Outwr, hBrush
DeleteObject hBrush
DeleteDC WCtrlHDC
DeleteObject WCtrlhWnd
End If
End Sub
Sub LookFrames()
Dim WCtrl As Control, rI%, Ra As Range
Dim Outwr As RECTxy, WCtrlhWnd&
Set Ra = ActiveSheet.Range("e4:r30")
Ra.NumberFormat = "0.0"
Ra.ClearContents
UFS.Show False
rI = 4
For Each WCtrl In UFS.Controls
WCtrlhWnd = WCtrl.[_GethWnd]
rI = rI + 1
Cells(rI, 5) = WCtrl.Name
Cells(rI, 6) = TypeName(WCtrl)
Cells(rI, 7) = WCtrlhWnd
Cells(rI, 8) = WCtrl.Left
Cells(rI, 9) = WCtrl.Top
Cells(rI, 10) = WCtrl.Width
Cells(rI, 11) = WCtrl.Height
If WCtrlhWnd <> 0 Then
GetClientRect WCtrlhWnd, Outwr
Cells(rI, 12) = Outwr.X1
Cells(rI, 13) = Outwr.Y1
Cells(rI, 14) = Outwr.X2
Cells(rI, 15) = Outwr.Y2
DeleteObject WCtrlhWnd
End If
Next WCtrl
Ra.Columns.AutoFit
End Sub
Sub DoTextOn()
UFS.Show False
Dim WHnd&, FHdc&, Tout$, Wc As Control
For Each Wc In UFS.Controls
WHnd = Wc.[_GethWnd]
If WHnd <> 0 Then
FHdc = GetDC(WHnd)
Tout = Wc.Name & " as " & WHnd
TextOut FHdc, 10, 20, Tout, Len(Tout)
DeleteDC FHdc
DeleteObject WHnd
End If
Next Wc
End Sub

Related

VBA to take screenshot of browser window and save it to a folder

I want to run a macro which will take a screenshot of webpage (one of the multiple tabs in the browser )open in another window and save the screenshot as png file to a folder (path and filename specified in a cell in excel sheet).
I searched for the same on google and got the code to change the active window and take the screenshot. But the below code will paste the screenshot in the active sheet. Instead I want to save it as a png or jpg file to a specific folder with a specific file name as given in excel sheet from where the program is triggered. So everytime the screenshot is triggered it will have a different file name picked from the active excel cell with some constant prefix.
Option explicit
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal _
bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Private Const VK_SNAPSHOT = &H2C
Sub PrintScreen()
appactivate("Microsoft Word",wait)
keybd_event VK_SNAPSHOT, 1, 0, 0
ActiveSheet.Paste
end sub
This answer assumes that the browser you are using is Chrome ... change the relevant text in the call to AppActivate to be the relevant text for the browser you are using. Also assumed is the filename in the ActiveCell is just the file name (not including the file extension).
You will need to set the PREFIX text to the folder you want to save the file in. This can include a prefix for the filename itself if you want, but if just a folder, must end with a trailing backslash.
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 Const VK_SNAPSHOT = &H2C
Private Const KEYEVENTF_KEYUP = &H2
Private Const PREFIX As String = "<My folder>\"
Sub PrintScreen()
' grab filename
Dim fileName As String
fileName = ActiveCell.Value
' activate, for example, Chrome ... this will only work if Chrome is already in
' a 'normal' or 'maximized' window (ie not 'minimized')
AppActivate "Chrome", False
' take screenshot
keybd_event VK_SNAPSHOT, 0, 0, 0
keybd_event VK_SNAPSHOT, 0, KEYEVENTF_KEYUP, 0
' wait for screenshot
Dim t As Single
t = Timer + 0.1
Do While Timer < t
DoEvents
Loop
' paste screenshot to sheet and grab it ... the active sheet must be
' a Worksheet for this to work
ActiveSheet.Paste
Dim shp As Shape
Set shp = Selection.ShapeRange(1)
' save as file ... PNG in this case
If ExportPicture(shp, PREFIX & fileName & ".png", "png") Then
' do success stuff!
Debug.Print "Success"
Else
' do failed stuff!
Debug.Print "Failed"
End If
' optionally, if required, delete the screenshot
shp.Delete
End Sub
' Export Shape, as a picture, to a file
Function ExportPicture(shp As Shape, sFile As String, sFilter As String) As Boolean
On Error GoTo errExit
Dim ch As ChartObject
Set ch = shp.Parent.ChartObjects.Add(0, 0, shp.Width, shp.Height)
ch.Activate
ch.ShapeRange.Fill.Visible = msoFalse ' to allow transparency if PNG
ch.ShapeRange.Line.Visible = msoFalse
ch.Chart.Paste
ch.Chart.Export sFile, sFilter
ExportPicture = True
errExit:
If Not ch Is Nothing Then ch.Delete
End Function
This function takes a screenshot and saves as a bmp file
'***********************************************************************************************
' * Please leave any Trademarks or Credits in place.
' *
' * ACKNOWLEDGEMENT TO CONTRIBUTORS :
' * STEPHEN BULLEN, 15 November 1998 - Original PastPicture code
' * G HUDSON, 5 April 2010 - Pause Function
' * LUTZ GENTKOW, 23 July 2011 - Alt + PrtScrn
' * PAUL FRANCIS, 11 April 2013 - Putting all pieces together, bridging the 32 bit and 64 bit version.
' * CHRIS O, 12 April 2013 - Code suggestion to work on older versions of Access.
' *
' * DESCRIPTION: Creates a standard Picture object from whatever is on the clipboard.
' * This object is then saved to a location on the disc. Please note, this
' * can also be assigned to (for example) and Image control on a userform.
' *
' * The code requires a reference to the "OLE Automation" type library.
' *
' * The code in this module has been derived from a number of sources
' * discovered on MSDN, Access World Forum, VBForums.
' *
' * To use it, just copy this module into your project, then you can use:
' * SaveClip2Bit("C:\Pics\Sample.bmp")
' * to save this to a location on the Disc.
' * (Or)
' * Set ImageControl.Image = PastePicture
' * to paste a picture of whatever is on the clipboard into a standard image control.
' *
' * PROCEDURES:
' * PastePicture : The entry point for 'Setting' the Image
' * CreatePicture : Private function to convert a bitmap or metafile handle to an OLE reference
' * fnOLEError : Get the error text for an OLE error code
' * SaveClip2Bit : The entry point for 'Saving' the Image, calls for PastePicture
' * AltPrintScreen: Performs the automation of Alt + PrtScrn, for getting the Active Window.
' * Pause : Makes the program wait, to make sure proper screen capture takes place.
'**************************************************************************************************
Option Explicit
Option Compare Text
'Declare a UDT to store a GUID for the IPicture OLE Interface
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
'Declare a UDT to store the bitmap information
Private Type uPicDesc
Size As Long
Type As Long
hPic As Long
hPal As Long
End Type
'Windows API Function Declarations
#If Win64 = 1 And VBA7 = 1 Then
'Does the clipboard contain a bitmap/metafile?
Private Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Integer) As Long
'Open the clipboard to read
Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
'Get a pointer to the bitmap/metafile
Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As Long
'Close the clipboard
Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
'Convert the handle into an OLE IPicture interface.
Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleaut32.dll" (PicDesc As uPicDesc, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
'Create our own copy of the metafile, so it doesn't get wiped out by subsequent clipboard updates.
Declare PtrSafe Function CopyEnhMetaFile Lib "gdi32" Alias "CopyEnhMetaFileA" (ByVal hemfSrc As Long, ByVal lpszFile As String) As Long
'Create our own copy of the bitmap, so it doesn't get wiped out by subsequent clipboard updates.
Declare PtrSafe Function CopyImage Lib "user32" (ByVal handle As Long, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
'Uses the Keyboard simulation
Private Declare PtrSafe Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
#Else
'Does the clipboard contain a bitmap/metafile?
Private Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Integer) As Long
'Open the clipboard to read
Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
'Get a pointer to the bitmap/metafile
Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As Long
'Close the clipboard
Private Declare Function CloseClipboard Lib "user32" () As Long
'Convert the handle into an OLE IPicture interface.
Private Declare Function OleCreatePictureIndirect Lib "oleaut32.dll" (PicDesc As uPicDesc, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
'Create our own copy of the metafile, so it doesn't get wiped out by subsequent clipboard updates.
Declare Function CopyEnhMetaFile Lib "gdi32" Alias "CopyEnhMetaFileA" (ByVal hemfSrc As Long, ByVal lpszFile As String) As Long
'Create our own copy of the bitmap, so it doesn't get wiped out by subsequent clipboard updates.
Declare Function CopyImage Lib "user32" (ByVal handle As Long, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
'Uses the Keyboard simulation
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
#End If
'The API format types we're interested in
Const CF_BITMAP = 2
Const CF_PALETTE = 9
Const CF_ENHMETAFILE = 14
Const IMAGE_BITMAP = 0
Const LR_COPYRETURNORG = &H4
Private Const KEYEVENTF_KEYUP = &H2
Private Const VK_SNAPSHOT = &H2C
Private Const VK_MENU = &H12
' Subroutine : AltPrintScreen
' Purpose : Capture the Active window, and places on the Clipboard.
Sub AltPrintScreen()
keybd_event VK_MENU, 0, 0, 0
keybd_event VK_SNAPSHOT, 0, 0, 0
keybd_event VK_SNAPSHOT, 0, KEYEVENTF_KEYUP, 0
keybd_event VK_MENU, 0, KEYEVENTF_KEYUP, 0
End Sub
Sub PrintScreen()
keybd_event VK_SNAPSHOT, 1, 0, 0
End Sub
' Subroutine : PastePicture
' Purpose : Get a Picture object showing whatever's on the clipboard.
Function PastePicture() As IPicture
'Some pointers
Dim h As Long, hPtr As Long, hPal As Long, lPicType As Long, hCopy As Long
'Check if the clipboard contains the required format
If IsClipboardFormatAvailable(CF_BITMAP) Then
'Get access to the clipboard
h = OpenClipboard(0&)
If h > 0 Then
'Get a handle to the image data
hPtr = GetClipboardData(CF_BITMAP)
hCopy = CopyImage(hPtr, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG)
'Release the clipboard to other programs
h = CloseClipboard
'If we got a handle to the image, convert it into a Picture object and return it
If hPtr <> 0 Then Set PastePicture = CreatePicture(hCopy, 0, CF_BITMAP)
End If
End If
End Function
' Subroutine : CreatePicture
' Purpose : Converts a image (and palette) handle into a Picture object.
' NOTE : Requires a reference to the "OLE Automation" type library
Private Function CreatePicture(ByVal hPic As Long, ByVal hPal As Long, ByVal lPicType) As IPicture
' IPicture requires a reference to "OLE Automation"
Dim r As Long, uPicInfo As uPicDesc, IID_IDispatch As GUID, IPic As IPicture
'OLE Picture types
Const PICTYPE_BITMAP = 1
Const PICTYPE_ENHMETAFILE = 4
' Create the Interface GUID (for the IPicture interface)
With IID_IDispatch
.Data1 = &H7BF80980
.Data2 = &HBF32
.Data3 = &H101A
.Data4(0) = &H8B
.Data4(1) = &HBB
.Data4(2) = &H0
.Data4(3) = &HAA
.Data4(4) = &H0
.Data4(5) = &H30
.Data4(6) = &HC
.Data4(7) = &HAB
End With
' Fill uPicInfo with necessary parts.
With uPicInfo
.Size = Len(uPicInfo) ' Length of structure.
.Type = PICTYPE_BITMAP ' Type of Picture
.hPic = hPic ' Handle to image.
.hPal = hPal ' Handle to palette (if bitmap).
End With
' Create the Picture object.
r = OleCreatePictureIndirect(uPicInfo, IID_IDispatch, True, IPic)
' If an error occurred, show the description
If r <> 0 Then Debug.Print "Create Picture: " & fnOLEError(r)
' Return the new Picture object.
Set CreatePicture = IPic
End Function
' Subroutine : fnOLEError
' Purpose : Gets the message text for standard OLE errors
Private Function fnOLEError(lErrNum As Long) As String
'OLECreatePictureIndirect return values
Const E_ABORT = &H80004004
Const E_ACCESSDENIED = &H80070005
Const E_FAIL = &H80004005
Const E_HANDLE = &H80070006
Const E_INVALIDARG = &H80070057
Const E_NOINTERFACE = &H80004002
Const E_NOTIMPL = &H80004001
Const E_OUTOFMEMORY = &H8007000E
Const E_POINTER = &H80004003
Const E_UNEXPECTED = &H8000FFFF
Const S_OK = &H0
Select Case lErrNum
Case E_ABORT
fnOLEError = " Aborted"
Case E_ACCESSDENIED
fnOLEError = " Access Denied"
Case E_FAIL
fnOLEError = " General Failure"
Case E_HANDLE
fnOLEError = " Bad/Missing Handle"
Case E_INVALIDARG
fnOLEError = " Invalid Argument"
Case E_NOINTERFACE
fnOLEError = " No Interface"
Case E_NOTIMPL
fnOLEError = " Not Implemented"
Case E_OUTOFMEMORY
fnOLEError = " Out of Memory"
Case E_POINTER
fnOLEError = " Invalid Pointer"
Case E_UNEXPECTED
fnOLEError = " Unknown Error"
Case S_OK
fnOLEError = " Success!"
End Select
End Function
' Routine : SaveClip2Bit
' Purpose : Saves Picture object to desired location.
' Arguments : Path to save the file
Public Sub SaveClip2Bit(savePath As String)
On Error GoTo errHandler:
AltPrintScreen
Pause (3)
SavePicture PastePicture, savePath
errExit:
Exit Sub
errHandler:
Debug.Print "Save Picture: (" & Err.Number & ") - " & Err.Description
Resume errExit
End Sub
' Routine : Pause
' Purpose : Gives a short interval for proper image capture.
' Arguments : Seconds to wait.
Public Function Pause(NumberOfSeconds As Variant)
On Error GoTo Err_Pause
Dim PauseTime As Variant, start As Variant
PauseTime = NumberOfSeconds
start = Timer
Do While Timer < start + PauseTime
DoEvents
Loop
Exit_Pause:
Exit Function
Err_Pause:
MsgBox Err.Number & " - " & Err.Description, vbCritical, "Pause()"
Resume Exit_Pause
End Function
Use like this:
SaveClip2Bit "C:\test\test.bmp"

Userform.show on cursor position

i wish to create code that will open form at any form object by mouse hover. For now i have form.show and unload form on mousemove event.
I can add some variable to each object to recognize their position on window and define top left like on example
Private Sub UserForm_Initialize()
If par_hoverForm = uf_Generator.com_MaxQty.Name Then
Me.Top = Application.Top + uf_Generator.Top + uf_Generator.fr_settings.Top + uf_Generator.com_MaxQty.Top + uf_Generator.com_MaxQty.Height + 30
Me.Left = uf_Generator.Left + uf_Generator.fr_settings.Left + uf_Generator.com_MaxQty.Left + 10
End If
End Sub
but is it possible to form.show at cursor position + some offset? this code getting cursor coordinates but i dont know how to convert it to top/left.
#If VBA7 Then
Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
#Else
Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
#End If
' Create custom variable that holds two integers
Type POINTAPI
Xcoord As Long
Ycoord As Long
End Type
Sub GetCursorPosDemo()
Dim llCoord As POINTAPI
' Get the cursor positions
GetCursorPos llCoord
' Display the cursor position coordinates
MsgBox "X Position: " & llCoord.Xcoord & vbNewLine & "Y Position: " & llCoord.Ycoord
End Sub
any advice are welcome
I will show you one way to do it, which should allow you to customize it to your needs.
First, this is the base code to convert the pixels to point and move an object to the pointer. I've set it to be called with another object, but obviously you can code a specific object in there:
Private Type POINTAPI
X As Long
Y As Long
End Type
Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Public Sub position(this As Object)
Dim lngCurPos As POINTAPI
Dim DocZero As POINTAPI
Dim PointsPerPixelY As Double
Dim PointsPerPixelX As Double
Dim hdc As Long
hdc = GetDC(0)
PointsPerPixelY = 72 / GetDeviceCaps(hdc, 90)
PointsPerPixelX = 72 / GetDeviceCaps(hdc, 88)
ReleaseDC 0, hdc
DocZero.Y = ActiveWindow.PointsToScreenPixelsY(0)
DocZero.X = ActiveWindow.PointsToScreenPixelsX(0)
GetCursorPos lngCurPos
this.Top = (lngCurPos.Y - DocZero.Y) * PointsPerPixelY
this.Left = (lngCurPos.X - DocZero.X) * PointsPerPixelX
End Sub
This worked well for my little test object, but not quite for my form.
For my form, I changed the last two rows to: (Your Mileage May Vary)
this.Top = (lngCurPos.Y - DocZero.Y) * PointsPerPixelY + this.Height * 2.2
this.Left = (lngCurPos.X - DocZero.X) * PointsPerPixelX - this.Width / 2.5
Then, in my Form1 TextBox, I use Call position(UserForm2) in the MouseMove event to update the position of UserForm2 constantly.
One problem I had, was that I was also calling UserForm2.show, which kept resetting the position, making it jump around.
To get around this, I added another function:
Public Function IsLoaded(formName As String) As Boolean
Dim frm As Object
For Each frm In VBA.UserForms
If frm.Name = formName Then
IsLoaded = True
Exit Function
End If
Next frm
IsLoaded = False
End Function
And then my UserForm1 Code looks like this:
Private Sub TextBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Not IsLoaded("UserForm2") Then UserForm2.show
Call position(UserForm2)
End Sub
I did not add any code to remove Form2 again, since you have that already.
This was my result:

How to achieve a responsive mouseover effect on Controls in a modeless vba Userform on a large Worksheet

I have the following code, which works perfectly fine on a normal VBA Userform: whenever the mouse hovers anywhere over the label, the color of said label is red, otherwise it's white. This effect is very responsive and makes the label feel very Button-like.
Code of the Userform with 1 label on it:
Dim active As Boolean
Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If active = False Then
Label1.BackColor = RGB(255, 0, 0)
active = True
End If
End Sub
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If active = True Then
Label1.BackColor = RGB(255, 255, 255)
active = False
End If
End Sub
If i change the UserForm to be called modeless, from a module like this:
Sub loader()
UserForm1.Show vbModeless
End Sub
The Mouseover effect still works but it becomes very sluggish and unresponsive. It seems like the refreshrate has gone down massively.
Edit: I found out this problem only appears when the Active Worksheet is a big one, which obviously slows everything down a bit. The sheet that gives me headaches has around 1000 rows and 50 columns with many cells containing longer strings. I think the sheet itself is around 1MB of Data. Forumlas are set to manual refresh only. I'm on a Laptop with an i7 8550U and 8GB of ram using Office 32 bit.
My question is:
Is it possible to achieve the behaviour of the modal Userform in the modeless one?
I looked for ways to manipulate the refreshrate of a modeless Userform but couldn't find anything helpful.
An alternative solution would be to make scrolling in the Worksheet possible while the Userform is shown in modal mode.
Another solution might be making the UserForm modal while the mouse is on it and modeless once the mouse leaves a certain area (the UserForm borders). Is that possible?
Solution 1 - recommended
Add the following code to your UserForm:
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#Else
Private Declare Sub Sleep Lib "kernel32" (ByVal lngMilliSeconds As Long)
#End If
Dim m_isOpen As Boolean
Private Sub UserForm_Activate()
m_isOpen = True
Do While m_isOpen
Sleep 15 'this correlates to the "refresh rate" of the mouseover effect,
DoEvents 'sleep 100 leads to sluggish behaviour
Loop
End Sub
Private Sub UserForm_Terminate()
m_isOpen = False
End Sub
The mouseover effect should now be responsive again.
Solution 2
This is an implementation of the last of my proposed solution ideas.
It will make the UserForm automatically go modal while the mouse is inside the area of the UserForm and go modeless once the mouse leaves this zone. Just add this code to a plain UserForm:
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As PointAPI) As Long
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnd As LongPtr) As LongPtr
Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hdc As LongPtr, ByVal nIndex As Long) As Long
Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hWnd As LongPtr, ByVal hdc As LongPtr) As Long
#Else
Private Public Declare Function GetCursorPos Lib "user32" (lpPoint As PointAPI) As Long
Private Public Declare Sub Sleep Lib "kernel32" (ByVal lngMilliSeconds As Long)
Private Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function GetDeviceCaps Lib "Gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, ByVal hDC As Long) As Long
#End If
Private Const LOGPIXELSX = 88
Private Const LOGPIXELSY = 90
Private Type PointAPI
x As Long
y As Long
End Type
Dim m_modal As Boolean
Dim m_modalityIndicator As Object
Private Function pointsPerPixelX() As Double
Dim hdc As LongPtr 'Used for transforming windows API Mouse-coordinates
hdc = GetDC(0) 'to vba coordinates
pointsPerPixelX = 72 / GetDeviceCaps(hdc, LOGPIXELSX)
ReleaseDC 0, hdc
End Function
Private Function pointsPerPixelY() As Double
Dim hdc As LongPtr 'Used for transforming windows API Mouse-coordinates
hdc = GetDC(0) 'to vba coordinates
pointsPerPixelY = 72 / GetDeviceCaps(hdc, LOGPIXELSY)
ReleaseDC 0, hdc
End Function
Private Function GetX() As Long 'Get current X coordinate of Mouse
Dim n As PointAPI
GetCursorPos n
GetX = n.x
End Function
Private Function GetY() As Long 'Get current y coordinate of Mouse
Dim n As PointAPI
GetCursorPos n
GetY = n.y
End Function
Sub MonitorMouse()
Dim x As Long, y As Long
On Error GoTo userform_closed
Do While True
Sleep 15: DoEvents
x = GetX(): y = GetY()
With Me
If m_modal Then
If x < .left / pointsPerPixelX() Or _
x > (.left + .Width) / pointsPerPixelX() Or _
y < .top / pointsPerPixelY() Or _
y > (.top + .Height) / pointsPerPixelY() Then
.Hide
.show vbModeless
m_modal = False
End If
Else
If x > .left / pointsPerPixelX() And _
x < (.left + .Width) / pointsPerPixelX() And _
y > .top / pointsPerPixelY() And _
y < (.top + .Height) / pointsPerPixelY() Then
.Hide
m_modal = True
.show
Exit Sub
End If
End If
End With
Loop
Exit Sub
userform_closed:
err.Clear: On Error GoTo 0
End Sub
Private Function isFormModeless() As Boolean
On Error GoTo EH
Me.show vbModeless: isFormModeless = True
Exit Function
EH:
isFormModeless = False
End Function
Private Sub UserForm_Activate()
If isFormModeless Then
m_modalityIndicator.Caption = "modeless"
Else
m_modalityIndicator.Caption = "modal"
End If
MonitorMouse
End Sub
Private Sub UserForm_Initialize()
Set m_modalityIndicator = Me.Controls.Add("Forms.Label.1", "ModalityIndicator", True)
With m_modalityIndicator
.left = Me.left
.top = Me.top
.Width = Me.Width
.Height = Me.Height
.Font.size = 36
End With
End Sub

Determine if application is running with Excel

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

SendInput VB Basic Example

I hope someone can assist, I trying to find an example of a SendInput code that simulate keyboard commands, I wish to find the notepad window and enter a test message.
I had initially used SendKeys on a project I am working on, the SendKeys function enabled me to forward keyboard commands to a bespoke software that we are use at our workplace.
I hope someone can help, the examples on the internet does not seem to work.
Can anyone also advise if the SendInput method is intrusive, i.e. will it cause any damage to the recipient window.
The SendKey method worked, however the reliability seems to be very hit and miss.
Many Thanks
Sara
Edit:
I have found the following code on the internet, is the following a SendInput method? As I notice that term 'SendKey' is used?
Private Declare Function SendInput Lib "user32.dll" _
(ByVal nInputs As Long, ByRef pInputs As Any, _
ByVal cbSize As Long) As Long
Private Declare Function VkKeyScan Lib "user32" Alias "VkKeyScanA" _
(ByVal cChar As Byte) As Integer
Private Type KeyboardInput ' typedef struct tagINPUT {
dwType As Long ' DWORD type;
wVK As Integer ' union {MOUSEINPUT mi;
wScan As Integer ' KEYBDINPUT ki;
dwFlags As Long ' HARDWAREINPUT hi;
dwTime As Long ' };
dwExtraInfo As Long ' }INPUT, *PINPUT;
dwPadding As Currency ' 8 extra bytes, because mouses take more.
End Type
Private Const INPUT_MOUSE As Long = 0
Private Const INPUT_KEYBOARD As Long = 1
Private Const KEYEVENTF_KEYUP As Long = 2
Private Const VK_LSHIFT = &HA0
Public Sub SendKey(ByVal Data As String)
Dim ki() As KeyboardInput
Dim i As Long
Dim o As Long ' output buffer position
Dim c As String ' character
ReDim ki(1 To Len(Data) * 4) As KeyboardInput
o = 1
For i = 1 To Len(Data)
c = Mid$(Data, i, 1)
Select Case c
Case "A" To "Z": ' upper case
ki(o).dwType = INPUT_KEYBOARD 'shift down
ki(o).wVK = VK_LSHIFT
ki(o + 1) = ki(o) ' key down
ki(o + 1).wVK = VkKeyScan(Asc(c))
ki(o + 2) = ki(o + 1) ' key up
ki(o + 2).dwFlags = KEYEVENTF_KEYUP
ki(o + 3) = ki(o) ' shift up
ki(o + 3).dwFlags = KEYEVENTF_KEYUP
o = o + 4
Case Else: ' lower case
ki(o).dwType = INPUT_KEYBOARD
ki(o).wVK = VkKeyScan(Asc(c))
ki(o + 1) = ki(o)
ki(o + 1).dwFlags = KEYEVENTF_KEYUP
o = o + 2
End Select
Next i
Debug.Print SendInput(o - 1, ki(1), LenB(ki(1))),
Debug.Print Err.LastDllError
End Sub
Private Sub Command1_Click()
Text1.Text = ""
Text1.SetFocus
DoEvents
Call SendKey("This Is A Test")
End Sub
The following code is not for VB.net but VB/VBA, its similar to the sendkeys method but probably a little more reliable as it sends the keys specifically to the target application. (the post where i got it shows the sendkeys method too)
Public Declare Function FindWindowX Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, _
ByVal hWnd2 As Long, ByVal lpsz1 As Long, ByVal lpsz2 As Long) As Long
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Integer, ByVal lParam As Integer) As Long
Private Const WM_KEYDOWN = &H100
Private Const WM_KEYUP = &H101
Sub Three()
hWind = FindWindow(vbNullString, "Untitled - Notepad")
cWind = FindWindowX(hWind, 0, 0, 0)
Debug.Print PostMessage(cWind, WM_KEYDOWN, vbKeyA, 0)
Debug.Print PostMessage(cWind, WM_KEYDOWN, vbKeyB, 0)
Debug.Print PostMessage(cWind, WM_KEYDOWN, vbKeyC, 0)
End Sub
Code taken from this forum post
If you paste this into a new module in Excel/VBA and have an new instance of notepad running, when the sub is executed "abc" should appear in notepad.
I don't see how using this, or the sendkeys method could "damage" the target window. So long as you time the messages properly (not sending tonnes of characters to the window all at the same time) it shouldn't cause any problems.
I had managed to find another SendInput script online, I have copied it below for anyone else who may be interested.
I have been using SendKeys to copy data from a spreadsheet and enter these on a system at work, this saves valuable time as there is a vast amount of information that needs to be entered.
The SendKeys function worked without any issues (although due to reliability issues I had to consider alternatives), will the SendInput cause any issues to the other window i.e. other than simulating keyboard buttons will it interfere with any other functions of the target window?
Private Declare Function SendInput Lib "user32.dll" _
(ByVal nInputs As Long, ByRef pInputs As Any, _
ByVal cbSize As Long) As Long
Private Declare Function VkKeyScan Lib "user32" Alias "VkKeyScanA" _
(ByVal cChar As Byte) As Integer
Private Type KeyboardInput ' typedef struct tagINPUT {
dwType As Long ' DWORD type;
wVK As Integer ' union {MOUSEINPUT mi;
wScan As Integer ' KEYBDINPUT ki;
dwFlags As Long ' HARDWAREINPUT hi;
dwTime As Long ' };
dwExtraInfo As Long ' }INPUT, *PINPUT;
dwPadding As Currency ' 8 extra bytes, because mouses take more.
End Type
Private Const INPUT_MOUSE As Long = 0
Private Const INPUT_KEYBOARD As Long = 1
Private Const KEYEVENTF_KEYUP As Long = 2
Private Const VK_LSHIFT = &HA0
Public Sub SendKey(ByVal Data As String)
Dim ki() As KeyboardInput
Dim i As Long
Dim o As Long ' output buffer position
Dim c As String ' character
ReDim ki(1 To Len(Data) * 4) As KeyboardInput
o = 1
For i = 1 To Len(Data)
c = Mid$(Data, i, 1)
Select Case c
Case "A" To "Z": ' upper case
ki(o).dwType = INPUT_KEYBOARD 'shift down
ki(o).wVK = VK_LSHIFT
ki(o + 1) = ki(o) ' key down
ki(o + 1).wVK = VkKeyScan(Asc(c))
ki(o + 2) = ki(o + 1) ' key up
ki(o + 2).dwFlags = KEYEVENTF_KEYUP
ki(o + 3) = ki(o) ' shift up
ki(o + 3).dwFlags = KEYEVENTF_KEYUP
o = o + 4
Case Else: ' lower case
ki(o).dwType = INPUT_KEYBOARD
ki(o).wVK = VkKeyScan(Asc(c))
ki(o + 1) = ki(o)
ki(o + 1).dwFlags = KEYEVENTF_KEYUP
o = o + 2
End Select
Next i
Debug.Print SendInput(o - 1, ki(1), LenB(ki(1))),
'Debug.Print Err.LastDllError
End Sub
Private Sub Command1_Click()
Text1.Text = ""
Text1.SetFocus
DoEvents
Call SendKey("This Is A Test")
End Sub

Resources