[VB6] Adding Custom Tasks and Items to the Jump List (Taskbar Right-click)
cJump v1.0 Adding to the Jump List with ICustomDestinationList
This project has been a long time coming. Started working on porting a C++ example to VB6 way back in 2014, and wasn't able to get it working despite many many hours playing around with interface definitions. Had tons of hope when Olaf was able to get things working using vbRichClient5 (see this thread). But of course I wanted to use the interfaces through oleexp rather than RC5. Simply changing out the calls in his project still left things not working, and I had tons of frustration as I'd spend countless hours, give up, and revisit and repeat over the years. Today I found the problem; I re-did the conversion, only this time included the class, and it worked. So more hours and hours thinking it needed to be done from a class, until I traced it to a single call and single problem: I had declared an API call wrong, and had copy/pasted the right version into the class. That's that held this all up for 5 years and countless hours trying to figure out the problem.
When you click on one of the items, it launches a new instance of your app with the information on what was clicked passed through the command line, retrieved with the Command$() function in VB. Note that this means nothing will happen if you click an item without having compiled an exe first (and I get an error clicking an item from the IDE in the new instance, but it works fine when compiled). You can then handle the command in any number of ways; have the new instance perform the task, or use various IPC methods to notify the first instance of your app and then exit before loading anything (note that this it not shown in the demo, the demo just creates a block for a non-empty command line).
Requirements
Windows 7 or newer oleexp v4.5 or higher (Released 01 Aug 2018)
(Older oleexp versions *might* work with or without slight changes, but I had been playing around with the interfaces quite a bit trying to get them to work, so I'm not sure which versions work, which versions don't, or which versions need the calls to be different)
Code
Using oleexp allows simplifying things quite a bit, here's the class:
Code:
Option Explicit
'cJump v1.0: Using Jump Lists in VB6
'by fafalone
'
'Requires oleexp.tlb v4.5 or higher
'This is a version of Olaf's demonstration class of doing this with vbRichClient5, to use
'the relavent interfaces through oleexp instead
Private Declare Function SetCurrentProcessExplicitAppUserModelID Lib "shell32" (ByVal psID As Long) As Long 'NOT STRING NOT STRING NOT STRING
Private pCDL As ICustomDestinationList
Public Function InitList(sAppID As String, Optional nMinSlot As Long) As IObjectArray
SetCurrentProcessExplicitAppUserModelID StrPtr(sAppID)
Set pCDL = New DestinationList
pCDL.SetAppID StrPtr(sAppID)
pCDL.BeginList nMinSlot, IID_IObjectArray, InitList
End Function
Public Sub AddToList(pList As oleexp.IObjectCollection, Optional sTitle As String, Optional sArgs As String, Optional sToolTip As String, Optional ByVal nIconID As Long)
Dim hr As Long
Dim pLink As ShellLinkW
Dim pStore As IPropertyStore
Set pLink = New ShellLinkW
pLink.SetPath App.Path & "\" & App.EXEName & ".exe"
pLink.SetArguments sArgs
pLink.SetDescription sToolTip
pLink.SetIconLocation "imageres.dll", -nIconID
hr = pLink.QueryInterface(IID_IPropertyStore, pStore)
If sTitle <> "" Then
Dim vTitle As Variant
vTitle = CVar(sTitle)
pStore.SetValue PKEY_Title, vTitle
pStore.Commit
Else
pStore.SetValue PKEY_AppUserModel_IsDestListSeparator, CVar(True)
pStore.Commit
End If
pList.AddObject ByVal ObjPtr(pLink)
End Sub
Public Sub AddUserTasks(pList As IObjectCollection)
pCDL.AddUserTasks ByVal ObjPtr(pList)
End Sub
Public Sub AppendCategory(sCat As String, pList As IObjectCollection)
pCDL.AppendCategory ByVal StrPtr(sCat), ByVal ObjPtr(pList)
End Sub
Public Sub AbortList()
pCDL.AbortList
End Sub
Public Sub CommitList()
pCDL.CommitList
End Sub
'-------------------
'You can remove the following if you have mIID.bas in your project
Private Sub DEFINE_UUID(Name As UUID, L As Long, w1 As Integer, w2 As Integer, B0 As Byte, b1 As Byte, b2 As Byte, B3 As Byte, b4 As Byte, b5 As Byte, b6 As Byte, b7 As Byte)
With Name
.Data1 = L
.Data2 = w1
.Data3 = w2
.Data4(0) = B0
.Data4(1) = b1
.Data4(2) = b2
.Data4(3) = B3
.Data4(4) = b4
.Data4(5) = b5
.Data4(6) = b6
.Data4(7) = b7
End With
End Sub
Private Function IID_IObjectArray() As UUID
Static iid As UUID
If (iid.Data1 = 0) Then Call DEFINE_UUID(iid, &H92CA9DCD, CInt(&H5622), CInt(&H4BBA), &HA8, &H5, &H5E, &H9F, &H54, &H1B, &HD8, &HC9)
IID_IObjectArray = iid
End Function
Private Function IID_IPropertyStore() As UUID
Static iid As UUID
If (iid.Data1 = 0) Then Call DEFINE_UUID(iid, &H886D8EEB, CInt(&H8CF2), CInt(&H4446), &H8D, &H2, &HCD, &HBA, &H1D, &HBD, &HCF, &H99)
IID_IPropertyStore = iid
End Function
'End of mIID.bas section
'-----------------------------
'-----------------------------
'You can remove the following if you have mPKEY.bas in your project
Private Sub DEFINE_PROPERTYKEY(Name As PROPERTYKEY, L As Long, w1 As Integer, w2 As Integer, B0 As Byte, b1 As Byte, b2 As Byte, B3 As Byte, b4 As Byte, b5 As Byte, b6 As Byte, b7 As Byte, pid As Long)
With Name.fmtid
.Data1 = L
.Data2 = w1
.Data3 = w2
.Data4(0) = B0
.Data4(1) = b1
.Data4(2) = b2
.Data4(3) = B3
.Data4(4) = b4
.Data4(5) = b5
.Data4(6) = b6
.Data4(7) = b7
End With
Name.pid = pid
End Sub
Private Function PKEY_Title() As PROPERTYKEY
Static pkk As PROPERTYKEY
If (pkk.fmtid.Data1 = 0&) Then Call DEFINE_PROPERTYKEY(pkk, &HF29F85E0, &H4FF9, &H1068, &HAB, &H91, &H8, &H0, &H2B, &H27, &HB3, &HD9, 2)
PKEY_Title = pkk
End Function
Private Function PKEY_AppUserModel_IsDestListSeparator() As PROPERTYKEY
Static pkk As PROPERTYKEY
If (pkk.fmtid.Data1 = 0&) Then Call DEFINE_PROPERTYKEY(pkk, &H9F4C2855, &H9F79, &H4B39, &HA8, &HD0, &HE1, &HD4, &H2D, &HE1, &HD5, &HF3, 6)
PKEY_AppUserModel_IsDestListSeparator = pkk
End Function
'End of mPKEY.bas section
'---------------------------
I've included the relevant sections of mIID and mPKEY in the class so those are not required.
Using it is pretty simple, again this is pretty much just a conversion of Olaf's version:
Code:
Public cJL As New cJump
Private Sub CreateJumpList(sID As String)
cJL.InitList sID
Dim MyCat As New EnumerableObjectCollection
cJL.AddToList MyCat, "Item 1", "/cat /arg1", "ToolTip 1", 14
cJL.AddToList MyCat, "Item 2", "/cat /arg2", "ToolTip 2", 23
cJL.AppendCategory "Custom Category", MyCat
Dim Tasks As New EnumerableObjectCollection
cJL.AddToList Tasks, "Task 1", "/task /arg1", "ToolTip Task 1", 47
cJL.AddToList Tasks, "Task 2", "/task /arg2", "ToolTip Task 2", 78
cJL.AddToList Tasks 'Leave blank for separator
cJL.AddToList Tasks, "Task 3", "/task /arg3", "ToolTip Task 3", 86
cJL.AddUserTasks Tasks
cJL.CommitList
End Sub
The class uses imageres.dll for the icons, the trailing numbers above are icon indexes within it, but you can change that to anything else with the .SetIconLocation call in AddToList.
ITaskbarList
For anyone curious, yes you can use both ICustomDestinationList (this project) with ITaskbarList3/4 (overlay icons, progress, change thumbnail area, and add buttons -- see this demo project). The buttons/changed thumbnail of the latter only appear when the taskbar icon is hovered over, whereas the jump list only appears upon a right-click; so none of the features in either project conflict with any others and can be present in the same taskbar item.
Download cJump class and demo project:
Last edited by fafalone; Sep 13th, 2019 at 06:54 PM.
Reason: NOT STRING