Display Userform in TaskBar with custom Icon and Hide Excel (mimicking a standalone application)

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,806
Office Version
  1. 2016
Platform
  1. Windows
Hi dear excel enthusiasts,

The following little project makes your excel userform look like a standalone application by hiding the main excel window and leaving only the userform with a personalised icon added to it that shows on the TaskBar instead of the native excel icon ?

The code makes use of the Shell32.dll ITASKLIST3 Interface at runtime via low level calls to the DispCallFunc API.- (No third-party dll required, all code is self-contained)

This Shell Interface is the only way a button of your application can be added to the windows TaskBar in Windows 7 and onwards.... All resources that i have seen accross the internet rely on setting the WS_EX_APPWINDOW style flag of the userform window but this woked only in Windows XP and backwards., not anymore.



This is the Sub signature - all arguments are self-explanatory :
Code:
Sub FormToTaskBar _
    ( _
        ByVal Form As Object, _
        Optional ByVal AddIconFromPic As StdPicture, _
        Optional ByVal AddIconFromFile As String, _
        Optional ByVal FileIconIndex As Long = 0, _
        Optional ThumbnailTooltip As String, _
        Optional ByVal HideExcel As Boolean _
    )



Workbook Sample

UserFormInTaskbar_32_64.gif






1- API code in a Standard Module:
VBA Code:
Option Explicit

'Jaafar Tribak @ MrExcel.com on 07/02/2020.
'Display vba userform icon in taskbar.
'Makes use of the Shell32.dll ITASKLIST3 Interface in order to work in Windows7 and onwards.

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type


Private Type PROPERTYKEY
    fmtid As GUID
    pid As Long
End Type


#If VBA7 Then

    #If Win64 Then
        Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
        Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
    #Else
        Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
        Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As Long) As LongPtr
    #End If
  
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long
    Private Declare PtrSafe Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
    Private Declare PtrSafe Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
    Private Declare PtrSafe Function ExtractIconA Lib "Shell32.dll" (ByVal hInst As LongPtr, ByVal lpszExeFileName As String, ByVal nIconIndex As LongPtr) As Long
    Private Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As LongPtr) As Long
    Private Declare PtrSafe Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As Any) As Long
    Private Declare PtrSafe Function SHGetPropertyStoreForWindow Lib "Shell32.dll" (ByVal hwnd As LongPtr, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
    Private Declare PtrSafe Function ShowWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal nCmdShow As Long) As Long
    Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long
    Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
    Private Declare PtrSafe Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
    Private Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function DrawMenuBar Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function BringWindowToTop Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function SetForegroundWindow Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As LongPtr) As Long
    Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
  
