Qt实现Windows右键菜单(Windows Shell, 单例)

1 概述

本文基于Qt利用Windows的Shell编程实现文件和目录的右键菜单。

2 定义

Windows Shell编程需要引用头文件:

#include <Shlobj.h>
#include <shlwapi.h>
#include <windows.h>

这里定义两个类ContextMenu和ContextMenuHelper

2.1 ContextMenu

该类定义了上下文菜单调用接口,定义如下:

class ContextMenu
{
public:
    static void show(QStringList const& fileNames,
                     void *handle, QPoint const& p);
};

该类利用下面的辅助类ContextMenuHelper实现功能。

2.2 ContextMenuHelper

该类是一个辅助类型定义如下:

struct ContextMenuHelper
{
    static ContextMenuHelper* Instatnce();//获取单例对象
    ~ContextMenuHelper();

    void showContextMenu(QStringList const& fileNames, void* handle, int x, int y);
private:
    ContextMenuHelper();
    LPSHELLFOLDER getParentFolder(QString const& filePath, bool isRoot = false);
    bool getChildParent(ShellItem::Ptr & item, QString &childPath, QString const&   filePath);
    LPSHELLFOLDER getSpecialFolder(int idFolder);
    bool isRootPath(QString const& path);
    QString strToString(LPITEMIDLIST pidl, STRRET *str);
    QString toWindowsPath(QString const& linuxPath);
    QString toLinuxPath(QString const& windowsPath);
    IShellFolder* pDesktop = nullptr;
};

如上所示ContextMenuHelper是一个单例类,公共接口showContextMenu实现右键上下文菜单。
后面讲述两个类的实现。

3 实现

3.1 ContextMenu

该类实现比较简单,直接调用ContextMenuHelper的showContextMenu,如下所示。

void ContextMenu::show(QStringList const& fileNames,
                     void *handle, QPoint const& p)
{
    ContextMenuHelper::Instatnce()->showContextMenu(fileNames, handle, p.x(), p.y());
}

3.2 ContextMenuHelper

重点是辅助类实现。

3.2.1 对象构建与销毁

    ContextMenuHelper::ContextMenuHelper()
    {
        SHGetDesktopFolder(&pDesktop);
    }
    ContextMenuHelper::~ContextMenuHelper()
    {
        if(pDesktop)
            pDesktop->Release();
    }
    

对象的构造函数定义为private,没法直接实现构造,需要实现静态函数Instatnce来完成对象构建。

3.2.2 单例函数

ContextMenuHelper* Instatnce()
{
    static ContextMenuHelper helper;
    return &helper;
}

这样一个单例类构建完成。

3.2.3 showContextMenu

实现如下:

