How to make mouse hook in VBA to/from other Excel instance? - vba

I need Mouse hook in userform:
Private Sub UserForm_Initialize()
SetHookInOtherInstance
End Sub
Private Sub UserForm_Terminate()
RemoveHookInOtherInstance
End Sub
and it work fine in the same instance, but I want to use another to prevent Excel from crashing while debugging etc (that's the best idea from this question ) and just nothing happens.
Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As LongPtr) As LongPtr
Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As LongPtr, ByVal lpFn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As LongPtr) As LongPtr
Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal nCode As LongPtr, ByVal wParam As LongPtr, lParam As Any) As LongPtr
Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As LongPtr
Private i As Long
Private HookHandle As LongPtr
Private ExcelOtherInstance As Excel.Application
Private Const WH_MOUSE = 7
Private Const HC_ACTION = 0
Public Sub SetHookInOtherInstance()
Set ExcelOtherInstance = New Excel.Application
ExcelOtherInstance.Visible = True
ExcelOtherInstance.Workbooks.Add
ExcelOtherInstance.Workbooks.Open ThisWorkbook.FullName, False, True
ExcelOtherInstance.Run "SetHook", GetCurrentThreadId
End Sub
Public Sub RemoveHookInOtherInstance()
On Error Resume Next
ExcelOtherInstance.Run "RemoveHook"
End Sub
Public Sub RemoveHook()
On Error Resume Next
UnhookWindowsHookEx HookHandle
End Sub
Public Sub SetHook(ThreadId As LongPtr)
HookHandle = SetWindowsHookEx(WH_MOUSE, AddressOf NewProc, 0, ThreadId)
End Sub
Public Function NewProc(ByVal lngCode As LongPtr, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
If lngCode = HC_ACTION Then
Application.StatusBar = CStr(i)
i = i + 1
End If
NewProc = CallNextHookEx(HookHandle, lngCode, wParam, lParam)
Exit Function
End Function
Any ideas?

Related

Listen to Windows messages in control, created with WinAPI, in VBA

I had a great help with understanding how to correctly create controls (particularly, ListBox) in VBA with WinAPI.
So, according to the structure, how VBA handles windows, we have three handles:
hWin - UserForm's handle
hClient - UserForm child's handle (Server)
hList - ListBox's handle
The question is - how to listen to Windows messages, incoming from Windows and generated by ListBox?
To listen to the messages, override the function that processes messages sent to a window, which is in this case hClient.
To listen to a change of selection in UserForm1:
Option Explicit
Private hWin As LongPtr
Private hClient As LongPtr
Private hList As LongPtr
Private Sub UserForm_Initialize()
' get the top window handle '
hWin = FindWindowEx(0, 0, StrPtr("ThunderDFrame"), StrPtr(Me.Caption))
If hWin Then Else Err.Raise 5, , "Top window not found"
' get first child / client window '
hClient = FindWindowEx(hWin, 0, 0, 0)
If hClient Then Else Err.Raise 5, , "Client window not found"
' create the list box '
hList = CreateWindowEx( _
dwExStyle:=WS_EX_CLIENTEDGE, _
lpClassName:=StrPtr("LISTBOX"), _
lpWindowName:=0, _
dwStyle:=WS_CHILD Or WS_VISIBLE Or WS_VSCROLL Or WS_SIZEBOX Or LBS_NOTIFY Or LBS_HASSTRINGS, _
x:=10, _
y:=10, _
nWidth:=100, _
nHeight:=100, _
hwndParent:=hClient, _
hMenu:=0, _
hInstance:=0, _
lpParam:=0)
' add some values '
SendMessage hList, LB_ADDSTRING, 0, StrPtr("item a")
SendMessage hList, LB_ADDSTRING, 0, StrPtr("item b")
SendMessage hList, LB_ADDSTRING, 0, StrPtr("item c")
' intercept messages '
UserForm1_Register Me, hClient
End Sub
Public Sub WndProc(ByVal hwnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr)
Select Case uMsg
Case WM_COMMAND
Select Case (wParam \ 65536) And 65535 ' HIWORD '
Case LBN_SELCHANGE
Debug.Print "Selection changed"
End Select
End Select
End Sub
and in a module:
Option Explicit
Public Declare PtrSafe Function FindWindowEx Lib "user32.dll" Alias "FindWindowExW" ( _
ByVal hwndParent As LongPtr, _
ByVal hwndChildAfter As LongPtr, _
ByVal lpszClass As LongPtr, _
ByVal lpszWindow As LongPtr) As LongPtr
Public Declare PtrSafe Function CreateWindowEx Lib "user32.dll" Alias "CreateWindowExW" ( _
ByVal dwExStyle As Long, _
ByVal lpClassName As LongPtr, _
ByVal lpWindowName As LongPtr, _
ByVal dwStyle As Long, _
ByVal x As Long, _
ByVal y As Long, _
ByVal nWidth As Long, _
ByVal nHeight As Long, _
ByVal hwndParent As LongPtr, _
ByVal hMenu As LongPtr, _
ByVal hInstance As LongPtr, _
ByVal lpParam As LongPtr) As LongPtr
Public Declare PtrSafe Function SendMessage Lib "user32.dll" Alias "SendMessageW" ( _
ByVal hwnd As LongPtr, _
ByVal wMsg As Long, _
ByVal wParam As LongPtr, _
ByVal lParam As LongPtr) As LongPtr
Private Declare PtrSafe Function CallWindowProc Lib "user32.dll" Alias "CallWindowProcW" ( _
ByVal lpPrevWndFunc As LongPtr, _
ByVal hwnd As LongPtr, _
ByVal Msg As Long, _
ByVal wParam As LongPtr, _
ByVal lParam As LongPtr) As LongPtr
#If Win64 Then
Private Declare PtrSafe Function SetWindowLong Lib "user32.dll" Alias "SetWindowLongPtrW" ( _
ByVal hwnd As LongPtr, _
ByVal nIndex As Long, _
ByVal dwNewLong As LongPtr) As Long
#Else
Private Declare PtrSafe Function SetWindowLong Lib "user32.dll" Alias "SetWindowLongW" ( _
ByVal hwnd As LongPtr, _
ByVal nIndex As Long, _
ByVal dwNewLong As LongPtr) As Long
#End If
Public Const WS_EX_CLIENTEDGE = &H200&
Public Const WS_CHILD = &H40000000
Public Const WS_VISIBLE = &H10000000
Public Const WS_VSCROLL = &H200000
Public Const WS_SIZEBOX = &H40000
Public Const LBS_NOTIFY = &H1&
Public Const LBS_HASSTRINGS = &H40&
Public Const LB_ADDSTRING = &H180&
Public Const GW_CHILD = &O5&
Public Const GWL_WNDPROC As Long = -4
Public Const WM_COMMAND = &H111&
Public Const LBN_SELCHANGE = 1
Private UserForm1_Form As UserForm1
Private UserForm1_Func As LongPtr
Public Sub UserForm1_Register(form As UserForm1, ByVal hwnd As LongPtr)
Set UserForm1_Form = form
UserForm1_Func = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf UserForm1_WinProc)
If UserForm1_Func = 0 Then Err.Raise 1, , "Failed to register"
End Sub
Private Function UserForm1_WinProc(ByVal hwnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
UserForm1_Form.WndProc hwnd, uMsg, wParam, lParam
UserForm1_WinProc = CallWindowProc(UserForm1_Func, hwnd, uMsg, wParam, lParam)
End Function

How to bring external application window on top? [duplicate]

This question already has an answer here:
Check process is running, then switch to it?
(1 answer)
Closed 5 years ago.
I have Outlook express always on top and Google chrome behind Outlook. How to bring running Google chrome on top of OutLook express using visual basic?
Following opens a new application but i want existing Google chrome to bring on top?
Shell("C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", AppWinStyle.MaximizedFocus)
EDIT:
Public Class Form1
Declare Auto Function FindWindow Lib "User32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
Declare Auto Function SetForegroundWindow Lib "User32.dll" (ByVal Hwnd As IntPtr) As Long
'Private Declare Function SetForegroundWindow Lib "user32.dll" (ByVal hwnd As Int32) As Int32
Declare Auto Function FindWindowEx Lib "User32.dll" (ByVal hwndParent As IntPtr, ByVal hwndChildAfter As IntPtr, ByVal lpszClass As String, ByVal lpszWindow As String) As IntPtr
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Shell("C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", AppWinStyle.MaximizedFocus)
Dim Handle As IntPtr = FindWindow("Notepad", Nothing)
If Handle.Equals(IntPtr.Zero) Then
End
End If
'Dim HandleChildOne As IntPtr = FindWindowEx(Handle, IntPtr.Zero, "Notepad", IntPtr.Zero)
'If HandleChildOne.Equals(IntPtr.Zero) Then
'End
'End If
Dim Result As Integer = SetForegroundWindow(Handle)
If Result.Equals(0) Then
End
Else
MsgBox("Above 0: success. https://msdn.microsoft.com/en-us/library/windows/desktop/ms633539(v=vs.85).aspx " & Result)
End If
End Sub
Private Sub Label1_Click(sender As Object, e As EventArgs) Handles Label1.Click
End
End Sub
End Class
Method 1 of #Codexer works (Method 2, 3 also included for research later). Note that, Chrome window position/size get unexpectedly modified while applying ShowWindow(Handle, 9)
Public Class Form1
Declare Auto Function FindWindow Lib "User32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
Declare Auto Function SetForegroundWindow Lib "User32.dll" (ByVal Hwnd As IntPtr) As Long
Declare Auto Function FindWindowEx Lib "User32.dll" (ByVal hwndParent As IntPtr, ByVal hwndChildAfter As IntPtr, ByVal lpszClass As String, ByVal lpszWindow As String) As IntPtr
Declare Auto Function SetWindowPos Lib "User32.dll" (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)
Const HWND_TOPMOST = -1
Const HWND_NOTOPMOST = -2
Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Const SWP_NOACTIVATE = &H10
Const SWP_SHOWWINDOW = &H40
Declare Auto Function ShowWindow Lib "User32.dll" (handle As IntPtr, nCmdShow As Integer) As Boolean
Declare Auto Function IsIconic Lib "User32.dll" (handle As IntPtr) As Boolean
' Method 1
Private Sub StartOrShowProcess(ByVal strProcessName As String)
Try
Dim handle As IntPtr
Dim proc As Process() = Process.GetProcessesByName(strProcessName)
If proc.Count > 0 Then
For Each procP As Process In proc
handle = procP.MainWindowHandle
' Do we have handle and minimized or not minimized?
If handle <> 0 Then
ShowWindow(handle, 9)
SetForegroundWindow(handle)
End If
Next
Else 'Not running or started...
Process.Start(strProcessName)
End If
Catch ex As Exception
'Handle your error...
End Try
End Sub
' Method 2/3
Private Sub Old()
'=== Method 1: Target chrome > as new window
'Shell("C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", AppWinStyle.MaximizedFocus)
'=== Method 2: Target chrome > Target specific TAB
Dim Handle As IntPtr = FindWindow(Nothing, "Nieuw tabblad - Google Chrome")
If Handle.Equals(IntPtr.Zero) Then
Handle = FindWindow(Nothing, "TITLE... - Google Chrome")
If Handle.Equals(IntPtr.Zero) Then
End
End If
End If
' !!!ShowWindow!!!! help to detect from minmize state
ShowWindow(Handle, 9)
Dim Result As Integer = SetForegroundWindow(Handle)
If Result.Equals(0) Then
End
End If
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.TopMost = True
StartOrShowProcess("chrome")
End Sub
Private Sub Label1_Click(sender As Object, e As EventArgs) Handles Label1.Click
End
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
End
End Sub
End Class

Expression cannot be converted to long error

im getting the following error, and have no idea how to solve this:
BC30581: Adressoff Expression cannot be converted to Long because Long is not a delegate type.
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 TimerID As Long
Public TimerSeconds As Single
Sub StartTimer()
TimerSeconds = 1000 ' how often to "pop" the timer.
TimerID = SetTimer(0&, 0&, TimerSeconds * 1000&, AddressOf TimerProc)
End Sub
Sub EndTimer()
On Error Resume Next
KillTimer(0&, TimerID)
End Sub
Sub TimerProc(ByVal HWnd As Long, ByVal uMsg As Long,
ByVal nIDEvent As Long, ByVal dwTimer As Long)
MsgBox("test123")
End Sub
It appears that your PInvoke signature is wrong. Try this one instead:
Public Delegate Sub TimerProc(ByVal hWnd As IntPtr, ByVal uMsg As UInteger, ByVal nIDEvent As IntPtr, ByVal dwTime As UInteger)
<DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function SetTimer(ByVal hWnd As IntPtr, ByVal nIDEvent As IntPtr, ByVal uElapse As UInteger, ByVal lpTimerFunc As TimerProc) As IntPtr
End Function
You will need to change your pinvoke signature for KillTimer as well. See pinvoke.net for more information.
I got it to work with this:
Imports System.Runtime.InteropServices
Public Class TimerMethods
<DllImport("user32.dll", SetLastError:=True)>
Public Shared Function SetTimer(ByVal hWnd As IntPtr, ByVal nIDEvent As IntPtr, ByVal uElapse As UInteger, ByVal lpTimerFunc As TimerProc) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True)>
Public Shared Function KillTimer(ByVal hWnd As IntPtr, ByVal nIDEvent As IntPtr) As Boolean
End Function
Public Delegate Sub TimerProc(ByVal hWnd As IntPtr, ByVal uMsg As UInteger, ByVal nIDEvent As IntPtr, ByVal dwTime As UInteger)
Public timerID As IntPtr
Sub StartTimer(windowHandle As IntPtr)
Dim timerSeconds = 3 ' how often to "pop" the timer.
timerID = SetTimer(windowHandle, IntPtr.Zero, CUInt(timerSeconds * 1000), AddressOf TimerMethods.TimerCallback)
If timerID = IntPtr.Zero Then
Debug.WriteLine("Timer start error.")
Else
Debug.WriteLine("Timer started.")
End If
End Sub
Sub EndTimer()
KillTimer(IntPtr.Zero, timerID)
End Sub
Public Shared Sub TimerCallback(ByVal hWnd As IntPtr, ByVal uMsg As UInteger, ByVal nIDEvent As IntPtr, ByVal dwTime As UInteger)
MsgBox(uMsg)
End Sub
End Class
and a simple form with just a button on it to start the timer:
Public Class Form1
Dim t As TimerMethods
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
t = New TimerMethods
t.StartTimer(Me.Handle)
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
If t IsNot Nothing Then
t.EndTimer()
End If
End Sub
End Class
You don't have to use windowHandle to tie the timer to the form: you could use IntPtr.Zero instead.
Any time you see a variable name like hWnd, it wants a handle, which is an IntPtr in VB.NET.
Reference: pinvoke.net/default.aspx/user32.SetTimer
There seems little point in using PInvoke to do this. There is a Windows Forms Timer that you can use easily:
Public Class Form3
Private _timer As Timer
Public Sub StartTimer()
_timer = New Timer()
_timer.Interval = 1000 ' timer interval in ms
AddHandler _timer.Tick, AddressOf TimerProc
_timer.Enabled = True
End Sub
Public Sub EndTimer()
_timer.Enabled = False
RemoveHandler _timer.Tick, AddressOf TimerProc
End Sub
Sub TimerProc(sender As Object, e As EventArgs)
MsgBox("test123")
End Sub
End Class
I have tested the code in VBE in Excel 2013 and run successfully.
I state beforehand that this solution is for VBA environment
since that was a possible one at the stage I wrote it
I made it run with some edits:
added the continuation escape sequence "_" at the end of each but the last line of SetTimer() and KillTimer () function declaration
in EndTimer() assigned KillTimer to a Long variable, which I also declared
in TimerProc() added a call to EndTimer() to stop it !!!
here follows the working code for me:
Option Explicit
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 TimerID As Long
Public TimerSeconds As Single
Sub StartTimer()
TimerSeconds = 1000 ' how often to "pop" the timer.
TimerID = SetTimer(0&, 0&, TimerSeconds * 1000&, AddressOf TimerProc)
End Sub
Sub EndTimer()
Dim i As Long '<--| added
On Error Resume Next
i = KillTimer(0&, TimerID) '<--| added
End Sub
Sub TimerProc(ByVal HWnd As Long, ByVal uMsg As Long, _
ByVal nIDEvent As Long, ByVal dwTimer As Long)
MsgBox ("test123")
EndTimer '<--| added !!!
End Sub