#Else

    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 Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As Long, ByVal offsetinVft As Long, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As Long, ByRef retVAR As Variant) As Long
    Private Declare Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
    Private Declare Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As Long, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As Long) As Long
    Private Declare Function ExtractIconA Lib "Shell32.dll" (ByVal hInst As Long, ByVal lpszExeFileName As String, ByVal nIconIndex As Long) As Long
    Private Declare Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As Long) As Long
    Private Declare Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As Long, ByRef cGUID As Any) As Long
    Private Declare Function SHGetPropertyStoreForWindow Lib "Shell32.dll" (ByVal hwnd As Long, ByRef riid As GUID, ByRef ppv 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 ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
    Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
    Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
    Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
    Private Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function BringWindowToTop Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long

#End If




'___________________________________________________Public Routines____________________________________________________________________


Public Sub FormToTaskBar _
    ( _
        ByVal Form As Object, _
        Optional ByVal AddIconFromPic As StdPicture, _
        Optional ByVal AddIconFromFile As String, _
        Optional ByVal FileIconIndex As Long = 0, _
        Optional ThumbnailTooltip As String, _
        Optional ByVal HideExcel As Boolean _
    )



    Const VT_LPWSTR = 31
  
    #If Win64 Then
        Const vTblOffsetFac_32_64 = 2
        Dim hform As LongLong, hApp As LongLong, hVbe As LongLong, pPstore As LongLong, pTBarList As LongLong
        Dim PV(0 To 2) As LongLong
      
        PV(0) = VT_LPWSTR: PV(1) = StrPtr("Dummy")
    #Else
        Const vTblOffsetFac_32_64 = 1
        Dim hform As Long, hApp As Long, hVbe As Long, pPstore As Long, pTBarList As Long
        Dim PV(0 To 3) As Long
      
        PV(0) = VT_LPWSTR: PV(2) = StrPtr("Dummy")
    #End If


    Const IPropertyKey_SetValue = 24 * vTblOffsetFac_32_64
    Const IPropertyKey_Commit = 28 * vTblOffsetFac_32_64
    Const ITASKLIST3_HrInit = 12 * vTblOffsetFac_32_64
    Const ITASKLIST3_AddTab = 16 * vTblOffsetFac_32_64
    Const ITASKLIST3_DeleteTab = 20 * vTblOffsetFac_32_64
    Const ITASKLIST3_ActivateTab = 24 * vTblOffsetFac_32_64
    Const ITASKLIST3_SetThumbnailTooltip = 76 * vTblOffsetFac_32_64


    Const IID_PropertyStore = "{886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99}"
    Const IID_PropertyKey = "{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"
    Const CLSID_TASKLIST = "{56FDF344-FD6D-11D0-958A-006097C9A090}"
    Const IID_TASKLIST3 = "{EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF}"

    Const CLSCTX_INPROC_SERVER = &H1
    Const S_OK = 0
    Const CC_STDCALL = 4

    Const GWL_STYLE = (-16)
    Const WS_MINIMIZEBOX = &H20000
    Const GWL_HWNDPARENT = (-8)

    Dim tClsID As GUID, tIID As GUID, tPK As PROPERTYKEY

  
    Call WindowFromAccessibleObject(Form, hform)
    Call SetProp(Application.hwnd, "hForm", hform)
    Call SetWindowLong(hform, GWL_HWNDPARENT, 0)
    Call SetWindowLong(hform, GWL_STYLE, GetWindowLong(hform, GWL_STYLE) Or WS_MINIMIZEBOX)
    Call DrawMenuBar(hform)
  
    If Not AddIconFromPic Is Nothing Then
        Call AddIcon(Form, AddIconFromPic, , FileIconIndex)
    ElseIf Len(AddIconFromFile) Then
        Call AddIcon(Form, , AddIconFromFile, FileIconIndex)
    End If

    Call CLSIDFromString(StrPtr(IID_PropertyStore), tIID)
    If SHGetPropertyStoreForWindow(hform, tIID, pPstore) = S_OK Then
        Call CLSIDFromString(StrPtr(IID_PropertyKey), tPK)
        tPK.pid = 5 ':  PV(0) = VT_LPWSTR: PV(1) = StrPtr("Dummy")
        Call vtblCall(pPstore, IPropertyKey_SetValue, vbLong, CC_STDCALL, VarPtr(tPK), VarPtr(PV(0))) 'SetValue Method
         Call vtblCall(pPstore, IPropertyKey_Commit, vbLong, CC_STDCALL) ' Commit Method
        Call CLSIDFromString(StrPtr(CLSID_TASKLIST), tClsID)
        Call CLSIDFromString(StrPtr(IID_TASKLIST3), tIID)
        If CoCreateInstance(tClsID, 0, CLSCTX_INPROC_SERVER, tIID, pTBarList) = S_OK Then
            SetProp Application.hwnd, "pTBarList", pTBarList
            Call vtblCall(pTBarList, ITASKLIST3_HrInit, vbLong, CC_STDCALL) 'HrInit Method
            Call vtblCall(pTBarList, ITASKLIST3_DeleteTab, vbLong, CC_STDCALL, hform) 'DeleteTab Method
            Call vtblCall(pTBarList, ITASKLIST3_AddTab, vbLong, CC_STDCALL, hform) 'AddTab Method
            Call vtblCall(pTBarList, ITASKLIST3_ActivateTab, vbLong, CC_STDCALL, hform) 'ActivateTab Method
            If Len(ThumbnailTooltip) Then
                Call vtblCall(pTBarList, ITASKLIST3_SetThumbnailTooltip, vbLong, CC_STDCALL, hform, StrPtr(ThumbnailTooltip)) 'ActivateTab Method
            End If
            If HideExcel Then
                Application.Visible = False
                hApp = Application.hwnd
                Call SetProp(Application.hwnd, "hApp", hApp)
                Call vtblCall(pTBarList, ITASKLIST3_DeleteTab, vbLong, CC_STDCALL, hApp) 'DeleteTab Method
                hVbe = FindWindow("wndclass_desked_gsk", vbNullString)
                If IsWindowVisible(hVbe) Then
                    Call SetProp(Application.hwnd, "hVbe", hVbe)
                    Call ShowWindow(hVbe, 0)
                    Call vtblCall(pTBarList, ITASKLIST3_DeleteTab, vbLong, CC_STDCALL, hVbe) 'DeleteTab Method
                End If
            End If
        End If
    End If
    Call SetForegroundWindow(hform): Call BringWindowToTop(hform)
  
End Sub


Public Sub ResetTaskbar(Optional ByVal Dummy As Boolean)


    #If Win64 Then
        Const vTblOffsetFac_32_64 = 2
        Dim pTBarList As LongPtr, hform As LongPtr, hApp As LongPtr, hVbe As LongPtr
  
    #Else
        Const vTblOffsetFac_32_64 = 1
        Dim pTBarList As Long, hform As Long, hApp As Long, hVbe As Long
  
    #End If


    Const ITASKLIST3_AddTab = 16 * vTblOffsetFac_32_64
    Const ITASKLIST3_DeleteTab = 20 * vTblOffsetFac_32_64
    Const CC_STDCALL = 4
  
    Dim i As Long

    pTBarList = GetProp(Application.hwnd, "pTBarList")
    hform = GetProp(Application.hwnd, "hForm")
    hApp = GetProp(Application.hwnd, "hApp")
    hVbe = GetProp(Application.hwnd, "hVbe")

    Call vtblCall(pTBarList, ITASKLIST3_DeleteTab, vbLong, CC_STDCALL, hform) 'DeleteTab Method
    For i = 1 To 2
        Call vtblCall(pTBarList, ITASKLIST3_AddTab, vbLong, CC_STDCALL, Choose(i, hApp, hVbe))  'AddTab Method
    Next i
  
    Application.Visible = True


End Sub




'___________________________________________________Private Routines____________________________________________________________________



Private Sub AddIcon(ByVal Form As Object, Optional AddIconFromPic As StdPicture, Optional ByVal AddIconFromFile As String, Optional ByVal Index As Long = 0)
  
    #If Win64 Then
        Dim hwnd As LongPtr, hIcon As LongPtr
    #Else
        Dim hwnd As Long, hIcon As Long
    #End If
  
    Const WM_SETICON = &H80
    Const ICON_SMALL = 0
    Const ICON_BIG = 1

    Dim N As Long, S As String

    Call WindowFromAccessibleObject(Form, hwnd)
    If Not AddIconFromPic Is Nothing Then
        hIcon = Form.Image1.Picture.Handle
        Call SendMessage(hwnd, WM_SETICON, ICON_SMALL, ByVal hIcon)
        Call SendMessage(hwnd, WM_SETICON, ICON_BIG, ByVal hIcon)
    ElseIf Len(AddIconFromFile) Then
        If Dir(AddIconFromFile, vbNormal) = vbNullString Then
            Exit Sub
        End If
        N = InStrRev(AddIconFromFile, ".")
        S = LCase(Mid(AddIconFromFile, N + 1))
        Select Case S
            Case "exe", "ico", "dll"
            Case Else
                Err.Raise 5
        End Select
        If hwnd = 0 Then
            Exit Sub
        End If
        hIcon = ExtractIconA(0, AddIconFromFile, Index)
        If hIcon <> 0 Then
            Call SendMessage(hwnd, WM_SETICON, ICON_SMALL, ByVal hIcon)
        End If
    End If
    Call DrawMenuBar(hwnd)
    DeleteObject hIcon
  
End Sub


#If Win64 Then
    Private Function vtblCall(ByVal InterfacePointer As LongLong, ByVal VTableOffset As Long, ByVal FunctionReturnType As Long, ByVal CallConvention As Long, ParamArray FunctionParameters() As Variant) As Variant

    Dim vParamPtr() As LongLong
#Else
    Private Function vtblCall(ByVal InterfacePointer As Long, ByVal VTableOffset As Long, ByVal FunctionReturnType As Long, ByVal CallConvention As Long, ParamArray FunctionParameters() As Variant) As Variant

    Dim vParamPtr() As Long
#End If

    If InterfacePointer = 0& Or VTableOffset < 0& Then Exit Function
    If Not (FunctionReturnType And &HFFFF0000) = 0& Then Exit Function

    Dim pIndex As Long, pCount As Long
    Dim vParamType() As Integer
    Dim vRtn As Variant, vParams() As Variant

    vParams() = FunctionParameters()
    pCount = Abs(UBound(vParams) - LBound(vParams) + 1&)
    If pCount = 0& Then
        ReDim vParamPtr(0 To 0)
        ReDim vParamType(0 To 0)
    Else
        ReDim vParamPtr(0 To pCount - 1&)
        ReDim vParamType(0 To pCount - 1&)
        For pIndex = 0& To pCount - 1&
            vParamPtr(pIndex) = VarPtr(vParams(pIndex))
            vParamType(pIndex) = VarType(vParams(pIndex))
        Next
    End If

    pIndex = DispCallFunc(InterfacePointer, VTableOffset, CallConvention, FunctionReturnType, pCount, _
    vParamType(0), vParamPtr(0), vRtn)
    If pIndex = 0& Then
        vtblCall = vRtn
    Else
        SetLastError pIndex
    End If

End Function



2- Code Usage in the UserForm Module:
VBA Code:
Option Explicit


Private Sub UserForm_Initialize()

    Call FormToTaskBar _
        ( _
            Form:=Me, _
            AddIconFromFile:="C:\Users\Info-Hp\Downloads\canada.ico", _
            ThumbnailTooltip:="This is a UserForm Taskbar Tooltip.", _
            HideExcel:=True _
        )


End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)

    Call ResetTaskbar
  
