Contents
Preparation
Working With Jump Lists
As of Windows 7 the taskbar has become more efficient, and has a lot of new features. Such as Jump List, Thumbnails, Thumbnail toolbar, Overlay Icons, Progress bar and other things.
This article focuses on how to manage/customize a Jump List using Qt with GCC.
A Jump List is like a customized Start menu for your application. It can contain Destinations and Tasks. A Destination can be a link to a file, location, URL. A Task can be viewed as an action or command which the application can perform.
It contains two built-in Destinations categories, Recent and Frequent. These lists are managed automatically.
By default a Jump List contains a Recent category. Each time a file is opened with the application, or file-based applications through SHAddToRecentDocs, or the common file dialog, it will be added automatically to Recent and Frequent.
Preparation
1) Register your application to handle a file type
To enable the Recent category it is enough to register a file type with your application, and it is not necessary to be the default file-type handler.
Below is a simple example of registry keys and values needed to register a .rgm file extension to C:\Path\to\bin\app.exe application.
It is possible to copy this into a .reg file and import it.
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.rgm]
@="rgm_auto_file"
[HKEY_CLASSES_ROOT\rgm_auto_file]
@="RGM file"
[HKEY_CLASSES_ROOT\rgm_auto_file\DefaultIcon]
@="C:\\Path\\to\\bin\\app.exe,1"
[HKEY_CLASSES_ROOT\rgm_auto_file\shell]
[HKEY_CLASSES_ROOT\rgm_auto_file\shell\Open]
[HKEY_CLASSES_ROOT\rgm_auto_file\shell\Open\Command]
@="C:\\Path\\to\\bin\\app.exe '%1'"
2) Find and declare missing declarations of the needed COM interfaces, their CLSID and IID, and “friends” (enums, typedefs).
In order to be able to manage the Jump List from your application it is necessary to have access to different COM interfaces. Not all of them are accessible within MINGW with GCC.
This can be done by finding their definitions in Windows 7 SDK header files.
For instance:
ICustomDestinationList is declared in c:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include\ShObjIdl.h:
EXTERN_C const CLSID CLSID_DestinationList;
#ifdef __cplusplus
class DECLSPEC_UUID("77f10cf0-3db5-4966-b520-b7c54fd35ed6")
DestinationList;
#endif
MIDL_INTERFACE("6332debf-87b5-4670-90c0-5e57b408a49e")
ICustomDestinationList : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE SetAppID(
/* [string][in] */ __RPC__in_string LPCWSTR pszAppID) = 0;
virtual HRESULT STDMETHODCALLTYPE BeginList(
/* [out] */ __RPC__out UINT *pcMinSlots,
/* [in] */ __RPC__in REFIID riid,
/* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;
virtual HRESULT STDMETHODCALLTYPE AppendCategory(
/* [string][in] */ __RPC__in_string LPCWSTR pszCategory,
/* [in] */ __RPC__in_opt IObjectArray *poa) = 0;
virtual HRESULT STDMETHODCALLTYPE AppendKnownCategory(
/* [in] */ KNOWNDESTCATEGORY category) = 0;
virtual HRESULT STDMETHODCALLTYPE AddUserTasks(
/* [in] */ __RPC__in_opt IObjectArray *poa) = 0;
virtual HRESULT STDMETHODCALLTYPE CommitList( void) = 0;
virtual HRESULT STDMETHODCALLTYPE GetRemovedDestinations(
/* [in] */ __RPC__in REFIID riid,
/* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;
virtual HRESULT STDMETHODCALLTYPE DeleteList(
/* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszAppID) = 0;
virtual HRESULT STDMETHODCALLTYPE AbortList( void) = 0;
};
will become:
DEFINE_GUID(CLSID_DestinationList,0x77f10cf0,0x3db5,0x4966,0xb5,0x20,0xb7,0xc5,0x4f,0xd3,0x5e,0xd6);
DEFINE_GUID(IID_ICustomDestinationList,0x6332debf,0x87b5,0x4670,0x90,0xc0,0x5e,0x57,0xb4,0x08,0xa4,0x9e);
DECLARE_INTERFACE_(ICustomDestinationList, IUnknown)
{
STDMETHOD (SetAppID) (LPCWSTR pszAppID);
STDMETHOD (BeginList) (UINT *pcMinSlots, REFIID riid, void **ppv) PURE;
STDMETHOD (AppendCategory) (LPCWSTR pszCategory, IObjectArray *poa) PURE;
STDMETHOD (AppendKnownCategory) (KNOWNDESTCATEGORY category) PURE;
STDMETHOD (AddUserTasks) (IObjectArray *poa) PURE;
STDMETHOD (CommitList) (void) PURE;
STDMETHOD (GetRemovedDestinations) (REFIID riid, void **ppv) PURE;
STDMETHOD (DeleteList) (LPCWSTR pszAppID) PURE;
STDMETHOD (AbortList) (void) PURE;
};
typedef ICustomDestinationList *LPICustomDestinationList;
Here you cand find more.
To perform different jobs on a Jump List you’ll need to declare more interfaces.
Minimum needed to add items to Recent or/and create custom category:
ICustomDestinationList, IObjectArray
To create Tasks or/and to add customized items to Recent/Frequent/Custom list:
ICustomDestinationList, IObjectArray, IShellItem, IShellLink, IPropertyStore
One other helpful hint, each time you encounter the following macro in Windows 7 SDK examples:
IID_PPV_ARGS(&pItem)
you can substitute it with
IID_Of_The_Item’s_Class, reinterpret_cast<void**>(&(pItem))
3) Add to your Qt project file the needed libraries to link with
LIBS += libole32
Working With Jump Lists
How to Add Items to Recent/Frequent list
The simplest way to do this is to call
QString filePath("c:\\file.rgm");
SHAddToRecentDocs(0x00000003, filePath.toStdWString().c_str());
If the file extension of the filePath is not registered to this application, this call will have no effect.
Show/hide Recent or/and Frequent list
- Create the custom jump list object
ICustomDestinationList* destinationList;
CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, IID_ICustomDestinationList, reinterpret_cast<void**> (&(destinationList)));
- Initialize the list
UINT max_count;
IObjectArray* objectArray;
destinationList->BeginList(&max_count, IID_IObjectArray, reinterpret_cast<void**> (&(objectArray)));
This function will start a “transaction”.
Returned values through max_count represents the max allowed recent items to display in Jump List, and objectArray holds the items which have been removed by the user.
- Enable the Frequent list
destinationList->AppendKnownCategory(KDC_FREQUENT);
- Enable the Recent list
destinationList->AppendKnownCategory(KDC_RECENT);
- Commit the menu “transaction”
destinationList->CommitList();
The whole example can be found here :
https://github.com/xfreebird/blogstuff/tree/master/qt/jumplist_example1
For simplicity there are no checks for valid pointers or initialization results.
Creating a Custom Jump List Cateogry
- The first step is to declare the COM interfaces, enums and typedefs for:
ICustomDestinationList, IObjectCollection, IObjectArray, IShellItem, IShellLink, IPropertyStore
All the details can be found here.
- It is needed to find a way to initialize a IShellItem, providing just a valid file path.
I chose to call SHCreateItemFromParsingName. But since it is declared in Shobjidl.h, I can’t include this file with GCC. So I have to load this function from the shell32.dll itself.
Typedef a function pointer type:
extern "C"
{
typedef HRESULT (WINAPI *t_SHCreateItemFromParsingName)(PCWSTR pszPath, IBindCtx *pbc, REFIID riid, void **ppv);
}
then use it:
IShellItem* FilePath2ShellItem(QString path) {
HMODULE shell;
IShellItem *shell_item = NULL;
t_SHCreateItemFromParsingName SHCreateItemFromParsingName = NULL;
shell = LoadLibrary(L"shell32.dll");
if (shell) {
SHCreateItemFromParsingName = reinterpret_cast<t_SHCreateItemFromParsingName>(GetProcAddress (shell, "SHCreateItemFromParsingName"));
if (SHCreateItemFromParsingName != NULL) {
SHCreateItemFromParsingName(path.toStdWString().c_str(), NULL, IID_IShellItem, reinterpret_cast<void**> (&(shell_item)));
}
FreeLibrary (shell);
}
return shell_item;
}
- Init a ICustomDestinationList:
UINT max_count = 0;
IObjectArray* objectArray;
ICustomDestinationList* destinationList;
//create the custom jump list object
CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, IID_ICustomDestinationList, reinterpret_cast<void**> (&(destinationList)));
- Start the “transaction” :
destinationList->BeginList(&max_count, IID_IObjectArray, reinterpret_cast<void**> (&(objectArray)));
IObjectCollection* obj_collection;
CoCreateInstance(CLSID_EnumerableObjectCollection, NULL,
CLSCTX_INPROC, IID_IObjectCollection, reinterpret_cast<void**> (&(obj_collection)));
- Init an object array from obj_collection:
IObjectArray* object_array;
obj_collection->QueryInterface(IID_IObjectArray, reinterpret_cast<void**> (&(object_array)));
- Create and add shell items to this object_array. It is important to know, if the file path is not valid, or the file extension is not registered to this application, SHCreateItemFromParsingName will fail to create a valid IShellItem.
obj_collection->AddObject(FilePath2ShellItem("C:\\file1.rgm"));
obj_collection->AddObject(FilePath2ShellItem("C:\\file2.rgm"));
- Add the object array as a new category in the Jump List:
destinationList->AppendCategory(L“Custom entry”, object_array);
- Commit the changes:
destinationList->CommitList();
- Release the resources:
object_array->Release();
obj_collection->Release();
objectArray->Release();
destinationList->Release();
The code can be found here https://github.com/xfreebird/blogstuff/tree/master/qt/jumplist_example2
Adding Tasks
To populate the Tasks list it is needed to create a collection of IShellLinks and call ICustomDestinationList::AddUserTasks.
As previously this must be done after calling ICustomDestinationList::BeginList,
and applying it by calling ICustomDestinationList::CommiList after.
A task is a shortcut.
To create a task item can be used the code below:
IShellLink* shell_link = NULL;
CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<void**> (&(shell_link)));
Set the values:
shell_link->SetPath(L"C:\\path\\to\\app.exe");
shell_link->SetArguments(L"Some args");
shell_link->SetIconLocation(L"C:\\path\\to\\app.exe", app_index);
shell_link->SetDescription(L"Description");
To set the title we need to use a IPropertyStore object.
PROPVARIANT pv;
IPropertyStore* prop_store = NULL;
shell_link->QueryInterface(IID_IPropertyStore, reinterpret_cast<void**> (&(prop_store)));
InitPropVariantFromString(L"Title", &pv);
prop_store->SetValue(PKEY_Title, pv);
prop_store->Commit();
If we want this item to be a separator:
PROPVARIANT pv;
IPropertyStore* prop_store = NULL;
shell_link->QueryInterface(IID_IPropertyStore, reinterpret_cast<void**> (&(prop_store)));
InitPropVariantFromBoolean(TRUE, &pv);
prop_store->SetValue(PKEY_AppUserModel_IsDestListSeparator, pv);
prop_store->Commit();
The rest is almost similar to the example of creating a custom category.
Instead of:
obj_collection->AddObject(FilePath2ShellItem("C:\\file1.rgm"));
obj_collection->AddObject(FilePath2ShellItem("C:\\file2.rgm"));
destinationList->AppendCategory(L“Custom entry”, object_array);
will be
obj_collection->AddObject(CreateShellLink("Task 1", "Task Description", "C:\\path\\to\\app.exe", "app args", "c:\\path\\to\\ico\\resources", 0));
destinationList->AddUserTasks(object_array);
The sources for the example 3 can be found here https://github.com/xfreebird/blogstuff/tree/master/qt/jumplist_example3
Useful links:
Taskbar Extensions
Hilo: Developing C++ Applications for Windows 7
Chapter 14: Adding Support for Windows 7 Jump Lists & Taskbar Tabs
Windows 7 Taskbar – Part 1, The Basics
Windows 7 SDK Examples
CodeProject article on JumpLists
CodeProject article on JumpLists Tasks
Link to EcWin7, from here I understood how to import COM declarations from Windows SDK headers
2 comments:
THANK
YOU!
Though, I am not interested in jumplists/tasks, I was *struggling* with COM+gcc.
(Trying to make the original SDK headers work)
But it seems reimpl the interface is in the end easier and more elegant!
Thanks, again!
Nice article, seems that it's not necessary anymore: http://qt-project.org/doc/qt-5/qtwinextras-overview.html
Post a Comment