目录
1 概述
本文基于Qt利用Windows的Shell编程实现SendTo菜单。
2 定义
Windows Shell编程需要引用头文件:
#include <Shlobj.h>
#include <shlwapi.h>
#include <windows.h>
这里定义三个类SendToMenu和ShellMenuItem以及实现辅助类ContextMenuHelper.
2.1 SendToMenu
该类定义了SendTo菜单调用接口,定义如下:
class SendToMenu
{
public:
static ShellMenuItems menuItems();
};
该类包括一个接口:
- menuItems返回SendTo菜单项
2.2 ShellMenuItem
该类包含菜单项标题和图标,用来创建菜单项。
class ShellMenuItem
{
public:
QString caption;
QIcon icon;
void exec(QStringList const& fileNames) const;
bool operator < (ShellMenuItem const& r) { return caption < r.caption; }
};
typedef QList<ShellMenuItem> ShellMenuItems;
接口说明:
- exec将文件发送到对应菜单项上。
- 重载小于号操作符用于按caption排序.
2.3 ContextMenuHelper
该类实现具体功能,定义如下:
struct ContextMenuHelper
{
static ContextMenuHelper* Instatnce();//获取单例对象
~ContextMenuHelper();
ShellMenuItems sendToMenuItems();
void execSendToCmd(QStringList const& fileNames, QString const& caption);
private:
ContextMenuHelper();
bool open(QStringList const& fileNames, bool isOpenDataObject = true);
void close();
LPSHELLFOLDER getSpecialFolder(int idFolder);
QString strToString(LPITEMIDLIST pidl, STRRET *str);
QString toWindowsPath(QString const& linuxPath);
QIcon getICon(QString const& fileName);
void doDrop(LPDATAOBJECT pdto, LPDROPTARGET pdt);
IShellFolder* pDesktop = nullptr;
IDataObject* pDataObject = nullptr;
std::vector<LPITEMIDLIST> pidls;
};
接口说明:
- sendToMenuItems获取菜单项
- execSendToCmd执行菜单命令
3 实现
3.1 SendToMenu
menuItems函数实现:
ShellMenuItems SendToMenu::menuItems()
{
return ContextMenuHelper::Instatnce()->sendToMenuItems();
}
该函数直接调用辅助类的sendToMenuItems函数。
3.2 ShellMenuItem
exec函数实现如下:
void ShellMenuItem::exec(QStringList const& fileNames) const
{
ContextMenuHelper::Instatnce()->execSendToCmd(fileNames, caption);
}
该函数直接调用辅助类的execSendToCmd函数。
3.3 ContextMenuHelper
该类型函数接口比较多,下面一一介绍其实现。
3.3.1 sendToMenuItems
函数实现如下:
ShellMenuItems ContextMenuHelper::sendToMenuItems()
{
ShellMenuItems items;
LPSHELLFOLDER psf = getSpecialFolder(CSIDL_SENDTO);
if(psf)
{
LPENUMIDLIST pEnum;
HRESULT hr = psf->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnum);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl;
STRRET str;
while(pEnum->Next(1, &pidl, 0) == S_OK)
{
ShellMenuItem item;
psf->GetDisplayNameOf(pidl, SHGDN_NORMAL, &str);
item.caption = strToString(pidl, &str);
psf->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str);
item.icon = getICon(strToString(pidl, &str));
items << item;
CoTaskMemFree(pidl);
}
pEnum->Release();
}
psf->Release();
}
std::sort(items.begin(), items.end());
return items;
}
函数流程:
- 获取SendTo Shell文件夹对象psf
- 在Shell文件夹对象psf上遍历子对象构造ShellMenuItem,并放入ShellMenuItems对象items中。
- 对items按caption排序。
3.3.2 execSendToCmd
函数实现如下:
void ContextMenuHelper::execSendToCmd(QStringList const& fileNames, QString const& caption)
{
if(!open(fileNames))
return;
LPSHELLFOLDER psf = getSpecialFolder(CSIDL_SENDTO);
if(!psf)
return;
LPENUMIDLIST peidl;
HRESULT hr = psf->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &peidl);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl = 0;
STRRET str;
while(peidl->Next(1, &pidl, 0) == S_OK)
{
psf->GetDisplayNameOf(pidl, SHGDN_NORMAL, &str);
if(strToString(pidl, &str) == caption)
break;
CoTaskMemFree(pidl);
pidl = 0;
}
if(pidl)
{
LPDROPTARGET pdt;
hr = psf->GetUIObjectOf(0, 1, (PCUITEMID_CHILD_ARRAY)&pidl,
IID_IDropTarget, 0, (LPVOID *)&pdt);
if (SUCCEEDED(hr))
{
doDrop(pDataObject, pdt);
pdt->Release();
}
}
}
close();
}
函数流程:
- 打开文件fileNames到数据对象pDataObject
- 获取SendTo Shell文件夹对象psf
- 在Shell文件夹对象psf上遍历找到名称为caption的菜单项
- 获取菜单项的DropTarget对象pdt。
- 将数据对象pDataObject drop到pdt上实现将文件发送到对应项上。
- 关闭文件。
3.3.3 getSpecialFolder
获取指定文件对应的LPSHELLFOLDER对象,在本文使用的是CSIDL_SENDTO(SendTo对象)。
LPSHELLFOLDER ContextMenuHelper::getSpecialFolder(int idFolder)
{
LPITEMIDLIST pidl;
LPSHELLFOLDER psf = NULL;
HRESULT hr = SHGetSpecialFolderLocation(0, idFolder, &pidl);
if (SUCCEEDED(hr))
{
pDesktop->BindToObject(pidl, NULL, IID_IShellFolder, (LPVOID *)&psf);
CoTaskMemFree(pidl);
}
return psf;
}
3.3.4 open
实现如下:
bool ContextMenuHelper::open(QStringList const& fileNames, bool isOpenDataObject = true)
{
HRESULT hr;
pidls.resize(fileNames.size());
for(int i = 0; i < fileNames.size(); i++)
{
QString fileName = toWindowsPath(fileNames[i]);
hr = pDesktop->ParseDisplayName(0, 0, (LPWSTR)(fileName.toStdWString().c_str()),
0, (LPITEMIDLIST*)&pidls[i], 0);
if(FAILED(hr))
return false;
}
if(isOpenDataObject)
{
hr = pDesktop->GetUIObjectOf(0, fileNames.size(), (PCUITEMID_CHILD_ARRAY)(&pidls[0]),
IID_IDataObject, 0, (void **)&pDataObject);
if(FAILED(hr))
return false;
}
return true;
}
函数流程:
- 获取每个文件的LPITEMIDLIST对象放入pidls中。
- 通过pidls打开数据对象pDataObject
3.3.5 close
实现如下:
void close()
{
if(pDataObject)
{
pDataObject->Release();
pDataObject = 0;
}
if(!pidls.empty())
{
for(size_t i = 0; i < pidls.size(); i++)
ILFree(pidls[i]);
pidls.clear();
}
}
函数流程:
- 释放数据对象pDataObject
- 释放每个LPITEMIDLIST对象pidl
3.3.6 其它函数
ContextMenuHelper::ContextMenuHelper* Instatnce()
{
static ContextMenuHelper helper;
return &helper;
}
ContextMenuHelper::ContextMenuHelper()
{
SHGetDesktopFolder(&pDesktop);
}
ContextMenuHelper::~ContextMenuHelper()
{
close();
if(pDesktop)
pDesktop->Release();
}
QString ContextMenuHelper::strToString(LPITEMIDLIST pidl, STRRET *str)
{
LPTSTR pszText;
QString text;
HRESULT hr = StrRetToStr(str, pidl, &pszText);
if (SUCCEEDED(hr))
{
text = QString::fromStdWString(pszText);
CoTaskMemFree(pszText);
}
return text;
}
QString ContextMenuHelper::toWindowsPath(QString const& linuxPath)
{
QString windowsPath = linuxPath;
windowsPath.replace("/", "\\");
return windowsPath;
}
QIcon ContextMenuHelper::getICon(QString const& fileName)
{
QFileInfo fileInfo(fileName.toLower());
return QFileIconProvider().icon(fileInfo);
}
void ContextMenuHelper::doDrop(LPDATAOBJECT pdto, LPDROPTARGET pdt)
{
POINTL pt = { 0, 0 };
DWORD dwEffect = DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK;
HRESULT hr = pdt->DragEnter(pdto, MK_LBUTTON, pt, &dwEffect);
if (SUCCEEDED(hr) && dwEffect)
pdt->Drop(pdto, MK_LBUTTON, pt, &dwEffect);
else
pdt->DragLeave();
}
4 使用
从QWidget派生一个类型Widget.
4.1 Widget 定义
Widget 定义如下:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void customContextMenu(QPoint const& point);
private:
Ui::Widget *ui;
};
4.2 Widget实现
Widget 实现如下:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(customContextMenu(QPoint)));
}
Widget::~Widget()
{
delete ui;
}
void Widget::customContextMenu(QPoint const&)
{
QMenu menu;
ShellMenuItems items =SendToMenu::menuItems();
foreach(auto item, items)
{
menu.addAction(item.icon, item.caption, this, [=](){
item.exec(QStringList() << "D:/wiki.zip");
});
}
menu.exec(QCursor::pos());
}
如customContextMenu函数中所示根据返回的ShellMenuItems创建菜单,单击右键显示显示SendTo菜单。