End Sub
 
If the form's icon doesn't disappear from the Windows Taskbar, your MAIN_USERFORM object variabele might be "Nothing" and if your form isn't closing at all you have to search for another cause, perhaps the queryclose event handler of the form isn't setup in the right manner.

Invocation from within (!!) your form like
VBA Code:
Call RemoveFromTaskbar(Me)
rather than from another module is recommended.
 
Upvote 0

Excel Facts

Remove leading & trailing spaces
Save as CSV to remove all leading and trailing spaces. It is faster than using TRIM().
You really got me stumped with this .

I had tested the code on different installs ( Office 2007- 2010 "32-64 Bits" - 2016 "32-64 Bits" and Windows 7 and 10 both 32 and 64 bits.) and it worked as expected in all of them.... No idea why it doesn't work for you as the code seems correct .... Maybe it is due to excel 365.

Regards.
Hi Jaafar! Thanks for your work on this code. I am trying to use it however when I run it it displays the userform but the screen is behind it any ideas? thanks!
 

Attachments

  • Capture.PNG
    Capture.PNG
    5.2 KB · Views: 50
Upvote 0
Brilliant job. I've been looking for ages for something like this.
Although the thread being somewhat old, I registered just to say thanx you very muchest. Works like a charm.
W10, MSO2016
 
Upvote 0
I am without doubt a "dabbler" in excel so my following question must be considered accordingly.
The writer of this code I wholeheartedly admire for the continuing assistance he gives to help all who post questions and hopefully mine too. His beginning statement
"The following little project makes your excel userform look like a standalone application by hiding the main excel window and leaving only the userform with a personalised icon added to it that shows on the TaskBar instead of the native excel icon ?"
Many users have had queries and many have said it works perfectly for them.

My question then is this. Should I be left after selecting 'Show User Form' the sun icon on the taskbar and the blank 'UserForm1' showing on the desktop as my screenshot shows.
Everything seems to work ok i.e. excel sheet 'disappears'. Taskbar icon can toggle the blank Userform1 non visible/visible. X on UserForm1 restores Excel sheet.

My personal preference for my project would be for the UserForm1 to be non visible to be toggled visible/non visible by the task bar icon.
So is the code working properly now and is it possible to achieve my personal preference.

Lastly as a newbie I hope I have posted this in the right place.





Screenshot (9)1.png
 
Upvote 0
@gilaxg10

Welcome to the forum and thanks for the feedback

My personal preference for my project would be for the UserForm1 to be non visible to be toggled visible/non visible by the task bar icon
I am not entirely sure I understand what you exactly mean but, yes, I did notice that sometimes when clicking on the form thumbnail on the taskbar, the userform is not properly toggled between minimized and maximized. If that's what you mean then below is an update that fixes the issue.
Again, thanks for bringing this to my attention.

Note that this only applies when you have a single userform.

UPDATE:
V2-MultipleFormsToTaskBar.xlsm







Updated API code:
VBA Code:
Option Explicit

'Jaafar Tribak @ MrExcel.com on 12/03/2021.
'Display vba userform icon in taskbar.
'Makes use of the Shell32.dll ITASKLIST3 Interface in order to work in Windows7 and onwards.
'UPDATE on 22/11/2022

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

Private Type PROPERTYKEY
    fmtid As GUID
    pid As Long
End Type

#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
        Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
    #Else
        Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
        Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As Long) As LongPtr
    #End If
    Private Declare PtrSafe Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long
    Private Declare PtrSafe Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
    Private Declare PtrSafe Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
    Private Declare PtrSafe Function ExtractIconA Lib "shell32.dll" (ByVal hInst As LongPtr, ByVal lpszExeFileName As String, ByVal nIconIndex As LongPtr) As Long
    Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As Any) As Long
    Private Declare PtrSafe Function SHGetPropertyStoreForWindow Lib "shell32.dll" (ByVal hwnd As LongPtr, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
    Private Declare PtrSafe Function DrawMenuBar Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As LongPtr) As Long
    Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
    Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
    Private Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr
    Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
    Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
    Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
    Private Declare PtrSafe Function ShowWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal nCmdShow As Long) As Long
    Private Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hwnd As LongPtr) As Long
 
    Private pTBarList As LongPtr
#Else
    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 DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As Long, ByVal offsetinVft As Long, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As Long, ByRef retVAR As Variant) As Long
    Private Declare Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
    Private Declare Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As Long, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As Long) As Long
    Private Declare Function ExtractIconA Lib "Shell32.dll" (ByVal hInst As Long, ByVal lpszExeFileName As String, ByVal nIconIndex As Long) As Long
    Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hwnd As Long) As Long
    Private Declare Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As Long, ByRef cGUID As Any) As Long
    Private Declare Function SHGetPropertyStoreForWindow Lib "Shell32.dll" (ByVal hwnd As Long, ByRef riid As GUID, ByRef ppv As Long) As Long
    Private Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
    Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
    Private Declare Function GetForegroundWindow Lib "user32" () As Long
    Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
    Private Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
    Private Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent 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 ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
    Private Declare Function IsWindowVisible Lib "user32" (ByVal Hwnd As Long) As Long
 
    Private pTBarList As Long
