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
 
This undoubtedly is a great solution (from a great mind) to any custom excel application. I have an issue with the usage. I have a UI with more than two dozen UF's. Some of the UF's get hidden, loaded, unloaded when another UF opens/loads/show or some UF's are open on top of modal or modeless UF's. When using this solution and knowing that some forms are hidden while opening others, and some UF's are open on top of UF's that stay loaded, when switching between these UF's, the activate, initialize, query close, deactivate events don't always trigger creating several task bar icons thus allowing the the user to mistake the correct icon to select. For example. UF1 is opened/loaded and from a button on UF1 form, UF2 is loaded on top of UF1 (modal) thus creating the issue. Excel requires UF2 (modal) to be unloaded/hidden prior to returning to UF1. The two icons on the task bar allow the user to select UF1 which is behind UF2 and brings UF1 to the front and unable to use because UF2 is loaded modal and must be closed prior to return to UF1. Here is my question. Is it possible to have the icon be shown for only the topmost UF at all times. Maybe have a hook in the UF that qualifies the API code that this UF is part of the collection of UF's that you want the icon to be displayed? I tried placing the ResetTaskbar call in the query close, deactivate, terminate to unload itself but as I mentioned above some of these events do not trigger depending how the UF's are managed thus creating >1 taskbar icons.

Thanks in advance.
@FTSC

Dynamically updating the taskbar to show the icon of the currently topmost userform requires subclassing which would make the entire application unstable.

I can think of a workaround like having a background timer procedure that monitors the topmost window and change the order of the tabs.

I will have a go at this and let you know what comes up.
 
Upvote 0

Excel Facts

Did you know Excel offers Filter by Selection?
Add the AutoFilter icon to the Quick Access Toolbar. Select a cell containing Apple, click AutoFilter, and you will get all rows with Apple
Thanks for the quick reply. I really appreciate it. I have many UF's that come and go to the front (an only two worksheets) in the entire UI. I hope I explained my concerns well enough for you and really appreciate your time and efforts.

Thank you
 
Upvote 0
Hi Jaafar,

It's perfect but can you make it only hide the user's book? since if there is a file open or if i try to open another existing file it hides it.
 
Upvote 0
@Jaafar Tribak your works always are unique and astonishing actually I use win10 and SYS 64bit ,excel 2016 your work is very impressive and I will take it and put in my project but I have an important question , does it effect with the others macros in the same file like error in my project maybe overlap the procedures with each them ?
finally if it is possible I would like it to appear in full screen mode
I mean making large the userform
 
Upvote 0
@Jaafar Tribak your works always are unique and astonishing actually I use win10 and SYS 64bit ,excel 2016 your work is very impressive and I will take it and put in my project but I have an important question , does it effect with the others macros in the same file like error in my project maybe overlap the procedures with each them ?
finally if it is possible I would like it to appear in full screen mode
I mean making large the userform

It shouldn't have an effect on your other macros... but careful coding always help.
As for making the userform fullscreen, here is an example and see if you can incorporate it into your project:
 
Upvote 0
Hi Jaafar,

It's perfect but can you make it only hide the user's book? since if there is a file open or if i try to open another existing file it hides it.
You can always omit hiding the main excel application window, if you wish so, by simply not setting the last argument ( HideExcel:=True)
 
Upvote 0
@FTSC

Sorry for not getting back sooner. I needed time in order to test the code on different platforms.

Demo file:
V2-MultipleFormsToTaskBar.xlsm


This new code works with multiple userforms and updates the taskbar icon dynamically to reflect the userform that is currently topmost (ie: the currently active userform).







1- Main Code in a Standard Module:
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.

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 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 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

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)
    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 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 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
    hPrevForegroundWinHwnd = GetForegroundWindow
    Call SetTimer(Application.hwnd, 0, 0, AddressOf TimerProc)

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(s) Module(s)
VBA Code:
Option Explicit