void ContextMenuHelper::showContextMenu(QStringList const& fileNames, void* handle, int x, int y)
{
    if(fileNames.isEmpty())
        return;
    QString parentPath = toWindowsPath(QFileInfo(fileNames[0]).path());/*将Qt路径分割符/转换为\*/
    LPSHELLFOLDER pParentFolder = getParentFolder(parentPath, isRootPath(fileNames[0]));
    if(!pParentFolder)
        return;

    LPITEMIDLIST* pidlFiles = (LPITEMIDLIST *)malloc(sizeof(LPITEMIDLIST) * fileNames.size());
    {
        LPENUMIDLIST pEnum;
        HRESULT hr = pParentFolder->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnum);
        if(SUCCEEDED(hr))
        {
            LPITEMIDLIST pidl;
            STRRET str;
            int index = 0;
            while(pEnum->Next(1, &pidl, 0) == S_OK)
            {
                pParentFolder->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str);
                QString fileName = toLinuxPath(strToString(pidl, &str));
                if(!fileNames.contains(fileName))
                    CoTaskMemFree(pidl);
                else
                {
                    if(index < fileNames.size())
                        pidlFiles[index++] = pidl;
                }
            }
        }
    }

    IContextMenu   *pcm;
    HRESULT hr = pParentFolder->GetUIObjectOf(0,
        fileNames.size(), (PCUITEMID_CHILD_ARRAY)pidlFiles,
        IID_IContextMenu, 0, (LPVOID*)&pcm);
    
    if(SUCCEEDED(hr))
    {
        HMENU hPopup = CreatePopupMenu();
        if(hPopup)
        {
            hr = pcm->QueryContextMenu(hPopup, 0, 1, 0x7fff,  CMF_NORMAL);

            if(SUCCEEDED(hr))
            {
                UINT  idCmd = TrackPopupMenu(hPopup,
                    TPM_LEFTALIGN | TPM_RETURNCMD | TPM_RIGHTBUTTON,
                    x, y, 0, (HWND)handle, 0);

                if(idCmd && (idCmd != (UINT)-1))
                {
                    CMINVOKECOMMANDINFO  cmi;
                    cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
                    cmi.fMask = 0;
                    cmi.hwnd = 0;
                    cmi.lpVerb = (LPCSTR)(INT_PTR)(idCmd - 1);
                    cmi.lpParameters = NULL;
                    cmi.lpDirectory = NULL;
                    cmi.nShow = SW_SHOWNORMAL;
                    cmi.dwHotKey = 0;
                    cmi.hIcon = NULL;
                    pcm->InvokeCommand(&cmi);
                }
            }
            DestroyMenu(hPopup);
        }
    }
    for(int i = 0; i < fileNames.size(); i++)
        ILFree(pidlFiles[i]);
    free(pidlFiles);
    pParentFolder->Release();
}

该接口支持显示同一目录下的多为文件和目录,代码流程如下:

  • 获取文件/目录的父文件夹对象pParentFolder(LPSHELLFOLDER).
  • 由pParentFolder遍历出要对应目录的子对象数组pidlFiles(LPITEMIDLIST).
  • pParentFolder获取pidlFiles的上下文菜单接口pcm(IContextMenu).
  • 创建弹出菜单hPopup,并填充菜单项
  • 弹出菜单后
  • 释放分配资源

3.2.4 getParentFolder

获取文件路径filePath的父文件夹对象,实现如下:

LPSHELLFOLDER ContextMenuHelper::getParentFolder(QString const& filePath, bool isRoot = false)
{
    LPSHELLFOLDER pDrives = getSpecialFolder(CSIDL_DRIVES);//获取我的电脑文件夹对象
    ShellItem::Ptr item;
    if(pDrives)
    {
        LPENUMIDLIST pEnum;
        HRESULT hr = pDrives->EnumObjects(0, SHCONTF_FOLDERS, &pEnum);
        if (SUCCEEDED(hr))
        {
            LPITEMIDLIST pidl;
            STRRET str;

            QString childPath;
            while(pEnum->Next(1, &pidl, 0) == S_OK)
            {
                pDrives->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str);
                childPath = strToString(pidl, &str);

                if(isRootPath(childPath) && filePath.startsWith(childPath))//这里childPath是C:\\,D:\\,E:\\
                {
                    item = ShellItem::Ptr(new ShellItem());
                    item->pidlRel = pidl;
                    pDrives->AddRef();
                    item->pParentFolder = pDrives;
                    break;
                }
            }
            //递归找到filePath所在文件夹
            while(true)
            {
                if(childPath == filePath)
                    break;

                if(!getChildParent(item, childPath, filePath))
                    break;
            }
            pEnum->Release();
            if(item)
            {
                if(isRoot)
                    return item->pParentFolder;

                LPSHELLFOLDER pParentFolder = 0;
                item->pParentFolder->BindToObject(item->pidlRel,
                    0, IID_IShellFolder, (LPVOID*)&pParentFolder);
                return pParentFolder;
            }
        }
    }
    return 0;
}

3.2.5 getChildParent

在文件目录下查找文件filePath, 实现如下:

bool ContextMenuHelper::getChildParent(ShellItem::Ptr & item, QString &childPath, QString const& filePath)
{
    if(!item)
        return  false;

    LPSHELLFOLDER  pParentFolder = NULL;
    HRESULT hr = item->pParentFolder->BindToObject(item->pidlRel, 0, IID_IShellFolder,
                                                            (LPVOID*)&pParentFolder);
    if(SUCCEEDED(hr))
    {
        LPENUMIDLIST   pEnum;
        hr = pParentFolder->EnumObjects(NULL, SHCONTF_FOLDERS, &pEnum);
        if(SUCCEEDED(hr))
        {
            LPITEMIDLIST   pidl;
            DWORD          dwFetched = 1;
            STRRET str;
            while(S_OK == (pEnum->Next(1, &pidl, &dwFetched)) && dwFetched)
            {
                    pParentFolder->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str);
                    childPath = strToString(pidl, &str);

                    int index = filePath.indexOf(childPath);
                    if(index >= 0)
                    {
                    if(filePath == childPath || filePath.at(childPath.size()) == '\\')
                    {
                        ShellItem::Ptr newItem(new ShellItem());
                        newItem->pidlRel = pidl;
                        pParentFolder->AddRef();
                        newItem->pParentFolder = pParentFolder;
                        item = newItem;
                        pEnum->Release();
                        pParentFolder->Release();
                        return true;
                    }
                    }
            }
            pEnum->Release();
        }
        pParentFolder->Release();
    }
    return false;
}

3.2.6 getSpecialFolder

获取指定文件对应的LPSHELLFOLDER对象,在本文使用的是CSIDL_DRIVES(驱动器对象),就是我电脑对象。

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.2.7 其它函数

其它函数实现如下:

bool ContextMenuHelper::isRootPath(QString const& path)
{
    //for dirver D: D:/
    //for virtual driver, the path's len is larger than 3
    return path.size() > 1 && path.size() <= 3;
}
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;
}

QString ContextMenuHelper::toLinuxPath(QString const& windowsPath)
{
    QString linuxPath = windowsPath;
    linuxPath.replace("\\", "/");
    return linuxPath;
}

4 使用

实现中用到HWND对象,需要在Qt中窗口对象中调用。
从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(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&)
{
    //winId()返回的Windows的HWND类型窗口对象。
    ContextMenu::show(QStringList() << "D:/", (void *)winId(), QCursor::pos());
}

如上所示在窗口中单击右键显示右键上下文菜单.

4.3 运行效果