#End If

Private oFormsCollection As Collection



'_______________________________Public Routines______________________________


Public Sub AddToTaskBar _
    ( _
        ByVal Form As Object, _
        Optional ByVal IconFromPic As StdPicture, _
        Optional ByVal IconFromFile As String, _
        Optional ByVal FileIconIndex As Long = 0, _
        Optional ThumbnailTooltip As String, _
        Optional HideExcelApplication As Boolean _
    )
 
    Const GWL_STYLE = (-16)
    Const WS_MINIMIZEBOX = &H20000
    Const GWL_HWNDPARENT = (-8)

    #If Win64 Then
        Dim hwnd As LongLong
    #Else
        Dim hwnd As Long
    #End If
 
   If HideExcelApplication Then
        Application.Visible = False
        Call ShowWindow(FindWindow("wndclass_desked_gsk", vbNullString), 0)
   End If
 
    Call IUnknown_GetWindow(Form, VarPtr(hwnd))
    Call SetWindowLong(hwnd, GWL_HWNDPARENT, 0)
    Call SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) Or WS_MINIMIZEBOX)
    Call DrawMenuBar(hwnd)
    If Not IconFromPic Is Nothing Then
        Call addicon(Form, IconFromPic, , FileIconIndex)
    ElseIf Len(IconFromFile) Then
        Call addicon(Form, , IconFromFile, FileIconIndex)
    End If
    If oFormsCollection Is Nothing Then
        Set oFormsCollection = New Collection
        Call CreateTBarInstance
        Call KillTimer(Application.hwnd, 0)
        Call SetTimer(Application.hwnd, 0, 0, AddressOf TimerProc)
    End If
    Call SetPropertyStoreValue(hwnd)
    Form.Tag = hwnd & "|" & ThumbnailTooltip
    oFormsCollection.Add Form, CStr(hwnd)

End Sub


Public Sub RemoveFromTaskBar(ByVal Form As Object)

    #If Win64 Then
        Dim hwnd As LongLong
    #Else
        Dim hwnd As Long
    #End If
 
    Call IUnknown_GetWindow(Form, VarPtr(hwnd))
    Call SetPropertyStoreValue(hwnd, False)
    oFormsCollection.Remove CStr(hwnd)
    If VBA.UserForms.Count = 1 Then
        Set oFormsCollection = Nothing
        Call KillTimer(Application.hwnd, 0)
        Application.Visible = True
    End If

End Sub




'_______________________________Private Routines___________________________________________

#If Win64 Then
    Private Sub SetPropertyStoreValue(ByVal hwnd As LongLong, Optional ByVal Enable As Boolean = True)
        Const PTR_LEN = 8&
        Const VT_LPWSTR = 31&
        Const VT_EMPTY = 0&
        Dim pPstore As LongLong
        Dim PV(0 To 2) As LongLong
        If Enable Then
            PV(0) = VT_LPWSTR: PV(1) = StrPtr("Dummy")
        Else
            PV(0) = VT_EMPTY: PV(1) = StrPtr("Dummy")
        End If
#Else
    Private Sub SetPropertyStoreValue(ByVal hwnd As Long, Optional ByVal Enable As Boolean = True)
        Const PTR_LEN = 4&
        Const VT_LPWSTR = 31&
        Const VT_EMPTY = 0&
        Dim pPstore As Long
        Dim PV(0 To 3) As Long
        If Enable Then
            PV(0) = VT_LPWSTR: PV(2) = StrPtr("Dummy")
        Else
            PV(0) = VT_EMPTY: PV(2) = StrPtr("Dummy")
        End If
#End If

    Const S_OK = 0&
    Const CC_STDCALL = 4&
    Const IID_PropertyStore = "{886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99}"
    Const IID_PropertyKey = "{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"
 
    Dim tClsID As GUID, tIID As GUID, tPK As PROPERTYKEY
 
    Call CLSIDFromString(StrPtr(IID_PropertyStore), tIID)
    If SHGetPropertyStoreForWindow(hwnd, tIID, pPstore) = S_OK Then
        Call CLSIDFromString(StrPtr(IID_PropertyKey), tPK)
        tPK.pid = 5
        If Enable Then
            Call vtblCall(pPstore, 6 * PTR_LEN, vbLong, CC_STDCALL, VarPtr(tPK), VarPtr(PV(0)))   'IPropertyStore::SetValue
            Call vtblCall(pPstore, 7 * PTR_LEN, vbLong, CC_STDCALL)  'IPropertyStore::Commit
        Else
            Call vtblCall(pPstore, 6 * PTR_LEN, vbLong, CC_STDCALL, VarPtr(tPK), VarPtr(PV(0)))  'IPropertyStore::SetValue
        End If
    Else
        MsgBox "Unable to get the 'IPropertyStore' interface...", , "ERROR":   End
    End If
 
End Sub


Private Sub CreateTBarInstance()

    Const CLSCTX_INPROC_SERVER = &H1
    Const S_OK = 0&
    Const CLSID_TASKLIST = "{56FDF344-FD6D-11D0-958A-006097C9A090}"
    Const IID_TASKLIST3 = "{EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF}"
 
    Dim tClsID As GUID, tIID As GUID
 
    Call CLSIDFromString(StrPtr(CLSID_TASKLIST), tClsID)
    Call CLSIDFromString(StrPtr(IID_TASKLIST3), tIID)
    If CoCreateInstance(tClsID, 0, CLSCTX_INPROC_SERVER, tIID, pTBarList) <> S_OK Then
        MsgBox "Unable to get the 'ITaskbarList3' interface...", , "ERROR":   End
    End If

End Sub


#If Win64 Then
    Private Sub AddTab(ByVal hwnd As LongLong, Optional ByVal ToolTip As String)
        Const PTR_LEN = 8&
#Else
    Private Sub AddTab(ByVal hwnd As Long, Optional ByVal ToolTip As String)
        Const PTR_LEN = 4&
