C++ 中的 Windows 7 实用程序:Jump Lists






4.90/5 (39投票s)
介绍如何将 Jump Lists 与您的 Windows 7 应用程序一起使用。
目录
引言
Windows 7 为 Explorer(尤其是任务栏)添加了许多很棒的增强功能。其中一个显著的新功能是 **跳转列表**。这是一个类似于上下文菜单的用户界面元素,与应用程序关联,通过右键单击应用程序的任务栏按钮来访问。本文介绍了跳转列表,展示了如何从 C++ 访问跳转列表,并演示了一些您可以对应用程序跳转列表进行的简单自定义。
示例代码使用 Visual Studio 2008、WTL 8.0 和 beta Windows 7 SDK 构建。与我之前的 Vista 增强功能文章一样,我将本文归类为中级,因为我不会涵盖 Win32 和 WTL 的基础知识。如果您需要快速了解 WTL,请参阅我关于 WTL 的系列文章。我也不会逐步介绍 Visual Studio 向导,因为这些向导在 WTL 系列中也已涵盖。(那些文章显示的是 VC 2003 中的向导,但 2008 中的向导类似。)
首先要强调的一些重要事项
- “跳转列表”是一个相对较新的术语。在开发过程中,Microsoft 使用了“目标列表”(destination list)这个术语,该术语出现在头文件中的各种位置(例如
ICustomDestinationList
接口)。 - 为了使应用程序的跳转列表正常工作,它必须是文件类型的注册处理程序。这不一定意味着它必须是*默认*处理程序,只是*一个*处理程序。稍后我们将看到如何注册为文件类型处理程序。
- 注册为文件类型可以在
HKEY_CLASSES_ROOT
(用于整个计算机的关联)或HKEY_CURRENT_USER\Software\Classes
(用于每个用户的关联)中进行。为简洁起见,本文将引用HKEY_CLASSES_ROOT
,但可下载的示例代码使用了HKEY_CURRENT_USER
。这是因为写入HKEY_CLASSES_ROOT
需要提升权限,而我希望示例程序无需提升权限即可运行。
什么是跳转列表?
如上所述,通过右键单击任务栏上的应用程序按钮可以调出该应用程序的跳转列表。如果应用程序存在于“开始”菜单的应用程序列表中(无论是通过固定还是最近使用),也可以通过“开始”菜单项访问跳转列表。
跳转列表包含两类项目:**目标**(destinations)和**任务**(tasks)。目标是应用程序可以操作的项目(通常是文件)。这类似于某些应用程序自行维护的 MRU(最近使用)文件列表。但是,对于目标,应用程序和 Windows 可以协同工作来管理一个中心化的文件列表。此外,应用程序不必做任何事情即可获得目标;Windows 会尝试自行找出应用程序正在操作的文件,并利用这些信息来构建跳转列表。如果应用程序愿意,它也可以将文件添加到此列表中。
任务是命令,例如“创建新文档”或“播放 MP3 文件专辑”。任务由 IShellLink
的实例表示,因此任务必须是可以通过命令行调用的操作。编写应用程序时,您将提前规划好命令行的参数以及您希望添加到应用程序跳转列表中的操作。由于任务本质上是应用程序特定的,Windows 不提供任何默认任务。跳转列表的底部包含一些用于管理窗口和将应用程序固定/取消固定的命令,但这些不被视为任务。它们只是窗口管理控件。
旧应用程序的默认跳转列表
让我们从了解 Windows 7 如何处理不了解跳转列表的旧应用程序开始。第一个示例程序是一个与 .aly
扩展名关联的基本文件查看器。该应用程序 NaiveAlyViewer 实际上并不处理文件,它只是演示了 Windows 确定哪些文件应该出现在应用程序跳转列表中的各种方法。
自 Windows 95 以来,Windows 就有一个用于填充“开始”菜单中“最近使用的文档”列表的算法。该算法基于 SHAddToRecentDocs()
API。在三种情况下会调用 SHAddToRecentDocs()
:
- 应用程序使用通用文件打开对话框(
GetOpenFileName()
或 IFileOpenDialog)。当用户使用这些对话框选择文件时,对话框会调用SHAddToRecentDocs()
。 - 用户在 Explorer 中双击文件,Explorer 运行与文件扩展名关联的应用程序,并为您调用
SHAddToRecentDocs()
。 - 应用程序本身调用
SHAddToRecentDocs()
。这通常发生在应用程序以 Explorer 无法自动检测到的方式访问文件时。
对于像这个一样朴素的应用程序,Windows 7 使用相同的算法来确定应用程序正在操作的文件。跳转列表的不同之处在于,“最近使用的文档”在一个列表中显示所有最近使用的文件,而跳转列表只显示与特定应用程序关联的文件。
要查看此查看器的工作效果,首先单击*注册为处理程序*以将应用程序与 .aly
扩展名关联。您必须在任何跳转列表功能生效之前执行此操作。应用程序的跳转列表最初是空的。
要将文件添加到跳转列表,请打开任何扩展名为 .aly
的文件,然后文件的完整路径将出现在对话框中。作为注册过程的一部分,该应用程序会在您的*我的文档*目录中创建几个 .aly
文件供您测试。您也可以创建自己的测试文件——只需创建一个扩展名为 .aly
的文件(文件的内容无关紧要)。打开文件后,右键单击任务栏按钮,该文件将出现在跳转列表的*最近使用*类别中。
您还可以尝试其他打开文件的方式,例如在 Explorer 中双击文件,或将文件拖到 NaiveAlyViewer 窗口中。Explorer 自动为跳转列表项目提供工具提示和上下文菜单,以及固定、取消固定和删除它们的功能。当您单击跳转列表中的文件时,Explorer 使用文件关联信息构建命令行。通常,这将运行应用程序并将文件的完整路径传递到命令行。
使用基本跳转列表功能
选择并使用 AppID
既然我们已经了解了跳转列表如何适用于旧程序,现在让我们开始在代码中添加跳转列表的知识。本节的示例应用程序也是 .aly
文件的查看器,但它可以以一些基本方式更改其跳转列表。
跳转列表中使用的一个重要概念是**应用程序用户模型 ID**。它缩写为“AppID”,但它*不*与 COM 中使用的 AppID 相关。AppID 是新的任务栏用于组织任务栏按钮的目的来标识进程和窗口的方式。
如果您不为应用程序分配 AppID——就像朴素查看器那样——那么 Windows 会为您创建一个 AppID。这有一个缺点,即任务栏会单独处理查看器的不同副本。例如,如果您构建了查看器的调试版本和发布版本,这两个可执行文件将不会共享一个任务栏按钮,并且将具有单独的跳转列表。分配 AppID 可以解决此问题。
我们的第一步是选择一个 AppID。MSDN 建议使用“Company.Product.SubProduct.Version”形式的字符串。这类似于 ProgID 的创建方式,事实上,我们也需要一个 ProgID,所以让我们现在选择一个 AppID 和 ProgID。
AppID:MDunn.CodeProjectSamples.SimpleAlyViewer
ProgID:MDunn.CodeProjectSamples.SimpleAlyViewerProgID
我们不会使用多个版本的应用程序,所以可以省略版本字段。现在我们有了这些 ID,我们需要告诉 Windows 三件事:
- 应用程序的 AppID
.aly
文件关联的 ProgID- 如何从 ProgID 找到 AppID
第一部分可以通过几种方式完成,但最简单的方法是像这样调用 SetCurrentProcessExplicitAppUserModelID()
:
LPCWSTR wszAppID = L"MDunn.CodeProjectSamples.SimpleAlyViewer"; HRESULT hr = SetCurrentProcessExplicitAppUserModelID ( wszAppID );
这必须在应用程序的初始化代码中完成,在其创建任何窗口或操作任何跳转列表之前。
下一步是将我们的应用程序的 ProgID 添加到 .aly
文件关联信息中。我们通过在 HKEY_CLASSES_ROOT\.aly
下添加一个名为 OpenWithProgIDs
的键,并设置一个名为我们的 ProgID 的字符串值来完成此操作。然后,我们像 COM 中一样,在 HKEY_CLASSES_ROOT
下为我们的 ProgID 创建一个键。由于我们的查看器也是 .aly
文件的默认处理程序,ProgID 键中的信息将决定如果您双击 .aly
文件时会发生什么(如前一个示例所示),以及跳转列表中应该显示什么。当应用程序*不是*文件类型的默认处理程序时,情况会变得更有趣;我们将在下一节中看到这种情况的一个示例。
ProgID 键中的最后一部分信息是 AppUserModelID
值。这告诉 Explorer 处理 .aly
文件的应用程序的 AppID。一旦 Explorer 知道了处理程序的 AppID,它就可以正确地管理任务栏按钮并更新其与跳转列表相关的内部数据结构。
我们在调用 SHAddToRecentDocs()
时指定的另一个重要地方是我们的 AppID。我们不只是传递文件路径,而是使用为 Windows 7 添加到 SHAddToRecentDocs()
的一些新参数。我们填充一个 SHARDAPPIDINFO
结构,该结构保存我们的 AppID 和文件上的 IShellItem
接口。创建 IShellItem
很容易:新的 SHCreateItemFromParsingName()
函数接受文件路径并返回 IShellItem
。
LPCWSTR szFilePath = /* full path to the file */; HRESULT hr; SHARDAPPIDINFO info; CComPtr<IShellItem> pItem; hr = SHCreateItemFromParsingName ( szFilePath, NULL, IID_PPV_ARGS(&pItem) ); if ( SUCCEEDED(hr) ) { info.psi = pItem; info.pszAppID = g_wszAppID; // our AppID - see above SHAddToRecentDocs ( SHARD_APPIDINFO, &info ); }
清除文件列表
现在我们的 AppID 已设置好,我们可以对我们的跳转列表执行有用的操作了。最简单的操作是清除文件列表,只留下窗口管理命令。我们使用 IApplicationDestinations
接口来操作跳转列表的内置功能。
HRESULT hr; CComPtr<IApplicationDestinations> pDests; hr = pDests.CoCreateInstance ( CLSID_ApplicationDestinations, NULL, CLSCTX_INPROC_SERVER ); if ( SUCCEEDED(hr) ) { hr = pDests->SetAppID ( g_wszAppID ); if ( SUCCEEDED(hr) ) pDests->RemoveAllDestinations(); }
我们调用的第一个方法是 SetAppID()
,以指示我们要操作哪个跳转列表。这必须在调用任何其他方法之前调用。然后我们调用 RemoveAllDestinations()
来清除最近使用过的文件列表。就是这样!
在“最近使用的”和“频繁使用的”类别之间切换
我们可以进行的另一个修改是更改跳转列表以显示频繁使用的文件。跳转列表中有两个*已知类别*:“最近使用的”(Recent)和“频繁使用的”(Frequent)。默认情况下,跳转列表显示*最近使用的*类别,但我们可以通过创建新的跳转列表来更改它。
所有跳转列表的修改都遵循此通用模式:
- 创建一个
ICustomDestinationList
接口。 - 调用
SetAppID()
,就像我们使用IApplicationDestinations
时一样。 - 调用
BeginList()
,它会创建一个新的跳转列表供我们修改。 - 修改跳转列表。
- 调用
CommitList()
来保存跳转列表。
请注意,跳转列表永远不会就地修改;您总是创建一个全新的跳转列表,然后通过调用 CommitList()
来告诉 Explorer 使用新列表。现在这对我们来说不是什么大问题,因为我们只使用内置的跳转列表功能,但当您向跳转列表添加自己的自定义项目时,需要记住这一点。如果您的应用程序维护任何影响跳转列表内容的状态,则必须将该状态存储在持久位置,以便在构建新跳转列表时可以读取。
SimpleAlyViewer 对话框有两个按钮,用于更改跳转列表中显示的类别。两个按钮的处理程序都调用辅助函数 ShowCategory()
并传入类别 ID:KDC_FREQUENT
或 KDC_RECENT
。
bool CMainDlg::ShowCategory ( KNOWNDESTCATEGORY category ) { HRESULT hr; CComPtr<ICustomDestinationList> pDestList; hr = pDestList.CoCreateInstance ( CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER ); if ( FAILED(hr) ) return false; hr = pDestList->SetAppID ( g_wszAppID ); if ( FAILED(hr) ) return false; UINT cMaxSlots; CComPtr<IObjectArray> pRemovedItems; hr = pDestList->BeginList ( &cMaxSlots, IID_PPV_ARGS(&pRemovedItems) ); if ( FAILED(hr) ) return false; hr = pDestList->AppendKnownCategory ( category ); if ( FAILED(hr) ) return false; return SUCCEEDED( pDestList->CommitList() ); }
与之前一样,我们创建必要的 COM 对象,查询 ICustomDestinationList
,并调用 SetAppID()
。我们调用 BeginList()
来创建一个新的跳转列表。BeginList()
有输出参数,指示跳转列表的最大大小,以及用户已从列表中删除的自定义项目数组。我们不必担心这些参数,因为我们没有将任何自定义项目添加到列表中。然后我们调用 AppendKnownCategory()
来将适当的类别添加到列表中,并使用 CommitList()
保存。
将跳转列表与非默认查看器一起使用
我们的最后一个示例将有所不同,它是一个图形查看器。
这次的不同之处在于该应用程序没有自己的文件类型;相反,它注册为现有文件类型的处理程序。(注册和取消注册的命令在*跳转列表*菜单中。)以下是此应用程序的 AppID 和 ProgID:
LPCWSTR g_wszAppID = L"MDunn.CodeProjectSamples.JumpListGraphicsViewer"; LPCWSTR g_wszProgID = L"MDunn.CodeProjectSamples.JumpListGraphicsViewerProgID";
我们的查看器将处理 BMP、JPG、PNG、GIF 和 TIFF 文件,方法是在每个扩展名的 OpenWithProgIDs
键下进行注册。例如,在 HKCR\.jpg\OpenWithProgIDs
键下,我们创建一个名为我们的 ProgID 的字符串值。
在我们的 ProgID 键下,我们添加两个值:FriendlyTypeName
和 AppUserModelID
。FriendlyTypeName
用作在跳转列表中调出文件属性对话框时显示的文件类型描述。FriendlyTypeName
中的字符串会覆盖您在 Explorer 中看到的描述,如下所示。第一个截图是从 Explorer 调出的属性页,第二个是从跳转列表项调出的属性页。请注意,*文件类型*字符串是不同的。(图标也不同;这是由下面描述的 DefaultIcon
键控制的。)
AppUserModelID
的工作方式与上一示例应用程序中描述的一样。还有一些其他注册表项可以完成我们的文件类型关联:
- 一个
DefaultIcon
键,指定要在我们的应用程序的跳转列表中使用的图标。 - 一个
CurVer
键,其中包含我们 ProgID 的当前版本。与之前一样,我们不使用版本控制,因此在 ProgID 中省略了版本字段。 - 一个
shell\open\command
键,其中包含用户从我们的跳转列表中选择文件时要使用的命令行。
有了这些注册表项,我们的查看器的跳转列表就可以按预期工作了,并且它还出现在我们注册的所有文件类型的*打开方式*子菜单中。
作为额外的细节,查看器演示了如何识别它正在通过*打开方式*菜单或跳转列表项运行。写入 command
键的命令行是 JumpListGfxViewer.exe /v "%1"
(`/v` 表示“查看器模式”)。当应用程序看到 `/v` 开关和文件路径时,它会在窗口中添加一个“查看器模式”字符串。
跳转列表问题
注意:本节中的信息基于 Windows 7 的 beta 版本(build 7000)。后续版本中的行为可能有所不同。
- 固定一个应用程序有时会导致添加第二个任务栏按钮。在开发示例应用程序的过程中,我遇到过这种情况,其中一个应用程序发生了,但其他应用程序没有。Windows 7 beta SDK 中的示例也显示了此问题。
- 如果您多次将已知类别添加到跳转列表中,该跳转列表将在底部显示一些空白区域。
修订历史
2009 年 5 月 19 日:文章首次发布