在这里插入图片描述

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 在 Qt 中使用右键菜单来显示输入框可以这样做: 1. 在你的窗口类中重新实现 `contextMenuEvent` 事件处理程序。 ``` void MainWindow::contextMenuEvent(QContextMenuEvent *event) { // 创建右键菜单 QMenu menu(this); // 在菜单中添加一个 QAction QAction *inputAction = menu.addAction("输入框"); // 在菜单中显示输入框 QInputDialog inputDialog; connect(inputAction, &QAction::triggered, &inputDialog, &QInputDialog::open); // 在鼠标位置显示菜单 menu.exec(event->globalPos()); } ``` 2. 在你的窗口类中添加一个槽函数来处理输入框的输入。 ``` void MainWindow::onInputDialogAccepted(const QString &text) { // 在这里处理输入框中的文本 qDebug() << "输入的文本: " << text; } ``` 3. 在创建输入框时连接槽函数。 ``` QInputDialog inputDialog; connect(&inputDialog, &QInputDialog::accepted, this, &MainWindow::onInputDialogAccepted); ``` 完整代码如下: ``` #include <QMenu> #include <QInputDialog> void MainWindow::contextMenuEvent(QContextMenuEvent *event) { QMenu menu(this); QAction *inputAction = menu.addAction("输入框"); connect(inputAction, &QAction::triggered, this, &MainWindow::showInputDialog); menu.exec(event->globalPos()); } void MainWindow::showInputDialog() { QInputDialog inputDialog; connect(&inputDialog, &QInputDialog::accepted, this, &MainWindow::onInputDialogAccepted); inputDialog.open(); } void MainWindow::onInputDialogAccepted(const QString &text) { qDebug() << "输入的文本: " << text; } ``` ### 回答2: 在QT实现右键菜单点击出现输入框的方法如下: 1. 首先,在需要添加右键菜单的组件(例如QWidget、QLineEdit等)的构造函数中,调用setContextMenuPolicy(Qt::CustomContextMenu)函数,设置为自定义右键菜单。 2. 然后,重写该组件的contextMenuEvent()函数。该函数会在用户右键点击组件时触发。在该函数中,创建一个QMenu对象作为右键菜单,然后添加一个QAction对象到菜单中。 3. 为QAction对象关联一个槽函数,该槽函数会在用户点击右键菜单中的该项时被调用。在槽函数中,创建一个QInputDialog输入对话框,用于用户输入内容。 4. 通过调用QInputDialog的exec()函数,显示输入对话框,并获取用户输入的内容。可以使用getText()、getInt()、getItem()等函数根据需要获取不同类型的输入。 下面是一个示例代码: // mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include <QWidget> #include <QMenu> #include <QInputDialog> class MyWidget : public QWidget { Q_OBJECT public: MyWidget(QWidget *parent = nullptr); protected: void contextMenuEvent(QContextMenuEvent *event) override; private slots: void showInputDialog(); private: QMenu *menu; }; #endif // MYWIDGET_H // mywidget.cpp #include "mywidget.h" MyWidget::MyWidget(QWidget *parent) : QWidget(parent) { setContextMenuPolicy(Qt::CustomContextMenu); menu = new QMenu(this); QAction *inputAction = new QAction("输入框", this); connect(inputAction, &QAction::triggered, this, &MyWidget::showInputDialog); menu->addAction(inputAction); } void MyWidget::contextMenuEvent(QContextMenuEvent *event) { menu->exec(event->globalPos()); } void MyWidget::showInputDialog() { bool ok; QString text = QInputDialog::getText(this, tr("输入框"), tr("请输入内容:"), QLineEdit::Normal, QString(), &ok); if (ok && !text.isEmpty()) { // 处理用户输入的内容 } } 在上述示例代码中,自定义的QWidget派生类MyWidget实现右键菜单功能。在构造函数中,创建一个QMenu对象,并添加一个QAction对象到菜单中。QAction对象的触发信号与槽函数showInputDialog()关联。在showInputDialog()函数中,创建一个QInputDialog对象,并调用exec()函数显示输入对话框。用户输入内容后,可以根据需要进行处理。 ### 回答3: 在QT实现右键菜单点击出现输入框,可以通过以下步骤进行: 1. 创建一个QWidget或者QMainWindow的派生类,作为主窗口。 2. 在主窗口中,重写其右键菜单事件函数contextMenuEvent(QContextMenuEvent *event),该函数在右键点击时会被调用。 3. 在重写的contextMenuEvent函数中,创建一个QMenu对象作为右键菜单。 4. 在QMenu对象中,使用addAction函数添加一个QAction对象,该对象表示右键菜单中的一个选项。 5. 为QAction对象的triggered()信号关联一个槽函数,该槽函数在右键菜单被点击时被执行。 6. 在槽函数中,创建一个QInputDialog对象,作为输入框。 7. 设置QInputDialog的标题、提示信息等属性。 8. 调用QInputDialog的exec()函数显示输入框,并通过返回值判断用户的操作。 9. 根据用户的操作,可以使用QInputDialog的textValue()函数获取用户输入的文本内容。 10. 根据需求,做相应的处理,比如将输入的内容显示在界面上或者进行其他操作。 最后,编译运行程序,当在主窗口中右键点击时,会弹出右键菜单,点击菜单中的选项后,会出现一个输入框供用户输入内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

flysnow010

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

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

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

打赏作者

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

抵扣说明:

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

余额充值