#End If

    Const CC_STDCALL = 4&
 
    Call vtblCall(pTBarList, 3 * PTR_LEN, vbLong, CC_STDCALL) 'ITaskbarList3::HrInit
    Call vtblCall(pTBarList, 4 * PTR_LEN, vbLong, CC_STDCALL, hwnd) 'ITaskbarList3::AddTab
    Call vtblCall(pTBarList, 6 * PTR_LEN, vbLong, CC_STDCALL, hwnd) 'ITaskbarList3::ActivateTab
    If Len(ToolTip) Then
        Call vtblCall(pTBarList, 19 * PTR_LEN, vbLong, CC_STDCALL, hwnd, StrPtr(ToolTip))  'ITaskbarList3::SetThumbnailTooltip
    End If

End Sub


#If Win64 Then
    Private Sub DeleteTab(ByVal hwnd As LongLong)
        Const PTR_LEN = 8&
#Else
    Private Sub DeleteTab(ByVal hwnd As Long)
        Const PTR_LEN = 4&
#End If

    Const CC_STDCALL = 4&
 
    Call vtblCall(pTBarList, 5 * PTR_LEN, vbLong, CC_STDCALL, hwnd) 'ITaskbarList3::DeleteTab
End Sub


Private Sub addicon(ByVal Form As Object, Optional IconFromPic As StdPicture, Optional ByVal IconFromFile As String, Optional ByVal Index As Long = 0)
 
    #If Win64 Then
        Dim hwnd As LongLong, hIcon As LongLong
    #Else
        Dim hwnd As Long, hIcon As Long
    #End If
 
    Const WM_SETICON = &H80
    Const ICON_SMALL = 0
    Const ICON_BIG = 1

    Dim n As Long, S As String
 
    Call IUnknown_GetWindow(Form, VarPtr(hwnd))
    If Not IconFromPic Is Nothing Then
        hIcon = IconFromPic.Handle
        Call SendMessage(hwnd, WM_SETICON, ICON_SMALL, ByVal hIcon)
        Call SendMessage(hwnd, WM_SETICON, ICON_BIG, ByVal hIcon)
    ElseIf Len(IconFromFile) Then
        If Dir(IconFromFile, vbNormal) = vbNullString Then
            Exit Sub
        End If
        n = InStrRev(IconFromFile, ".")
        S = LCase(Mid(IconFromFile, n + 1))
        Select Case S
            Case "exe", "ico", "dll"
            Case Else
                Err.Raise 5
        End Select
        If hwnd = 0 Then
            Exit Sub
        End If
        hIcon = ExtractIconA(0, IconFromFile, Index)
        If hIcon <> 0 Then
            Call SendMessage(hwnd, WM_SETICON, ICON_SMALL, ByVal hIcon)
        End If
    End If

    Call DrawMenuBar(hwnd)
    Call DeleteObject(hIcon)
 
End Sub


Private Sub TimerProc()

    #If Win64 Then
        Static hPrevForegroundWinHwnd As LongLong
        Dim hCurrentForegroundWinHwnd As LongLong
    #Else
        Static hPrevForegroundWinHwnd As Long
        Dim hCurrentForegroundWinHwnd As Long
    #End If
 
    Dim sTmpArray() As String
    Dim vItem As Variant
    Dim sTag As String, sToolTip As String
    Dim sBuff As String * 256, lRet As Long
 
   On Error Resume Next
 
   If oFormsCollection.Count = 1 Then Exit Sub

   If pTBarList = 0 Then
      'SAFE EXIT in case of an unhandled error !!
      Call KillTimer(Application.hwnd, 0):  Application.Visible = True: Exit Sub
   End If
 
    Call KillTimer(Application.hwnd, 0)
    If hPrevForegroundWinHwnd <> GetForegroundWindow Then
        lRet = GetClassName(GetForegroundWindow, sBuff, 256)
        If Left(sBuff, lRet) = "ThunderDFrame" Or Left(sBuff, lRet) = "ThunderXFrame" Then
            hCurrentForegroundWinHwnd = GetForegroundWindow
            For Each vItem In oFormsCollection
                sTag = vItem.Tag
                sTmpArray = Split(sTag, "|")
                If sTmpArray(0) = hCurrentForegroundWinHwnd Then
                    sToolTip = sTmpArray(1)
                End If
                #If Win64 Then
                    Call DeleteTab(CLngLng(sTmpArray(0)))
                #Else
                    Call DeleteTab(CLng(sTmpArray(0)))
                #End If
            Next
            Call AddTab(hCurrentForegroundWinHwnd, sToolTip)
            Call Sleep(50)
            For Each vItem In oFormsCollection
                sTag = vItem.Tag
                sTmpArray = Split(sTag, "|")
                If sTmpArray(0) <> hCurrentForegroundWinHwnd And CBool(IsWindowVisible(sTmpArray(0))) Then
                    #If Win64 Then
                        Call AddTab(CLngLng(sTmpArray(0)), sTmpArray(1))
                    #Else
                        Call AddTab(CLng(sTmpArray(0)), sTmpArray(1))
                    #End If
               
             
                End If
            Next
        End If
    End If
 
    Call SetTimer(Application.hwnd, 0, 0, AddressOf TimerProc)
 
    hPrevForegroundWinHwnd = GetForegroundWindow

End Sub


#If Win64 Then
    Private Function vtblCall(ByVal InterfacePointer As LongLong, ByVal VTableOffset As Long, ByVal FunctionReturnType As Long, ByVal CallConvention As Long, ParamArray FunctionParameters() As Variant) As Variant
        Dim vParamPtr() As LongLong
#Else
    Private Function vtblCall(ByVal InterfacePointer As Long, ByVal VTableOffset As Long, ByVal FunctionReturnType As Long, ByVal CallConvention As Long, ParamArray FunctionParameters() As Variant) As Variant
        Dim vParamPtr() As Long
#End If

    If InterfacePointer = 0& Or VTableOffset < 0& Then Exit Function
    If Not (FunctionReturnType And &HFFFF0000) = 0& Then Exit Function

    Dim pIndex As Long, pCount As Long
    Dim vParamType() As Integer
    Dim vRtn As Variant, vParams() As Variant

    vParams() = FunctionParameters()
    pCount = Abs(UBound(vParams) - LBound(vParams) + 1&)
    If pCount = 0& Then
        ReDim vParamPtr(0 To 0)
        ReDim vParamType(0 To 0)
    Else
        ReDim vParamPtr(0 To pCount - 1&)
        ReDim vParamType(0 To pCount - 1&)
        For pIndex = 0& To pCount - 1&
            vParamPtr(pIndex) = VarPtr(vParams(pIndex))
            vParamType(pIndex) = VarType(vParams(pIndex))
        Next
    End If

    pIndex = DispCallFunc(InterfacePointer, VTableOffset, CallConvention, FunctionReturnType, pCount, _
    vParamType(0), vParamPtr(0), vRtn)
    If pIndex = 0& Then
        vtblCall = vRtn
    Else
        SetLastError pIndex
    End If