Excel Useform: How to hide application but have icon in the taskbar

What I want to have is Application.Visible = False, so that my users cannot see the excel/worksheets, only the userform.
I have got this to work by using this code:
Private Sub Workbook_Open()
Application.Visible = False
UserForm2.Show
End Sub
However, this only has the userform floating around in the background. My users will have other applications open, and I want them to easily change to the userform by having an icon visible on the taskbar.
I have found the following example online, but I cannot seem to find where to place this code. Still very new to this, so hopefully I have the right code for the job. If I do, can someone talk me through where to place it, as it is not working when I paste it into my code?
(i.e. should it go under 'userform' or 'this workbook: declarations' etc. )
Thank you,
Option Explicit
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function ShowWindow Lib "user32" (ByVal hWnd As Long, ByVal nCmdShow As Long) As Long
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 SetFocus Lib "user32" (ByVal hWnd As Long) As Long
Private Const GWL_STYLE As Long = -16
Private Const GWL_EXSTYLE As Long = -20
Private Const WS_CAPTION As Long = &HC00000
Private Const WS_MINIMIZEBOX As Long = &H20000
Private Const WS_MAXIMIZEBOX As Long = &H10000
Private Const WS_POPUP As Long = &H80000000
Private Const WS_VISIBLE As Long = &H10000000
Private Const WS_EX_DLGMODALFRAME As Long = &H1
Private Const WS_EX_APPWINDOW As Long = &H40000
Private Const SW_SHOW As Long = 5
Private Sub UserForm_Activate()
Application.Visible = False
Application.VBE.MainWindow.Visible = False
Dim lngHwnd As Long
Dim lngCurrentStyle As Long, lngNewStyle As Long
If Val(Application.Version) < 9 Then
lngHwnd = FindWindow("ThunderXFrame", Me.Caption) 'XL97
Else
lngHwnd = FindWindow("ThunderDFrame", Me.Caption) 'XL2000, XP, 2003?
End If
'Set the Windows style so that the userform has a minimise and maximise button
lngCurrentStyle = GetWindowLong(lngHwnd, GWL_STYLE)
lngNewStyle = lngCurrentStyle Or WS_MINIMIZEBOX Or WS_MAXIMIZEBOX
lngNewStyle = lngNewStyle And Not WS_VISIBLE And Not WS_POPUP
SetWindowLong lngHwnd, GWL_STYLE, lngNewStyle
'Set the extended style to provide a taskbar icon
lngCurrentStyle = GetWindowLong(lngHwnd, GWL_EXSTYLE)
lngNewStyle = lngCurrentStyle Or WS_EX_APPWINDOW
SetWindowLong lngHwnd, GWL_EXSTYLE, lngNewStyle
ShowWindow lngHwnd, SW_SHOW
End Sub
Private Sub UserForm_Terminate()
Application.Visible = True
End Sub
Try placing this code in the userforms code module:
Option Explicit
'API functions
Private Declare Function GetWindowLong Lib "user32" _
Alias "GetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long) 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 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 Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function GetActiveWindow Lib "user32.dll" _
() As Long
Private Declare Function SendMessage Lib "user32" _
Alias "SendMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Declare Function DrawMenuBar Lib "user32" _
(ByVal hwnd As Long) As Long
'Constants
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOSIZE = &H1
Private Const GWL_EXSTYLE = (-20)
Private Const HWND_TOP = 0
Private Const SWP_NOACTIVATE = &H10
Private Const SWP_HIDEWINDOW = &H80
Private Const SWP_SHOWWINDOW = &H40
Private Const WS_EX_APPWINDOW = &H40000
Private Const GWL_STYLE = (-16)
Private Const WS_MINIMIZEBOX = &H20000
Private Const SWP_FRAMECHANGED = &H20
Private Const WM_SETICON = &H80
Private Const ICON_SMALL = 0&
Private Const ICON_BIG = 1&
Private Sub AppTasklist(myForm)
'Add this userform into the Task bar
Dim WStyle As Long
Dim Result As Long
Dim hwnd As Long
hwnd = FindWindow(vbNullString, myForm.Caption)
WStyle = GetWindowLong(hwnd, GWL_EXSTYLE)
WStyle = WStyle Or WS_EX_APPWINDOW
Result = SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, _
SWP_NOMOVE Or _
SWP_NOSIZE Or _
SWP_NOACTIVATE Or _
SWP_HIDEWINDOW)
Result = SetWindowLong(hwnd, GWL_EXSTYLE, WStyle)
Result = SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, _
SWP_NOMOVE Or _
SWP_NOSIZE Or _
SWP_NOACTIVATE Or _
SWP_SHOWWINDOW)
End Sub
Private Sub UserForm_Activate()
Application.Visible = False
Application.VBE.MainWindow.Visible = False
AppTaskList Me
End Sub
Private Sub UserForm_Terminate()
Application.Visible = True
End Sub
Disclaimer: This is not my code, and was found on a forum which I don't have the link for any longer.
So, as you may noticed this won't work on the 64 bit version of excel.
I made it compatible by adding conditionals to the code i took from here.
In case you're wondering how you can make API functions compatible with 64 bits versions of Excel here it's an excellent article that will get you through.
Option Explicit
'API functions
#If VBA7 Then
#If Win64 Then
Private Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongPtrA" _
(ByVal hWnd As LongPtr, _
ByVal nIndex As Long _
) As LongPtr
#Else
Private Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongA" _
(ByVal hWnd As LongPtr, _
ByVal nIndex As Long _
) As LongPtr
#End If
#If Win64 Then
Private Declare PtrSafe Function SetWindowLongPtr Lib "user32" Alias "SetWindowLongPtrA" _
(ByVal hWnd As LongPtr, _
ByVal nIndex As Long, _
ByVal dwNewLong As LongPtr _
) As LongPtr
#Else
Private Declare PtrSafe Function SetWindowLongPtr Lib "user32" Alias "SetWindowLongA" _
(ByVal hWnd As LongPtr, _
ByVal nIndex As Long, _
ByVal dwNewLong As LongPtr _
) As LongPtr
#End If
Private Declare PtrSafe Function SetWindowPos Lib "user32" _
(ByVal hWnd As LongPtr, _
ByVal hWndInsertAfter As LongPtr, _
ByVal X As Long, ByVal Y As Long, _
ByVal cx As Long, ByVal cy As Long, _
ByVal wFlags As Long _
) As LongPtr
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String _
) As LongPtr
Private Declare PtrSafe Function GetActiveWindow Lib "user32.dll" () As Long
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As LongPtr, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any _
) As LongPtr
Private Declare PtrSafe Function DrawMenuBar Lib "user32" _
(ByVal hWnd As LongPtr) As LongPtr
#Else
Private Declare Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongA" _
(ByVal hWnd As Long, _
ByVal nIndex As Long _
) As Long
Private Declare Function SetWindowLongPtr Lib "user32" Alias "SetWindowLongA" _
(ByVal hWnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong 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 Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String _
) As Long
Private Declare Function GetActiveWindow Lib "user32.dll" () As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any _
) As Long
Private Declare Function DrawMenuBar Lib "user32" _
(ByVal hWnd As Long) As Long
#End If
'Constants
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOSIZE = &H1
Private Const GWL_EXSTYLE = (-20)
Private Const HWND_TOP = 0
Private Const SWP_NOACTIVATE = &H10
Private Const SWP_HIDEWINDOW = &H80
Private Const SWP_SHOWWINDOW = &H40
Private Const WS_EX_APPWINDOW = &H40000
Private Const GWL_STYLE = (-16)
Private Const WS_MINIMIZEBOX = &H20000
Private Const SWP_FRAMECHANGED = &H20
Private Const WM_SETICON = &H80
Private Const ICON_SMALL = 0&
Private Const ICON_BIG = 1&
And then use the following subroutines:
Private Sub UserForm_Activate()
AddIcon 'Add an icon on the titlebar
AddMinimizeButton 'Add a Minimize button to Userform
AppTasklist Me 'Add this userform into the Task bar
End Sub
Private Sub AddIcon()
'Add an icon on the titlebar
Dim hWnd As Long
Dim lngRet As Long
Dim hIcon As Long
hIcon = Sheet1.Image1.Picture.Handle
hWnd = FindWindow(vbNullString, Me.Caption)
lngRet = SendMessage(hWnd, WM_SETICON, ICON_SMALL, ByVal hIcon)
lngRet = SendMessage(hWnd, WM_SETICON, ICON_BIG, ByVal hIcon)
lngRet = DrawMenuBar(hWnd)
End Sub
Private Sub AddMinimizeButton()
'Add a Minimize button to Userform
Dim hWnd As Long
hWnd = GetActiveWindow
Call SetWindowLongPtr(hWnd, GWL_STYLE, _
GetWindowLongPtr(hWnd, GWL_STYLE) Or _
WS_MINIMIZEBOX)
Call SetWindowPos(hWnd, 0, 0, 0, 0, 0, _
SWP_FRAMECHANGED Or _
SWP_NOMOVE Or _
SWP_NOSIZE)
End Sub
Private Sub AppTasklist(myForm)
'Add this userform into the Task bar
#If VBA7 Then
Dim WStyle As LongPtr
Dim Result As LongPtr
Dim hWnd As LongPtr
#Else
Dim WStyle As Long
Dim Result As Long
Dim hWnd As Long
#End If
hWnd = FindWindow(vbNullString, myForm.Caption)
WStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE)
WStyle = WStyle Or WS_EX_APPWINDOW
Result = SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, _
SWP_NOMOVE Or _
SWP_NOSIZE Or _
SWP_NOACTIVATE Or _
SWP_HIDEWINDOW)
Result = SetWindowLongPtr(hWnd, GWL_EXSTYLE, WStyle)
Result = SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, _
SWP_NOMOVE Or _
SWP_NOSIZE Or _
SWP_NOACTIVATE Or _
SWP_SHOWWINDOW)
End Sub
I haven't tested this yet on 32 bits versions of excel but it should work without problems.

