通过COM接口选中桌面图标

背景:
前段时间开发时遇到一个文件生成到桌面,然后选中桌面图标的功能要求。大家知道选中文件夹下的文件,可以使用 SHOpenFolderAndSelectItems 函数,当时就想,选中桌面图标,应该也是提供了对应的API。结果网上搜了一圈,并没发现对应的函数。
于是我的目光很快转移到 IShellView 这个COM接口上,去MSDN文档一看,果然有SelectItem这个函数。根据经验,八九不离十 就是它了。
但额外的问题来了,如果才能拿到属于桌面的 IShellView 接口呢?兜兜转转,在网上找了一些资料总算把代码整理出来,虽然我也不知道 QueryDesktopShellView 为什么要这样做,但它确实取到了桌面的 IShellView 接口。这正是COM技术的恶心之处,它看起来更规范,而且不需要你理解函数内部实现,但正因如此,很多人都被COM技术弄的云里雾里。
吐槽完了,这种网上找不到现成例子,实现起来也弄不清楚逻辑的代码,我还是记录一下,免得以后忘记了。

实现代码:


#include <iostream>
#include <ShObjIdl_core.h>
#include <comutil.h>
#include <exdisp.h>
#include <winerror.h>
#include <atlcomcli.h>
#include <shlguid.h>
#include <shtypes.h>
#include <vector>
#include <ShlObj_core.h>

//反正这个函数就能取到桌面的 IShellView
bool QueryDesktopShellView(IShellView** pShellView)
{
	long hWnd = 0;
	_variant_t VarLock;
	CComPtr<IShellWindows> pShellWindows;
	CComQIPtr<IWebBrowserApp> pWebBrowserApp;
	CComQIPtr<IServiceProvider> pServiceProvider;
	CComPtr<IShellBrowser> pShellBrowser;

	if (NULL == pShellView)
	{
		return false;
	}

	if (FAILED(pShellWindows.CoCreateInstance(CLSID_ShellWindows)))
	{
		return false;
	}

	if (!pShellWindows || FAILED(pShellWindows->FindWindowSW(&VarLock, &VarLock, SWC_DESKTOP,
		&hWnd, SWFO_NEEDDISPATCH, reinterpret_cast<IDispatch**>(&pWebBrowserApp))))
	{
		return false;
	}

	if (!pWebBrowserApp || FAILED(pWebBrowserApp->QueryInterface(IID_IServiceProvider,
		reinterpret_cast<void**>(&pServiceProvider))))
	{
		return false;
	}

	if (!pServiceProvider || FAILED(pServiceProvider->QueryService(SID_STopLevelBrowser, IID_IShellBrowser,
		reinterpret_cast<LPVOID*>(&pShellBrowser))))
	{
		return false;
	}

	if (!pShellBrowser || FAILED(pShellBrowser->QueryActiveShellView(pShellView)))
	{
		return false;
	}

	return true;
}

int main()
{
	LPITEMIDLIST pidl = NULL;
	std::vector<LPITEMIDLIST> vecItem;
	IShellView *pShellView = NULL;
	HRESULT hr = S_OK;
	CoInitialize(NULL);

	if (false == QueryDesktopShellView(&pShellView))
	{
		return false;
	}

	pidl = ::ILCreateFromPathW(L"腾讯QQ.lnk");
	if (pidl)
	{
		vecItem.push_back(pidl);
	}

	pidl = ::ILCreateFromPathW(L"微信.lnk");
	if (pidl)
	{
		vecItem.push_back(pidl);
	}

	pShellView->SelectItem(NULL, SVSI_DESELECTOTHERS);//取消之前的选中
	for (int i = 0; vecItem.size() > i; i++)
	{
		hr |= pShellView->SelectItem(vecItem[i], SVSI_SELECT);
	}

	pShellView->UIActivate(SVUIA_ACTIVATE_FOCUS);//设置桌面焦点

	for (int i = 0; vecItem.size() > i; i++)
	{
		::ILFree(vecItem[i]);
	}
	pShellView->Release();

	CoUninitialize();
	return 0;
}

代码刚写完的时候,调用了SelectItem函数,桌面却没有任何反应,搞的我有点怀疑人生,第二天回来的时候,我突然醒悟,肯定是桌面没有焦点的原因!于是再上MSDN看了一下文档,加了一句

pShellView->UIActivate(SVUIA_ACTIVATE_FOCUS); 

OK, 图标被选中了。

缺陷:
代码写完后测试了几次。测试的代码流程大概是:生成文件到桌面路径,则调用选中桌面图标函数。
但偶尔会出现没选中的情况,我捋了几次项目代码逻辑,没有问题啊。
最后推测原因如下:

  1. 文件生成到桌面目录后,桌面进程收到目录变化的通知,会重新加载桌面
  2. SelectItem 肯定是使用进程间通信通知桌面选中图标的
  3. 由于目录变更通知和我的SelectItem 函数,均存在于不同的进程,那他们内部线程执行的顺序也是不确定的, 如果发送目录变更通知的线程响应不够快,则 explorer.exe 可能先收到 SelectItem 通知,再收到目录变更通知,就会出现没选中的情况。

简单的解决办法是延时一下 SelectItem 的执行,不过我这边不纠结这种问题,所以并没进行额外处理。

另外桌面图标展示的是私有文件夹和公共文件夹,这两个文件夹里面的图标,所以桌面是会有同名文件的。若必须要求准确命中对应的Item, 估计要利用 GetItemObject 函数(不用知道Item数量,直接 i++ 枚举就行了)来查看Item 信息再进行选中。不过我这边暂时没打算做的这么细致,所以还是留给大家研究一下怎么实现吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值