End Function
 
Last edited:
Upvote 1
Oops ! Thread Editing time up :(

Little code change :

Just for the record, this small code section in the TimerProc Proc:
VBA Code:
 If oFormsCollection.Count = 1 Then Exit Sub

 If pTBarList = 0 Then
    'SAFE EXIT in case of an unhandled error !!
    Call KillTimer(Application.hwnd, 0):  Application.Visible = True: Exit Sub
 End If

Should become :
VBA Code:
 If pTBarList = 0 Then
    'SAFE EXIT in case of an unhandled error !!
    Call KillTimer(Application.hwnd, 0):  Application.Visible = True: Exit Sub
 End If
 
 If oFormsCollection.Count = 1 Then Exit Sub

I have already updated the above workbook example with this last minute small change.
 
Upvote 0
@gilaxg10

Welcome to the forum and thanks for the feedback


I am not entirely sure I understand what you exactly mean but, yes, I did notice that sometimes when clicking on the form thumbnail on the taskbar, the userform is not properly toggled between minimized and maximized. If that's what you mean then below is an update that fixes the issue.
Again, thanks for bringing this to my attention.

Note that this only applies when you have a single userform.

UPDATE:
V2-MultipleFormsToTaskBar.xlsm







Updated API code:
VBA Code:
Option Explicit

'Jaafar Tribak @ MrExcel.com on 12/03/2021.
'Display vba userform icon in taskbar.
'Makes use of the Shell32.dll ITASKLIST3 Interface in order to work in Windows7 and onwards.
'UPDATE on 22/11/2022

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

Private Type PROPERTYKEY
    fmtid As GUID
    pid As Long
End Type

#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
        Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
    #Else
        Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
        Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As Long) As LongPtr
    #End If
    Private Declare PtrSafe Function DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long
    Private Declare PtrSafe Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
    Private Declare PtrSafe Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
    Private Declare PtrSafe Function ExtractIconA Lib "shell32.dll" (ByVal hInst As LongPtr, ByVal lpszExeFileName As String, ByVal nIconIndex As LongPtr) As Long
    Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As Any) As Long
    Private Declare PtrSafe Function SHGetPropertyStoreForWindow Lib "shell32.dll" (ByVal hwnd As LongPtr, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
    Private Declare PtrSafe Function DrawMenuBar Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As LongPtr) As Long
    Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
    Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
    Private Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr
    Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
    Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
    Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
    Private Declare PtrSafe Function ShowWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal nCmdShow As Long) As Long
    Private Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hwnd As LongPtr) As Long
 
    Private pTBarList As LongPtr
#Else
    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 DispCallFunc Lib "oleAut32.dll" (ByVal pvInstance As Long, ByVal offsetinVft As Long, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As Long, ByRef retVAR As Variant) As Long
    Private Declare Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
    Private Declare Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As Long, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As Long) As Long
    Private Declare Function ExtractIconA Lib "Shell32.dll" (ByVal hInst As Long, ByVal lpszExeFileName As String, ByVal nIconIndex As Long) As Long
    Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hwnd As Long) As Long
    Private Declare Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As Long, ByRef cGUID As Any) As Long
    Private Declare Function SHGetPropertyStoreForWindow Lib "Shell32.dll" (ByVal hwnd As Long, ByRef riid As GUID, ByRef ppv As Long) As Long
    Private Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
    Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
    Private Declare Function GetForegroundWindow Lib "user32" () As Long
    Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
    Private Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
    Private Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent 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 ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
    Private Declare Function IsWindowVisible Lib "user32" (ByVal Hwnd As Long) As Long
 
    Private pTBarList As Long
#End If

Private oFormsCollection As Collection



'_______________________________Public Routines______________________________


Public Sub AddToTaskBar _
    ( _
        ByVal Form As Object, _
        Optional ByVal IconFromPic As StdPicture, _
        Optional ByVal IconFromFile As String, _
        Optional ByVal FileIconIndex As Long = 0, _
        Optional ThumbnailTooltip As String, _
        Optional HideExcelApplication As Boolean _
    )
 
    Const GWL_STYLE = (-16)
    Const WS_MINIMIZEBOX = &H20000
    Const GWL_HWNDPARENT = (-8)

    #If Win64 Then
        Dim hwnd As LongLong
    #Else
        Dim hwnd As Long
    #End If
 
   If HideExcelApplication Then
        Application.Visible = False
        Call ShowWindow(FindWindow("wndclass_desked_gsk", vbNullString), 0)
   End If
 
    Call IUnknown_GetWindow(Form, VarPtr(hwnd))
    Call SetWindowLong(hwnd, GWL_HWNDPARENT, 0)
    Call SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) Or WS_MINIMIZEBOX)
    Call DrawMenuBar(hwnd)
    If Not IconFromPic Is Nothing Then
        Call addicon(Form, IconFromPic, , FileIconIndex)
    ElseIf Len(IconFromFile) Then
        Call addicon(Form, , IconFromFile, FileIconIndex)
    End If
    If oFormsCollection Is Nothing Then
        Set oFormsCollection = New Collection
        Call CreateTBarInstance
        Call KillTimer(Application.hwnd, 0)
        Call SetTimer(Application.hwnd, 0, 0, AddressOf TimerProc)
    End If
    Call SetPropertyStoreValue(hwnd)
    Form.Tag = hwnd & "|" & ThumbnailTooltip
    oFormsCollection.Add Form, CStr(hwnd)

End Sub


Public Sub RemoveFromTaskBar(ByVal Form As Object)

    #If Win64 Then
        Dim hwnd As LongLong
    #Else
        Dim hwnd As Long
    #End If
 
    Call IUnknown_GetWindow(Form, VarPtr(hwnd))
    Call SetPropertyStoreValue(hwnd, False)
    oFormsCollection.Remove CStr(hwnd)
    If VBA.UserForms.Count = 1 Then
        Set oFormsCollection = Nothing
        Call KillTimer(Application.hwnd, 0)
        Application.Visible = True
    End If

End Sub




'_______________________________Private Routines___________________________________________

