I’m trying to implement mousewheel scroll in a large text box. I found Peter Thornton’s code and it works well for frames and userforms (only using it for the former right now), but not for text box because text boxes don’t have a .ScrollTop property.
The code I use now isn’t actually a scroll-wheel function. Full code below, but the relevant portion is:
If TypeName(mControl) = "TextBox" Then
If reasonCustKeyPressed Then
lngSelStart = .SelStart
.CurLine = .CurLine
lngOldLinePos = lngSelStart - .SelStart
reasonCustKeyPressed = False
End If
If lParam.Hwnd > 0 Then
.CurLine = Application.Max(0, .CurLine - cTBOX_SCROLLCHANGE)
Else
.CurLine = Application.Min(.LineCount - 1, .CurLine + cTBOX_SCROLLCHANGE)
End If
lngSelStart = .SelStart
If .CurLine < .LineCount - 1 Then
.CurLine = .CurLine + 1
.SelStart = .SelStart - 1
Else
.SelStart = Len(.Text)
End If
lngNewLineLen = .SelStart - lngSelStart
.SelStart = Application.Min(lngSelStart + lngOldLinePos, lngSelStart + lngNewLineLen)
End If
Can anybody give any suggestions on how I could implement actual scroll wheel functionality? One idea I have is to find:
Whether the scroll bar was active (the content isn't always long enough to activate it - but don't know how, windows API?).
Storing .SelStart in a temporary variable
Find the top / bottom line somehow (I can't find any property of the textbox like this in documentation)
Increase the bottom line / decrease the top line (as appropriate) by setting .CurLine
Resetting .SelStart to the temporary variable (or top / bottom line, if the line stored in the temporary variable is no longer visible).
This isn't ideal either, however, because it doesn't preserve the previous cursor position if you scroll too far. I might be able to work around it by storing the .SelStart variable in the module's state and jumping back to it on the KeyDown event. There are some really big gaps, however, that I don't really have a good idea of how to fill. Any ideas (for this or other, more elegant solutions)? Thank you in advance.
Full code:
Option Explicit
' Based on code from Peter Thornton here:
' http://social.msdn.microsoft.com/Forums/en-US/7d584120-a929-4e7c-9ec2-9998ac639bea/mouse-scroll-in-userform-listbox-in-excel-2010?forum=isvvba
Private Type POINTAPI
X As Long
y As Long
End Type
Private Type MOUSEHOOKSTRUCT
pt As POINTAPI
Hwnd As Long
wHitTestCode As Long
dwExtraInfo As Long
End Type
Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function GetWindowLong Lib "user32.dll" _
Alias "GetWindowLongA" ( _
ByVal Hwnd As Long, _
ByVal nIndex As Long) As Long
Private Declare Function SetWindowsHookEx Lib "user32" _
Alias "SetWindowsHookExA" ( _
ByVal idHook As Long, _
ByVal lpfn As Long, _
ByVal hmod As Long, _
ByVal dwThreadId As Long) As Long
Private Declare Function CallNextHookEx Lib "user32" ( _
ByVal hHook As Long, _
ByVal nCode As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Declare Function UnhookWindowsHookEx Lib "user32" ( _
ByVal hHook As Long) As Long
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 Declare Function WindowFromPoint Lib "user32" ( _
ByVal xPoint As Long, _
ByVal yPoint As Long) As Long
Private Declare Function GetCursorPos Lib "user32.dll" ( _
ByRef lpPoint As POINTAPI) As Long
Declare Function GetActiveWindow Lib "user32" () As Long
Private Const WH_MOUSE_LL As Long = 14
Private Const WM_MOUSEWHEEL As Long = &H20A
Private Const HC_ACTION As Long = 0
Private Const GWL_HINSTANCE As Long = (-6)
Private Const WM_KEYDOWN As Long = &H100
Private Const WM_KEYUP As Long = &H101
Private Const VK_UP As Long = &H26
Private Const VK_DOWN As Long = &H28
Private Const WM_LBUTTONDOWN As Long = &H201
Private Const cFRAME_SCROLLCHANGE As Long = 20
Private Const cTBOX_SCROLLCHANGE As Long = 1
Private mLngMouseHook As Long
Private mControlHwnd As Long
Private mbHook As Boolean
Private lngOldLinePos As Long
Dim mControl As Object
Sub HookFormScroll(oControl As Object, strFormCapt As String)
Dim lngAppInst As Long
Dim hwndUnderCursor As Long
Set mControl = oControl
hwndUnderCursor = FindWindow("ThunderDFrame", strFormCapt)
Debug.Print "Form window: " & hwndUnderCursor
If mControlHwnd <> hwndUnderCursor Then
UnhookFormScroll
Debug.Print "Unhook old proc"
mControlHwnd = hwndUnderCursor
lngAppInst = GetWindowLong(mControlHwnd, GWL_HINSTANCE)
If Not mbHook Then
mLngMouseHook = SetWindowsHookEx(WH_MOUSE_LL, AddressOf mouseProc, lngAppInst, 0)
mbHook = mLngMouseHook <> 0
If mbHook Then Debug.Print "Form hooked"
End If
End If
End Sub
Sub UnhookFormScroll()
If mbHook Then
UnhookWindowsHookEx mLngMouseHook
mLngMouseHook = 0
mControlHwnd = 0
mbHook = False
End If
End Sub
Private Function mouseProc( _
ByVal nCode As Long, ByVal wParam As Long, _
ByRef lParam As MOUSEHOOKSTRUCT) As Long
Dim lngSelStart As Long, lngNewLineLen As Long
On Error GoTo errH 'Resume Next
If (nCode = HC_ACTION) Then
If GetActiveWindow = mControlHwnd Then
If wParam = WM_MOUSEWHEEL Then
mouseProc = True
With mControl
If TypeName(mControl) = "Frame" Then
If lParam.Hwnd > 0 Then
.ScrollTop = Application.Max(0, .ScrollTop - cFRAME_SCROLLCHANGE)
Else
.ScrollTop = Application.Min(.ScrollHeight - .InsideHeight, .ScrollTop + cFRAME_SCROLLCHANGE)
End If
Else
If TypeName(mControl) = "TextBox" Then
If reasonCustKeyPressed Then
lngSelStart = .SelStart
.CurLine = .CurLine
lngOldLinePos = lngSelStart - .SelStart
reasonCustKeyPressed = False
End If
If lParam.Hwnd > 0 Then
.CurLine = Application.Max(0, .CurLine - cTBOX_SCROLLCHANGE)
Else
.CurLine = Application.Min(.LineCount - 1, .CurLine + cTBOX_SCROLLCHANGE)
End If
lngSelStart = .SelStart
If .CurLine < .LineCount - 1 Then
.CurLine = .CurLine + 1
.SelStart = .SelStart - 1
Else
.SelStart = Len(.Text)
End If
lngNewLineLen = .SelStart - lngSelStart
.SelStart = Application.Min(lngSelStart + lngOldLinePos, lngSelStart + lngNewLineLen)
End If
End If
End With
Exit Function
End If
End If
End If
mouseProc = CallNextHookEx( _
mLngMouseHook, nCode, wParam, ByVal lParam)
Exit Function
errH:
UnhookFormScroll
End Function
Daniel Pineault's code worked fine for me. It's one module and a few lines of code to put on your form.
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 a macro that copies rtf format word document to outlook email for sending it to many recipients. However, due to this a copy of the text is also saved on the clipboard and the code crashes if many recipients are there. I was using the below code for clearing the clipboard but the code is no longer working after the office 365 update. I tried changing the declare functions to include 'Ptrsafe' but still not able to run it. Any help would be greatly appreciated. Thanks
Declare Function AccessibleObjectFromWindow Lib "oleacc" ( _
ByVal hwnd As Long, ByVal dwId As Long, _
riid As tGUID, ppvObject As Object) As Long
Declare Function AccessibleChildren Lib "oleacc" _
(ByVal paccContainer As IAccessible, ByVal iChildStart As Long, _
ByVal cChildren As Long, rgvarChildren As Variant, _
pcObtained As Long) As Long
Declare Function FindWindow Lib "User32" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Declare Function GetParent Lib "User32" (ByVal hwnd As Long) As Long
Declare Function EnumChildWindows Lib "User32" (ByVal hwndParent _
As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Declare Function GetClassName Lib "User32" Alias "GetClassNameA" (ByVal hwnd As Long, _
ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Declare Function SendMessage Lib "User32" Alias "SendMessageA" (ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
Declare Function FindWindowEx Lib "User32" Alias "FindWindowExA" (ByVal hWnd1 As Long, _
ByVal hWnd2 As Long, ByVal lpClass As String, ByVal lpCaption As String) As Long
Const CHILDID_SELF = 0&
Const ROLE_PUSHBUTTON = &H2B&
Const WM_GETTEXT = &HD
Type tGUID
lData1 As Long
nData2 As Integer
nData3 As Integer
abytData4(0 To 7) As Byte
End Type
Type AccObject
objIA As IAccessible
lngChild As Long
End Type
Dim lngChild As Long
Dim strClass As String
Dim strCaption As String
'Using Active Accessibility to clear Office clipboard
'Assumption:
'this is running within Word or Excel as a macro, thus the global Application object is available
Sub ClearOfficeClipboard()
Static accButton As AccObject
If accButton.objIA Is Nothing Then
Dim fShown As Boolean
fShown = CommandBars("Office Clipboard").Visible 'Office 2013+ version
If Not (fShown) Then
CommandBars("Office Clipboard").Visible = True 'Office 2013+ version
End If
accButton = FindAccessibleChildInWindow(GetOfficeClipboardHwnd(Application), "Clear All", ROLE_PUSHBUTTON)
End If
If accButton.objIA Is Nothing Then
MsgBox "Unable to locate the ""Clear All"" button!"
Else
accButton.objIA.accDoDefaultAction accButton.lngChild
End If
CommandBars("Office Clipboard").Visible = False
End Sub
'Retrieve window class name
Function GetWndClass(ByVal hwnd As Long) As String
Dim buf As String
Dim retval As Long
buf = Space(256)
retval = GetClassName(hwnd, buf, 255)
GetWndClass = Left(buf, retval)
End Function
'Retrieve window title
Function GetWndText(ByVal hwnd As Long) As String
Dim buf As String
Dim retval As Long
buf = Space(256)
retval = SendMessage(hwnd, WM_GETTEXT, 255, buf)
GetWndText = Left(buf, InStr(1, buf, Chr(0)) - 1)
End Function
'The call back function used by EnumChildWindows
Function EnumChildWndProc(ByVal hChild As Long, ByVal lParam As Long) As Long
Dim found As Boolean
EnumChildWndProc = -1
If strClass > "" And strCaption > "" Then
found = StrComp(GetWndClass(hChild), strClass, vbTextCompare) = 0 And _
StrComp(GetWndText(hChild), strCaption, vbTextCompare) = 0
ElseIf strClass > "" Then
found = (StrComp(GetWndClass(hChild), strClass, vbTextCompare) = 0)
ElseIf strCaption > "" Then
found = (StrComp(GetWndText(hChild), strCaption, vbTextCompare) = 0)
Else
found = True
End If
If found Then
lngChild = hChild
EnumChildWndProc = 0
Else
EnumChildWndProc = -1
End If
End Function
'Find the window handle of a child window based on its class and titie
Function FindChildWindow(ByVal hParent As Long, Optional cls As String = "", Optional title As String = "") As Long
lngChild = 0
strClass = cls
strCaption = title
EnumChildWindows hParent, AddressOf EnumChildWndProc, 0
FindChildWindow = lngChild
End Function
'Retrieve the IAccessible interface from a window handle
'Reference:Jean Ross,Chapter 17: Accessibility in Visual Basic,Advanced Microsoft Visual Basic 6.0, 2nd Edition
Function IAccessibleFromHwnd(hwnd As Long) As IAccessible
Dim oIA As IAccessible
Dim tg As tGUID
Dim lReturn As Long
' Define the GUID for the IAccessible object
' {618736E0-3C3D-11CF-810C-00AA00389B71}
With tg
.lData1 = &H618736E0
.nData2 = &H3C3D
.nData3 = &H11CF
.abytData4(0) = &H81
.abytData4(1) = &HC
.abytData4(2) = &H0
.abytData4(3) = &HAA
.abytData4(4) = &H0
.abytData4(5) = &H38
.abytData4(6) = &H9B
.abytData4(7) = &H71
End With
' Retrieve the IAccessible object for the form
lReturn = AccessibleObjectFromWindow(hwnd, 0, tg, oIA)
Set IAccessibleFromHwnd = oIA
End Function
'Recursively looking for a child with specified accName and accRole in the accessibility tree
Function FindAccessibleChild(oParent As IAccessible, strName As String, lngRole As Long) As AccObject
Dim lHowMany As Long
Dim avKids() As Variant
Dim lGotHowMany As Long, i As Integer
Dim oChild As IAccessible
FindAccessibleChild.lngChild = CHILDID_SELF
If oParent.accChildCount = 0 Then
Set FindAccessibleChild.objIA = Nothing
Exit Function
End If
lHowMany = oParent.accChildCount
ReDim avKids(lHowMany - 1) As Variant
lGotHowMany = 0
If AccessibleChildren(oParent, 0, lHowMany, avKids(0), lGotHowMany) <> 0 Then
MsgBox "Error retrieving accessible children!"
Set FindAccessibleChild.objIA = Nothing
Exit Function
End If
'To do: the approach described in http://msdn.microsoft.com/msdnmag/issues/0400/aaccess/default.aspx
' are probably better and more reliable
On Error Resume Next
For i = 0 To lGotHowMany - 1
If IsObject(avKids(i)) Then
If StrComp(avKids(i).accName, strName) = 0 And avKids(i).accRole = lngRole Then
Set FindAccessibleChild.objIA = avKids(i)
Exit For
Else
Set oChild = avKids(i)
FindAccessibleChild = FindAccessibleChild(oChild, strName, lngRole)
If Not FindAccessibleChild.objIA Is Nothing Then
Exit For
End If
End If
Else
If StrComp(oParent.accName(avKids(i)), strName) = 0 And oParent.accRole(avKids(i)) = lngRole Then
Set FindAccessibleChild.objIA = oParent
FindAccessibleChild.lngChild = avKids(i)
Exit For
End If
End If
Next i
End Function
Function FindAccessibleChildInWindow(hwndParent As Long, strName As String, lngRole As Long) As AccObject
Dim oParent As IAccessible
Set oParent = IAccessibleFromHwnd(hwndParent)
If oParent Is Nothing Then
Set FindAccessibleChildInWindow.objIA = Nothing
Else
FindAccessibleChildInWindow = FindAccessibleChild(oParent, strName, lngRole)
End If
End Function
'Retrieve the window handle of the task pane
Function GetOfficeTaskPaneHwnd(app As Object) As Long
GetOfficeTaskPaneHwnd = FindChildWindow(app.hwnd, _
"MsoCommandBar", Application.CommandBars("Task Pane").NameLocal)
End Function
'Retrieve the window handle of the clipboard child window inside task pane
'The window title of the clipboard window seems to be language independent,
'making it a better start point to searching our UI element than the task pane window
Function GetOfficeClipboardHwnd(app As Object) As Long
GetOfficeClipboardHwnd = FindChildWindow(app.hwnd, , "Collect and Paste 2.0")
End Function```
We can clear the clipboard using a MsForms.DataObject. The code below creates one without the need to reference the MsForms library.
Sub ClearClipBoard()
With CreateObject("new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
.SetText ""
.PutInClipBoard
End With
End Sub
I have a code module where I create a text box with pressing a specific key and reduce the current selected cell value by the amount inserted in the text box. I came to the point where I created the text box. Now I need to access the events of the text box outside the Worksheet modules. I found out that I could create a class module with the WihtEvents property. Unfortunate this does not seem to work. Here the code which is executed to make the control:
Dim objControl As BankingEventSink
Private Sub ReduceCell()
If IsNumeric(ActiveCell.Text) Then
Dim value As Double
value = CDbl(ActiveCell.Text)
ActiveSheet.Shapes.AddOLEObject(ClassType:="Forms.TextBox.1").Name = "ReduceCellTextBox"
With ActiveSheet.OLEObjects("ReduceCellTextBox")
.Top = ActiveCell.Top + ActiveCell.Height
.Left = ActiveCell.Left
End With
ActiveSheet.OLEObjects("ReduceCellTextBox").Activate
Set objControl = New BankingEventSink
objControl.Init (ActiveSheet.OLEObjects("ReduceCellTextBox").Object)
Else
RethrowKeys ("{BS}{-}")
End If
End Sub
The code of the class module:
Dim WithEvents objOLEControl As MSForms.TextBox
Public Sub Init(oleControl As MSForms.TextBox)
Set objOLEControl = oleControl
End Sub
Private Sub ReduceCellTextBox_Change()
MsgBox "Changed"
End Sub
Private Sub ReduceCellTextBox_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, _
ByVal Shift As Integer)
MsgBox "Key down: " & KeyCode
End Sub
What ever I write in the text box no event is triggered. Where is the mistake?
To remove the title bar from a VBA userform, you need to use API's FindWindow, SetWindowLong, GetWindowLong and SetWindowPos. HERE is my one stop place for APIs
Create your userform and place a textbox in it. For example
Next paste this code in the userform.
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _
ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function SetWindowLong Lib "user32" _
Alias "SetWindowLongA" (ByVal hwnd As Long, _
ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function GetWindowLong Lib "user32" _
Alias "GetWindowLongA" (ByVal hwnd As Long, _
ByVal nIndex As Long) 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 GWL_STYLE = (-16)
Private Const WS_CAPTION = &HC00000
Private Const WS_BORDER = &H800000
Private Enum ESetWindowPosStyles
SWP_SHOWWINDOW = &H40
SWP_HIDEWINDOW = &H80
SWP_FRAMECHANGED = &H20
SWP_NOACTIVATE = &H10
SWP_NOCOPYBITS = &H100
SWP_NOMOVE = &H2
SWP_NOOWNERZORDER = &H200
SWP_NOREDRAW = &H8
SWP_NOREPOSITION = SWP_NOOWNERZORDER
SWP_NOSIZE = &H1
SWP_NOZORDER = &H4
SWP_DRAWFRAME = SWP_FRAMECHANGED
HWND_NOTOPMOST = -2
End Enum
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Dim FrmWndh As Long, lStyle As Long
Dim tR As RECT
Private Sub UserForm_Activate()
FrmWndh = FindWindow(vbNullString, Me.Caption)
lStyle = GetWindowLong(FrmWndh, GWL_STYLE)
lStyle = lStyle And Not WS_CAPTION
SetWindowLong FrmWndh, GWL_STYLE, lStyle
SetWindowPos FrmWndh, 0, tR.Left, tR.Top, _
tR.Right - tR.Left, tR.Bottom - tR.Top, _
SWP_NOREPOSITION Or SWP_NOZORDER Or SWP_FRAMECHANGED Or WS_BORDER
Me.Repaint
End Sub
Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 27 Then Unload Me
End Sub
When you now run the userform, it will look like this. Since we have removed the userform's title bar, I have added a code so that when you press ESC from the textbox, the userform will unload. You can change that to whatever (reasonable) you like.
Objective:To make a program which tracks the users keystrokes and displays them in cell(1,1).
Issue: Solved
Code: See below for a working copy.
Code included key press for:
Shift Key,
Caps Lock,
Spacebar,
Backspace &
Esc
A Working example:
Option Explicit
Option Compare Text
Private Type POINTAPI
x As Long
Y As Long
End Type
Private Type MSG
hwnd As Long
Message As Long
wParam As Long
lParam As Long
time As Long
pt As POINTAPI
End Type
Private Type KeyboardBytes
kbByte(0 To 255) As Byte
End Type
Dim KB_Array As KeyboardBytes 'WAS kbArray
Const VK_BACK As Long = &H8 '= 8
Const VK_TAB As Long = &H9 '= 9
Const VK_RETURN As Long = &HD '= 13
Const VK_SHIFT As Long = &H10 '= 16
Const VK_CAPITAL As Long = &H14 '=20
Const VK_ESC As Long = &H1B '= 27
Const VK_SPACE As Long = &H20 '= 32
Const WM_KEYDOWN As Long = &H100 'for PeekMessage
Const PM_REMOVE As Long = &H1 'for PeekMessage
Const KEY_MASK As Integer = &HFF80 ' decimal -128
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal nVirtKey As Integer) As Integer
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetKeyboardState Lib "user32" (KB_Array As KeyboardBytes) As Long
Private Declare Function SetKeyboardState Lib "user32" (KB_Array As KeyboardBytes) As Long
Private Declare Function WaitMessage Lib "user32" () As Long
Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" _
(ByRef lpMsg As MSG, ByVal hwnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long
Sub woops()
Dim msgMessage As MSG, iKeyCode As Long, lXLhwnd As Long, aString As String
Dim aExit As Boolean, CapsLock_On As Boolean, ShiftKey_On As Boolean
AppActivate "Microsoft Excel"
Cells(1, 1) = ""
lXLhwnd = FindWindow("XLMAIN", Application.Caption)
GetKeyboardState KB_Array
CapsLock_On = IIf(KB_Array.kbByte(VK_CAPITAL) = 1, True, False)
Cells(2, 1) = CapsLock_On
Do
WaitMessage
If PeekMessage(msgMessage, lXLhwnd, WM_KEYDOWN, WM_KEYDOWN, PM_REMOVE) Then
iKeyCode = msgMessage.wParam
Run KeyPress(iKeyCode, KB_Array, aString, CapsLock_On, ShiftKey_On, aExit)
End If
Loop Until aExit = True
Cells(1, 1) = ""
End Sub
Private Function KeyPress(ByVal KeyAscii As Integer, ByRef KB_Array As KeyboardBytes, _
ByRef String1 As String, ByRef CapsLock_On As Boolean, _
ByRef ShiftKey_On As Boolean, ByRef aExit As Boolean)
Dim aValue As Long
Select Case KeyAscii
Case VK_BACK: If String1 <> "" Then String1 = Left(String1, Len(String1) - 1)
Case VK_SHIFT:
Case VK_CAPITAL:
KB_Array.kbByte(VK_CAPITAL) = IIf(KB_Array.kbByte(VK_CAPITAL) = 1, 0, 1)
SetKeyboardState KB_Array
CapsLock_On = IIf(KB_Array.kbByte(VK_CAPITAL) = 1, True, False)
Case VK_ESC: aExit = True
Case VK_SPACE: String1 = String1 & " "
Case 65 To 90: 'A to Z
If CapsLock_On = False Then aValue = KeyAscii + 32 Else aValue = KeyAscii
If GetAsyncKeyState(VK_SHIFT) And KEY_MASK < 0 Then ShiftKey_On = True Else ShiftKey_On = False
If ShiftKey_On = True Then
If CapsLock_On = True Then aValue = aValue + 32 Else aValue = aValue - 32
End If
String1 = String1 & Chr(aValue)
Case Else: String1 = String1 & "[" & Chr(KeyAscii) & " - " & KeyAscii & "]"
End Select
Cells(1, 1) = String1
End Function
I am pretty new to the WinApi calls although familiar with VBA. What I am trying to do is to hook to a child class SysTreeView32 of VBE window (Project Explorer TreeView). I would like to expand/collapse the tree view elements by modifying the registry keys (or alternatively sending the mouse clicks (mouse_event) although I prefer the first option).
I can find the Excel Main Window by using this code:
Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Sub Find_Window()
Dim hWndExcel As Long
hWndExcel = FindWindow("XLMAIN", Application.Caption)
MsgBox hWndExcel
End Sub
With the help of Window Detective I can access the names, properties, etc. of the child classes.
But I cannot work it out how to access/activate(or even return the HWID of) the SysTreeView32 child class to collapse/expand elements(folders). I am not sure how to iterate over the elements yet, but I will research this afterwards. The problem here is accessing the SysTreeView32 class. How can I achieve it?
When I try to msgbox this
FindWindow("wndclass_desked_gsk", Application.Caption)
or
FindWindow("SysTreeView32", Application.Caption)
a 0 is returned so I am obviously doing something wrong :/
Thank you for your time.
you ought to be using:
application.vbe.mainwindow.caption
here's some sample collapse code
Private Const TVE_COLLAPSE = &H1
Private Const TVE_COLLAPSERESET = &H8000
Private Const TVE_EXPAND = &H2
Private Const TVE_EXPANDPARTIAL = &H4000
Private Const TVE_TOGGLE = &H3
Private Const TV_FIRST = &H1100
Private Const TVM_EXPAND = (TV_FIRST + 2)
Private Const TVM_GETNEXTITEM = (TV_FIRST + 10)
Private Const TVGN_ROOT = &H0
Private Const TVGN_NEXTVISIBLE = &H6
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
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Sub CollapseProjects()
Dim hWndVBE As Long, hWndPE As Long, hWndTvw As Long, hNode As Long, varReturn
hWndVBE = FindWindowEx(0, 0, "wndclass_desked_gsk", Application.VBE.MainWindow.Caption)
hWndPE = FindWindowEx(hWndVBE, 0, "PROJECT", vbNullString)
hWndTvw = FindWindowEx(hWndPE, 0, "SysTreeView32", vbNullString)
hNode = SendMessage(hWndTvw, TVM_GETNEXTITEM, TVGN_ROOT, 0&)
Do While hNode <> 0
varReturn = SendMessage(hWndTvw, TVM_EXPAND, TVE_COLLAPSE, hNode)
hNode = SendMessage(hWndTvw, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, hNode)
Loop
End Sub
further to your comment, here's the code to collapse only the 'Microsoft Excel Objects' nodes
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
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Public Declare Function SendMessageB Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
Private Const MAX_ITEM As Long = 256
Private Const TV_FIRST As Long = &H1100
Private Const TVM_EXPAND As Long = (TV_FIRST + 2)
Private Const TVM_GETNEXTITEM As Long = (TV_FIRST + 10)
Private Const TVM_GETITEM As Long = (TV_FIRST + 12)
Const TVE_COLLAPSE As Long = &H1
Const TVE_EXPAND As Long = &H2
Private Const TVGN_ROOT As Long = &H0
Private Const TVGN_NEXT As Long = &H1
Private Const TVIF_TEXT As Long = &H1
Private Const TVGN_NEXTVISIBLE = &H6
Private Type TVITEM ' was TV_ITEM
mask As Long
hItem As Long
state As Long
stateMask As Long
pszText As String
cchTextMax As Long
iImage As Long
iSelectedImage As Long
cChildren As Long
lParam As Long
End Type
Sub CollapseXLObjects()
Dim hWndVBE As Long
Dim hWndPE As Long
Dim hWndTvw As Long
Dim hNode As Long
Dim tvi As TVITEM
Dim nChild As Long
Dim sText As String
Dim varReturn
hWndVBE = FindWindowEx(0, 0, "wndclass_desked_gsk", Application.VBE.MainWindow.Caption)
hWndPE = FindWindowEx(hWndVBE, 0, "PROJECT", vbNullString)
hWndTvw = FindWindowEx(hWndPE, 0, "SysTreeView32", vbNullString)
hNode = SendMessage(hWndTvw, TVM_GETNEXTITEM, TVGN_ROOT, 0&)
Do While hNode <> 0
tvi.hItem = hNode
tvi.mask = TVIF_TEXT
tvi.cchTextMax = MAX_ITEM
tvi.pszText = String(MAX_ITEM, 0)
nChild = SendMessageB(hWndTvw, TVM_GETITEM, 0&, tvi)
If InStr(1, tvi.pszText, "Microsoft Excel Objects", vbTextCompare) > 0 Then
varReturn = SendMessage(hWndTvw, TVM_EXPAND, TVE_COLLAPSE, hNode)
Else
varReturn = SendMessage(hWndTvw, TVM_EXPAND, TVE_EXPAND, hNode)
End If
hNode = SendMessage(hWndTvw, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, hNode)
Loop
End Sub
If calling this sub from another sub (in my case a sub in an XL addin linked to a custom button which opens the VBE), and another module is highlighted (which might be in another project) then I've found the
Application.VBE.MainWindow.Caption
doesn't work. To capture the highlighted module I use:
Private Sub VisualBasicEditor()
On Error Resume Next
WinName = "Microsoft Visual Basic - " + ActiveWorkbook.Name + " [Running] - [" + Application.VBE.ActiveCodePane.CodeModule.Name + " (Code)]"
Application.VBE.MainWindow.Visible = True
Call CollapseXLObjects
End Sub
and in the module containing Sub CollapseXLObjects declare
Public WinName As String
and in Sub CollapseXLObjects
hWndVBE = FindWindowEx(0, 0, "wndclass_desked_gsk", WinName)