This is very cool
Option Explicit Option Compare Text 'by cytop Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _ (ByVal lpClassName As String, ByVal lpWindowName As String) As Long Private Declare Function ClientToScreen Lib "user32" _ (ByVal hwnd As Long, lpPoint As POINTAPI) As Long Private Declare Function GetDeviceCaps Lib "Gdi32" (ByVal hDC As Long, ByVal _ nIndex As Long) As Long Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal _ hDC As Long) As Long Private Declare Function SetCursorPos Lib "user32" (ByVal X As Long, ByVal Y As Long) As Long Private Const LOGPIXELSX = 88 Private Const LOGPIXELSY = 90 Private Type POINTAPI X As Long Y As Long End Type Public Sub CenterMouseOver(f As Object, c As Object) Dim P As POINTAPI Dim lngHwnd As Long '// Window handle - might be an idea to make sure caption '// is not the default 'UserformX' to avoid possibility '// of finding the wrong window - I'll tignten this '// up later to eliminate that possibility if needed. lngHwnd = FindWindow(vbNullString, f.Caption) '// PointPerPixelX/Y is the key to this. '// Find the mid point of the control based purely on the form '// co-ordinates and convert based on the number of points '// per pixel (X & Y are different - See constants declaration). P.X = (c.Left + (c.Width \ 2)) / PointsPerPixelX P.Y = (c.Top + (c.Height \ 2)) / PointsPerPixelY '// Convert the control location baased on the userform '// to an absolute Screen position based on 0,0 being '// top left handcorner of the screen/ ClientToScreen lngHwnd, P '// And simply put the cursor there SetCursorPos P.X, P.Y End Sub Private Function PointsPerPixelX() As Double Dim hDC As Long '// Desktop Device Context hDC = GetDC(0) '// A point is 1/72th of an inch and LOGPIXELSX returns '// the number of pixels per logical inch, so divided to give '// the width of a pixel in Excel's userform coordinates PointsPerPixelX = 72 / GetDeviceCaps(hDC, LOGPIXELSX) ReleaseDC 0, hDC End Function Public Function PointsPerPixelY() As Double '// Same as PointsPerPixelX Dim hDC As Long hDC = GetDC(0) PointsPerPixelY = 72 / GetDeviceCaps(hDC, LOGPIXELSY) ReleaseDC 0, hDC End Function
and in the userform activate event
'// Pass userform (Needed to get handle) and the '// control. This works for any controi with a '// Left/Top property CenterMouseOver Me, ComboBox1
the code must go in the Activate event rather than the Initialize event.
It seems the userform does not really exist as an object (as far as Windows is concerned, at least) until after the Initialize event has completed - so the code will not work correctly in the Initialize event.
Once you get under the hood with the API you run into loads of little issues like this... All part of the way VBA is structured.