#If Win64 Then
    Private Sub SetPropertyStoreValue(ByVal hwnd As LongLong, Optional ByVal Enable As Boolean = True)
        Const PTR_LEN = 8&
        Const VT_LPWSTR = 31&
        Const VT_EMPTY = 0&
        Dim pPstore As LongLong
        Dim PV(0 To 2) As LongLong
        If Enable Then
            PV(0) = VT_LPWSTR: PV(1) = StrPtr("Dummy")
        Else
            PV(0) = VT_EMPTY: PV(1) = StrPtr("Dummy")
        End If
#Else
    Private Sub SetPropertyStoreValue(ByVal hwnd As Long, Optional ByVal Enable As Boolean = True)
        Const PTR_LEN = 4&
        Const VT_LPWSTR = 31&
        Const VT_EMPTY = 0&
        Dim pPstore As Long
        Dim PV(0 To 3) As Long
        If Enable Then
            PV(0) = VT_LPWSTR: PV(2) = StrPtr("Dummy")
        Else
            PV(0) = VT_EMPTY: PV(2) = StrPtr("Dummy")
        End If
#End If

    Const S_OK = 0&
    Const CC_STDCALL = 4&
    Const IID_PropertyStore = "{886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99}"
    Const IID_PropertyKey = "{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"
 
    Dim tClsID As GUID, tIID As GUID, tPK As PROPERTYKEY
 
    Call CLSIDFromString(StrPtr(IID_PropertyStore), tIID)
    If SHGetPropertyStoreForWindow(hwnd, tIID, pPstore) = S_OK Then
        Call CLSIDFromString(StrPtr(IID_PropertyKey), tPK)
        tPK.pid = 5
        If Enable Then
            Call vtblCall(pPstore, 6 * PTR_LEN, vbLong, CC_STDCALL, VarPtr(tPK), VarPtr(PV(0)))   'IPropertyStore::SetValue
            Call vtblCall(pPstore, 7 * PTR_LEN, vbLong, CC_STDCALL)  'IPropertyStore::Commit
        Else
            Call vtblCall(pPstore, 6 * PTR_LEN, vbLong, CC_STDCALL, VarPtr(tPK), VarPtr(PV(0)))  'IPropertyStore::SetValue
        End If
    Else
        MsgBox "Unable to get the 'IPropertyStore' interface...", , "ERROR":   End
    End If
 
End Sub


Private Sub CreateTBarInstance()

    Const CLSCTX_INPROC_SERVER = &H1
    Const S_OK = 0&
    Const CLSID_TASKLIST = "{56FDF344-FD6D-11D0-958A-006097C9A090}"
    Const IID_TASKLIST3 = "{EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF}"
 
    Dim tClsID As GUID, tIID As GUID
 
    Call CLSIDFromString(StrPtr(CLSID_TASKLIST), tClsID)
    Call CLSIDFromString(StrPtr(IID_TASKLIST3), tIID)
    If CoCreateInstance(tClsID, 0, CLSCTX_INPROC_SERVER, tIID, pTBarList) <> S_OK Then
        MsgBox "Unable to get the 'ITaskbarList3' interface...", , "ERROR":   End
    End If

End Sub


#If Win64 Then
    Private Sub AddTab(ByVal hwnd As LongLong, Optional ByVal ToolTip As String)
        Const PTR_LEN = 8&
#Else
    Private Sub AddTab(ByVal hwnd As Long, Optional ByVal ToolTip As String)
        Const PTR_LEN = 4&
#End If

    Const CC_STDCALL = 4&
 
    Call vtblCall(pTBarList, 3 * PTR_LEN, vbLong, CC_STDCALL) 'ITaskbarList3::HrInit
    Call vtblCall(pTBarList, 4 * PTR_LEN, vbLong, CC_STDCALL, hwnd) 'ITaskbarList3::AddTab
    Call vtblCall(pTBarList, 6 * PTR_LEN, vbLong, CC_STDCALL, hwnd) 'ITaskbarList3::ActivateTab
    If Len(ToolTip) Then
        Call vtblCall(pTBarList, 19 * PTR_LEN, vbLong, CC_STDCALL, hwnd, StrPtr(ToolTip))  'ITaskbarList3::SetThumbnailTooltip
    End If

End Sub


#If Win64 Then
    Private Sub DeleteTab(ByVal hwnd As LongLong)
        Const PTR_LEN = 8&
#Else
    Private Sub DeleteTab(ByVal hwnd As Long)
        Const PTR_LEN = 4&
#End If

    Const CC_STDCALL = 4&
 
    Call vtblCall(pTBarList, 5 * PTR_LEN, vbLong, CC_STDCALL, hwnd) 'ITaskbarList3::DeleteTab
End Sub


Private Sub addicon(ByVal Form As Object, Optional IconFromPic As StdPicture, Optional ByVal IconFromFile As String, Optional ByVal Index As Long = 0)
 
    #If Win64 Then
        Dim hwnd As LongLong, hIcon As LongLong
    #Else
        Dim hwnd As Long, hIcon As Long
    #End If
 
    Const WM_SETICON = &H80
    Const ICON_SMALL = 0
    Const ICON_BIG = 1

    Dim n As Long, S As String
 
    Call IUnknown_GetWindow(Form, VarPtr(hwnd))
    If Not IconFromPic Is Nothing Then
        hIcon = IconFromPic.Handle
        Call SendMessage(hwnd, WM_SETICON, ICON_SMALL, ByVal hIcon)
        Call SendMessage(hwnd, WM_SETICON, ICON_BIG, ByVal hIcon)
    ElseIf Len(IconFromFile) Then
        If Dir(IconFromFile, vbNormal) = vbNullString Then
            Exit Sub
        End If
        n = InStrRev(IconFromFile, ".")
        S = LCase(Mid(IconFromFile, n + 1))
        Select Case S
            Case "exe", "ico", "dll"
            Case Else
                Err.Raise 5
        End Select
        If hwnd = 0 Then
            Exit Sub
        End If
        hIcon = ExtractIconA(0, IconFromFile, Index)
        If hIcon <> 0 Then
            Call SendMessage(hwnd, WM_SETICON, ICON_SMALL, ByVal hIcon)
        End If
    End If

    Call DrawMenuBar(hwnd)
    Call DeleteObject(hIcon)
 
End Sub