Private Sub UserForm_Initialize()
    Call AddToTaskBar( _
            Form:=Me, _
            IconFromPic:=Sheet1.Image1.Picture, _
            ThumbnailTooltip:="This is the taskbar tooltip for: " & Me.Name, HideExcelApplication:=False _
                   )
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Call RemoveFromTaskBar(Me)
End Sub



'==========================================================
'Alternatively, you can add an icon to the userform from an ICO file as follows:
'Change the Icon file path\name as required.
'===========================================================

'            Private Sub UserForm_Initialize()
'
'                Call AddToTaskBar( _
'                        Form:=Me, _
'                        IconFromFile:="C:\....\MyIcon.ico", _
'                        ThumbnailTooltip:="This is the taskbar tooltip for: " & Me.Name _
'                    )
'
'            End Sub


If you want to to use a file icon instead of an icon embeeded in the worksheet, just set the IconFromFile argument instead of the IconFromPic.

If you want to hide the application main window so that the project looks like a standalone application, just set the HideExcelApplication argument to True when calling the AddToTaskBar routine from any of the userforms
 
Upvote 0
@FTSC

Sorry for not getting back sooner. I needed time in order to test the code on different platforms.

Demo file:
V2-MultipleFormsToTaskBar.xlsm


This new code works with multiple userforms and updates the taskbar icon dynamically to reflect the userform that is currently topmost (ie: the currently active userform).







1- Main Code in a Standard Module:
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.

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 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 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

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)
    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 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 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
    hPrevForegroundWinHwnd = GetForegroundWindow
    Call SetTimer(Application.hwnd, 0, 0, AddressOf TimerProc)

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(s) Module(s)
VBA Code:
Option Explicit

Private Sub UserForm_Initialize()
    Call AddToTaskBar( _
            Form:=Me, _
            IconFromPic:=Sheet1.Image1.Picture, _
            ThumbnailTooltip:="This is the taskbar tooltip for: " & Me.Name, HideExcelApplication:=False _
                   )
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Call RemoveFromTaskBar(Me)
End Sub



'==========================================================
'Alternatively, you can add an icon to the userform from an ICO file as follows:
'Change the Icon file path\name as required.
'===========================================================

'            Private Sub UserForm_Initialize()
'
'                Call AddToTaskBar( _
'                        Form:=Me, _
'                        IconFromFile:="C:\....\MyIcon.ico", _
'                        ThumbnailTooltip:="This is the taskbar tooltip for: " & Me.Name _
'                    )
'
'            End Sub


If you want to to use a file icon instead of an icon embeeded in the worksheet, just set the IconFromFile argument instead of the IconFromPic.

If you want to hide the application main window so that the project looks like a standalone application, just set the HideExcelApplication argument to True when calling the AddToTaskBar routine from any of the userforms
wow ! it's very impressive , keep going innovation
 
Upvote 0
This is super cool and makes an application look much more professional, thank you so much! I actually registered to say thanks :D
What I struggled with was to fetch the image, because I could not understand how you added the image to the sheet in your example. For anyone having a similar issue:
  1. Add an image to your userform via Toolbox => Image
  2. In the image properties, go to Picture ... and load your .ico file
  3. Set visible = false
  4. Assuming your image is named Image1, you can then call the sub with IconFromPic:=Me.Image1.Picture
 
Upvote 0
This is super cool and makes an application look much more professional, thank you so much! I actually registered to say thanks :D
What I struggled with was to fetch the image, because I could not understand how you added the image to the sheet in your example. For anyone having a similar issue:
  1. Add an image to your userform via Toolbox => Image
  2. In the image properties, go to Picture ... and load your .ico file
  3. Set visible = false
  4. Assuming your image is named Image1, you can then call the sub with IconFromPic:=Me.Image1.Picture
Thanks for the feedback.

In addition to adding the icon from an image control in your sheet, you can also use the IconFromFile argument to load the icon from an icon file in your disk.
 
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