目录
1 概述
本文基于Qt利用Windows的Shell编程实现Windows桌面菜单。
2 定义
Windows Shell编程需要引用头文件:
#include <Shlobj.h>
#include <shlwapi.h>
#include <windows.h>
这里定义两个类DesktopMenu和ShellMenuItem
2.1 DesktopMenu
该类定义了桌面菜单调用接口,定义如下:
class DesktopMenu
{
public:
static ShellMenuItems computerShellItems();
static ShellMenuItems desktopMenuItems();
static void shellSubMenuItems(ShellMenuItem const& item,
ShellMenuItems & menuItems,
bool isOnlyDir = true);
};
该类包括三个接口:
- computerShellItems返回我的电脑菜单项
- desktopMenuItems返回桌面菜单项
- shellSubMenuItems返回子菜单项
2.2 ShellMenuItem
该类包含菜单项标题,图标以及文件路径,用来创建菜单项。
class ShellMenuItem
{
public:
QString caption;
QString filePath;
QIcon icon;
ShellItem::Ptr pItem;
bool operator < (ShellMenuItem const& r)
{
return *pItem < *(r.pItem);
}
void exec() const { pItem->exec(); }
};
typedef QList<ShellMenuItem> ShellMenuItems;
3 实现
3.1 DesktopMenu
下面分别介绍三个函数实现:
3.1.1 computerShellItems
void DesktopMenu::computerShellItems()
{
LPITEMIDLIST pidl;
HRESULT hr = SHGetSpecialFolderLocation(0, CSIDL_DRIVES, &pidl);
ShellMenuItems items;
if(SUCCEEDED(hr))
{
LPSHELLFOLDER pDesktop = NULL;
if(FAILED(SHGetDesktopFolder(&pDesktop)))
return items;
ShellMenuItem menutItem;
pDesktop->AddRef();
menutItem.pItem = ShellItem::Ptr(new ShellItem());
menutItem.pItem->pidlRel = pidl;
menutItem.pItem->pidlFQ = ShellItem::clone(pidl);
menutItem.pItem->pParentFolder = pDesktop;
shellSubMenuItems(menutItem, items, true);
pDesktop->Release();
}
return items;
}
该函数先获取CSIDL_DRIVES类型的LPITEMIDLIST对象,构造ShellMenuItem对象后获取该对象包含的子菜单项。
- 注意需要获取Desktop类型LPSHELLFOLDER作为ShellMenuItem对象的pParentFolder字段。
3.1.2 desktopMenuItems
void DesktopMenu::desktopMenuItems()
{
LPITEMIDLIST pidl;
HRESULT hr = SHGetSpecialFolderLocation(0, CSIDL_DESKTOP, &pidl);
ShellMenuItems items;
if(SUCCEEDED(hr))
{
ShellMenuItem menutItem;
menutItem.pItem = ShellItem::Ptr(new ShellItem());
menutItem.pItem->pidlRel = pidl;
menutItem.pItem->pidlFQ = ShellItem::clone(pidl);
shellSubMenuItems(menutItem, items, true);
}
return items;
}
该函数先获取CSIDL_DESKTOP类型的LPITEMIDLIST对象,构造ShellMenuItem对象后获取该对象包含的子菜单项。
3.1.3 shellSubMenuItems
void DesktopMenu::shellSubMenuItems(ShellMenuItem const& item,
ShellMenuItems & menuItems,
bool isOnlyDir)
{
LPSHELLFOLDER pParentFolder = NULL;
HRESULT hr;
if(!item.pItem->pParentFolder)
hr = SHGetDesktopFolder(&pParentFolder);
else
hr = item.pItem->pParentFolder->BindToObject(item.pItem->pidlRel, 0, IID_IShellFolder, (LPVOID*)&pParentFolder);
if(FAILED(hr))
return;
LPENUMIDLIST pEnum;
SHCONTF grfFlags = SHCONTF_FOLDERS;
if(!isOnlyDir)
grfFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN;
hr = pParentFolder->EnumObjects(NULL, grfFlags, &pEnum);
if(SUCCEEDED(hr))
{
LPITEMIDLIST pidlTemp;
DWORD dwFetched = 1;
STRRET str;
while(S_OK == (pEnum->Next(1, &pidlTemp, &dwFetched)) && dwFetched)
{
ShellMenuItem menutItem;
pParentFolder->GetDisplayNameOf(pidlTemp, SHGDN_FORPARSING, &str);
menutItem.filePath = strToString(pidlTemp, &str);
menutItem.pItem = ShellItem::Ptr(new ShellItem());
menutItem.pItem->pParentFolder = pParentFolder;
menutItem.pItem->pidlRel = pidlTemp;
menutItem.pItem->pidlFQ = ShellItem::concat(item.pItem->pidlFQ, pidlTemp);
pParentFolder->AddRef();
SHFILEINFO sfi;
if(SHGetFileInfo((LPCTSTR)menutItem.pItem->pidlFQ, 0, &sfi, sizeof(sfi),
SHGFI_PIDL | SHGFI_DISPLAYNAME | SHGFI_TYPENAME | SHGFI_ICON | SHGFI_SMALLICON))
{
menutItem.caption = QString::fromStdWString(sfi.szDisplayName);
menutItem.icon = QIcon(QtWin::fromHICON(sfi.hIcon));
}
menuItems << menutItem;
dwFetched = 0;
}
pEnum->Release();
}
pParentFolder->Release();
std::sort(menuItems.begin(), menuItems.end());
}
该函数根据传递pParentFolder遍历文件夹,获取目录名称和图标。
3.1.4 strToString
其它函数实现如下:
static QString 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;
}
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;
static bool isSwitch = false;
ShellMenuItems items;
if(isSwitch)
items = DesktopMenu::computerShellItems();
else
items = DesktopMenu::desktopMenuItems();
isSwitch = !isSwitch;
foreach(auto item, items)
{
ShellMenuItems childItems;
DesktopMenu::shellSubMenuItems(item, childItems, true);
if(childItems.isEmpty())
{
menu.addAction(item.icon, item.caption, this, [=](){
item.exec();
});
}
else
{
QMenu *subMenu = new QMenu(item.caption);
foreach(auto childItem, childItems)
{
subMenu->addAction(childItem.icon, childItem.caption, this, [=](){
QPoint p = QCursor::pos();
childItem.pItem->contextMenu((void *)winId(), p.x(), p.y());
});
}
QAction* action = subMenu->menuAction();
action->setIcon(item.icon);
menu.addMenu(subMenu);
}
}
menu.exec(QCursor::pos());
}
如customContextMenu函数中所示根据返回的ShellMenuItems创建菜单,单击右键显示显示桌面菜单。