Private Sub TimerProc()

    #If Win64 Then
        Static hPrevForegroundWinHwnd As LongLong
        Dim hCurrentForegroundWinHwnd As LongLong
    #Else
        Static hPrevForegroundWinHwnd As Long
        Dim hCurrentForegroundWinHwnd As Long
    #End If
 
    Dim sTmpArray() As String
    Dim vItem As Variant
    Dim sTag As String, sToolTip As String
    Dim sBuff As String * 256, lRet As Long
 
   On Error Resume Next
 
   If oFormsCollection.Count = 1 Then Exit Sub

   If pTBarList = 0 Then
      'SAFE EXIT in case of an unhandled error !!
      Call KillTimer(Application.hwnd, 0):  Application.Visible = True: Exit Sub
   End If
 
    Call KillTimer(Application.hwnd, 0)
    If hPrevForegroundWinHwnd <> GetForegroundWindow Then
        lRet = GetClassName(GetForegroundWindow, sBuff, 256)
        If Left(sBuff, lRet) = "ThunderDFrame" Or Left(sBuff, lRet) = "ThunderXFrame" Then
            hCurrentForegroundWinHwnd = GetForegroundWindow
            For Each vItem In oFormsCollection
                sTag = vItem.Tag
                sTmpArray = Split(sTag, "|")
                If sTmpArray(0) = hCurrentForegroundWinHwnd Then
                    sToolTip = sTmpArray(1)
                End If
                #If Win64 Then
                    Call DeleteTab(CLngLng(sTmpArray(0)))
                #Else
                    Call DeleteTab(CLng(sTmpArray(0)))
                #End If
            Next
            Call AddTab(hCurrentForegroundWinHwnd, sToolTip)
            Call Sleep(50)
            For Each vItem In oFormsCollection
                sTag = vItem.Tag
                sTmpArray = Split(sTag, "|")
                If sTmpArray(0) <> hCurrentForegroundWinHwnd And CBool(IsWindowVisible(sTmpArray(0))) Then
                    #If Win64 Then
                        Call AddTab(CLngLng(sTmpArray(0)), sTmpArray(1))
                    #Else
                        Call AddTab(CLng(sTmpArray(0)), sTmpArray(1))
                    #End If
              
            
                End If
            Next
        End If
    End If
 
    Call SetTimer(Application.hwnd, 0, 0, AddressOf TimerProc)
 
    hPrevForegroundWinHwnd = GetForegroundWindow

End Sub


#If Win64 Then
    Private Function vtblCall(ByVal InterfacePointer As LongLong, ByVal VTableOffset As Long, ByVal FunctionReturnType As Long, ByVal CallConvention As Long, ParamArray FunctionParameters() As Variant) As Variant
        Dim vParamPtr() As LongLong
#Else
    Private Function vtblCall(ByVal InterfacePointer As Long, ByVal VTableOffset As Long, ByVal FunctionReturnType As Long, ByVal CallConvention As Long, ParamArray FunctionParameters() As Variant) As Variant
        Dim vParamPtr() As Long
#End If

    If InterfacePointer = 0& Or VTableOffset < 0& Then Exit Function
    If Not (FunctionReturnType And &HFFFF0000) = 0& Then Exit Function

    Dim pIndex As Long, pCount As Long
    Dim vParamType() As Integer
    Dim vRtn As Variant, vParams() As Variant

    vParams() = FunctionParameters()
    pCount = Abs(UBound(vParams) - LBound(vParams) + 1&)
    If pCount = 0& Then
        ReDim vParamPtr(0 To 0)
        ReDim vParamType(0 To 0)
    Else
        ReDim vParamPtr(0 To pCount - 1&)
        ReDim vParamType(0 To pCount - 1&)
        For pIndex = 0& To pCount - 1&
            vParamPtr(pIndex) = VarPtr(vParams(pIndex))
            vParamType(pIndex) = VarType(vParams(pIndex))
        Next
    End If

    pIndex = DispCallFunc(InterfacePointer, VTableOffset, CallConvention, FunctionReturnType, pCount, _
    vParamType(0), vParamPtr(0), vRtn)
    If pIndex = 0& Then
        vtblCall = vRtn
    Else
        SetLastError pIndex
    End If

End Function
Well! I wasn't expecting such a quick reply. Thank you for that.
I have downloaded the latest update you supplied and tried and observed what is happening.
When the 'Show Userforms' button is selected the 'Userform' is displayed 'maximized' on the desktop with the thumbnail on the taskbar.
That works great, But!
Can the default be changed to when 'Show Userforms' button is selected the 'Userform' is displayed 'minimized' on the desktop with the thumbnail on the taskbar.
Many thanks for your continued support
 
Upvote 1
@gilaxg10

Ok, See if this works for you :

I have added this extra optional Boolean argument ( InitialMinimizedState ) to the AddToTaskBar Sub. Now, if you set this argumentt to TRUE, the form will be initially displayed minimized like you want.

Lile this :
Rich (BB code):
Private Sub UserForm_Initialize()
    
    Call AddToTaskBar( _
            Form:=Me, _
            IconFromPic:=Sheet1.Image1.Picture, _
            ThumbnailTooltip:="This is the taskbar tooltip for: " & Me.Name, HideExcelApplication:=True, _
            InitialMinimizedState:=True)
End Sub

This does minimize the form(s) upon first being displayed but is actually not very quick as you can briefly see them being minimized.

Here is a workbok demo you can test:
MultipleFormsToTaskBar_InitialMinimized.xlsm

Regards.
 
Upvote 0
@gilaxg10

Ok, See if this works for you :

I have added this extra optional Boolean argument ( InitialMinimizedState ) to the AddToTaskBar Sub. Now, if you set this argumentt to TRUE, the form will be initially displayed minimized like you want.

Lile this :
Rich (BB code):
Private Sub UserForm_Initialize()
   
    Call AddToTaskBar( _
            Form:=Me, _
            IconFromPic:=Sheet1.Image1.Picture, _
            ThumbnailTooltip:="This is the taskbar tooltip for: " & Me.Name, HideExcelApplication:=True, _
            InitialMinimizedState:=True)
End Sub

This does minimize the form(s) upon first being displayed but is actually not very quick as you can briefly see them being minimized.

Here is a workbok demo you can test:
MultipleFormsToTaskBar_InitialMinimized.xlsm

Regards.
Thank you so much Jaafar Tribak. That works perfectly for me.
 
Upvote 0

Forum statistics

Threads
1,224,813
Messages
6,181,114
Members
453,021
Latest member
Justyna P

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top