PtrSafe no longer supported in Outlook 2007 - re-editing macro

I'm currently considering using a macro for Outlook 2007 and read somewhere that PtrSafe is no longer supported.
Any idea what I can replace it with?
Private Declare PtrSafe Function FindWindowA Lib "user32" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare PtrSafe 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 SWP_NOSIZE = &H1
Private Const SWP_NOMOVE = &H2
Private Const FLAGS As Long = SWP_NOMOVE Or SWP_NOSIZE
Private Const HWND_TOPMOST = -1
Private Sub Application_Reminder(ByVal Item As Object)
Dim ReminderWindowHWnd As Variant
On Error Resume Next
ReminderWindowHWnd = FindWindowA(vbNullString, "1 Reminder")
SetWindowPos ReminderWindowHWnd, HWND_TOPMOST, 0, 0, 0, 0, FLAGS
End Sub
I don't know where you read that PtrSafe is no longer supported, but that is incorrect.
But you should look at http://msdn.microsoft.com/en-us/library/ee691831%28v=office.14%29.aspx
I followed that link, suggested by Charles Williams, and created this code which solves the compilation error:
#If Win64 Then
Private Declare PtrSafe Function FindWindowA Lib "user32" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare PtrSafe 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
#Else
Private Declare Function FindWindowA Lib "user32" _
(ByVal lpClassName As String, ByVal lpWindowName As String) 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
#End If