Qt实现桌面菜单(Windows Shell编程)

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创建菜单,单击右键显示显示桌面菜单。

4.3 运行效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flysnow010

你的鼓励就是我最大的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值