Qt实现Windows的SendTo菜单(Windows Shell编程)

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菜单。

4.3 运行效果

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

flysnow010

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

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

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

打赏作者

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

抵扣说明:

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

余额充值