如何在qt中像PC开始菜单一样在状态栏中创建菜单

how to create menu in statusbar in qt like PC start menu

在我的qt应用程序中,我设计了很少图标的状态栏。在状态栏的左侧,我添加了菜单图标 (QPixmap),当我单击该图标时,我需要显示一个类似于 PC 开始菜单的菜单。我搜索了很多,但没有找到 Qwidget。

这是我的qt应用程序window

根据我最近的评论,我在下面添加了我编辑过的代码,因为我无法在评论部分添加代码,请提出解决方案

//Menu Button
    menuBtn->setIcon(QPixmap(":/new/prefix1/ic_menu_black.png"));
    statusBar()->addWidget(menuBtn);
    connect(menuBtn,SIGNAL(clicked(bool)),this,SLOT(showPopupMenu()));

void MainWindow::showPopupMenu(){


    QMenu qMenuStart;

    QAction qCmdStart1(QString::fromUtf8("Product Details"), &qMenuStart);
    qMenuStart.addAction(&qCmdStart1);

    QAction qCmdStart2(QString::fromUtf8("Memory Status"), &qMenuStart);
    qMenuStart.addAction(&qCmdStart2);

    QObject::connect(&qCmdStart1, &QAction::triggered,[](bool){
        qDebug() << "Product Details triggered"; }
    );
    QObject::connect(&qCmdStart2, &QAction::triggered,[](bool){
        qDebug() << "Memory Status triggered"; }
    );

    qMenuStart.exec();

    menuBtn->setMenu(&qMenuStart);

    qMenuStart.show();
    qMenuStart.popup(mapToGlobal(pos() - QPoint(0, qMenuStart.height())));

    qDebug()<<"Menu Clicked!";

}

点击菜单按钮弹出菜单

看了你的问题,我不太确定你的实际问题是什么。不幸的是,由于我们公司的安全政策,i.stack.imgur.com 被阻止了,我只是在办公室。可能,您的快照已经为我澄清了事情。 (等我回家再看看。)

然而,出于好奇,我试了一下运气,想展示一下我的收获:

实际上,使用 QStatusBar::insertPermanentWidget() 将小部件添加到状态栏非常容易。

不幸的是,永久性小部件位于右侧。如果要求(将其放在左侧)是必不可少的,则可以通过将两个状态栏相互嵌套来进行调整。 (实际上,所有其他布局也应该有能力,但 QMainWindow::setStatusBar() 需要 QStatusBar*。)

内部状态栏是必须用于临时消息的状态栏。为了防止重复大小夹点的有趣效果,内部状态栏禁用了大小夹点(使用 QStatusBar::setSizeGripEnabled())。

嵌套状态栏的另一个副作用是内部状态栏左侧和右侧的分隔符可视化。我认为这不是那么糟糕(关于美学方面)并且不关心它。

可能,我可以简单地使用 QLabel(而不是内部状态栏)来显示消息(失去 QStatusBar 提供的附加功能,例如自动清除的超时一条消息)。

为了提供开始按钮,我最初使用了一个 QPushButton. Remembering that I have to place the start menu somehow I replaced it with a QToolBar as it may contain a QAction,它又准备好管理一个子菜单。

当我完成这项工作时,我意识到设置菜单时出现在开始按钮上的指示器。为了使样本尽可能短,我决定暂时接受它。

在与提问者进行了一些交流之后,我添加了一个使用从 QLabel 派生的 class 的替代实现。菜单放置必须自己完成。但最后,它并没有那么复杂......

如果定义了 USE_LABEL(靠近源代码的开头),则激活 2nd 版本。

我的样本testQStartBtn.cc:

#include <QtWidgets>

#define USE_LABEL

#ifdef USE_LABEL
class StartButton: public QLabel {

  private:
    QMenu *_pQMenu;

  public:
    StartButton(QWidget *pQParent = 0): QLabel(pQParent), _pQMenu(nullptr) { }

    QMenu* menu() { return _pQMenu; }
    QMenu* setMenu(QMenu *pQMenu) { std::swap(_pQMenu, pQMenu); return pQMenu; }

  protected:
    virtual void mousePressEvent(QMouseEvent *pQEvent);
};

void StartButton::mousePressEvent(QMouseEvent *pQEvent)
{
  if (_pQMenu && pQEvent->button() == Qt::LeftButton) {
    if (!_pQMenu->isVisible()) {
      _pQMenu->show(); // a trick to force computation of height() before
      _pQMenu->popup(mapToGlobal(pos()) - QPoint(0, _pQMenu->height()));
    } else _pQMenu->hide();
  }
}
#endif // USE_LABEL

int main(int argc, char **argv)
{
  qDebug() << "Qt Version: " << QT_VERSION_STR;
  // main application
#undef qApp // undef macro qApp out of the way
  QApplication qApp(argc, argv);
  // setup GUI
  QMainWindow qWin;
  QLabel qLblCentral(QString::fromUtf8("Central\nWidget"));
  qLblCentral.setAlignment(Qt::AlignCenter);
  qWin.setCentralWidget(&qLblCentral);
  QStatusBar qStBarLayout;
  // the "Windows Start Menu" alike
#ifdef USE_LABEL
  StartButton qBtnStart;
  qBtnStart.setPixmap(QPixmap(
    QApplication::applicationDirPath() + QString::fromLatin1("/Start.png")));
#else // (not) USE_LABEL
  QToolBar qToolbarStart;
  QIcon qIcnStart(
    QApplication::applicationDirPath() + QString::fromLatin1("/Start.png"));
  QAction qCmdStart(qIcnStart, QString::fromUtf8("Start"), &qToolbarStart);
#endif // USE_LABEL
  QMenu qMenuStart;
  QAction qCmdStart1(QString::fromUtf8("Command 1"), &qMenuStart);
  qMenuStart.addAction(&qCmdStart1);
  QAction qCmdStart2(QString::fromUtf8("Command 2"), &qMenuStart);
  qMenuStart.addAction(&qCmdStart2);
  QAction qCmdStart3(QString::fromUtf8("Command 3"), &qMenuStart);
  qMenuStart.addAction(&qCmdStart3);
#ifdef USE_LABEL
  qBtnStart.setMenu(&qMenuStart);
  qStBarLayout.insertPermanentWidget(0, &qBtnStart);
#else // (not) USE_LABEL
  qCmdStart.setMenu(&qMenuStart);
  qToolbarStart.addAction(&qCmdStart);
  qStBarLayout.insertPermanentWidget(0, &qToolbarStart);
#endif // USE_LABEL
  // the rest of status bar
  QStatusBar qStBar;
  qStBar.showMessage(QString::fromUtf8("<- Start Menu"), 3000 /* ms */);
  qStBar.setSizeGripEnabled(false);
  qStBarLayout.insertPermanentWidget(1, &qStBar, 1);
  qWin.setStatusBar(&qStBarLayout);
  qWin.show();
  // install signal handlers
  QObject::connect(&qCmdStart1, &QAction::triggered,
    [](bool){ qDebug() << "Command 1 triggered"; });
  QObject::connect(&qCmdStart2, &QAction::triggered,
    [](bool){ qDebug() << "Command 2 triggered"; });
  QObject::connect(&qCmdStart3, &QAction::triggered,
    [](bool){ qDebug() << "Command 3 triggered"; });
  // run application
  return qApp.exec();
}

我在 Windows 10(64 位)上用 VS2013 和 Qt5.6 编译。这是它的样子:

派生 QLabel 的更新版本如下所示:

我获取了您的示例代码并对其进行了更改以说明我在最新评论中描述的内容:

MainWindow声明中添加一个成员变量:

QMenu *menuStart;

修改后的示例代码:

// configure start menu:
menuStart = new QMenu(menuBtn);
menuStart->addAction(QString::fromUtf8("Product Details"),
  [](bool){
    qDebug() << "Product Details triggered"; }
  );
menuStart->addAction(QString::fromUtf8("Memory Status"),
  [](bool){
    qDebug() << "Memory Status triggered"; }
  );
//Menu Button
menuBtn->setIcon(QPixmap(":/new/prefix1/ic_menu_black.png"));
menuBtn->setMenu(menuStart);
statusBar()->addWidget(menuBtn);
connect(menuBtn,SIGNAL(clicked(bool)),this,SLOT(showPopupMenu()));

注:

我将 menuBtn 设置为 menuStart 的父级。因此,menuBtn 应该在 menuStart 自身被销毁时管理 menuStart 的销毁。

信号处理程序分别变得更短:

void MainWindow::showPopupMenu()
{
  menuStart->show(); // <-- trick to force layout of the menu before height() is called
  // position menu relative to menuBtn at popup
  menuStart->popup(
    mapToGlobal(menuBtn->pos() - QPoint(0, menuStart->height())));

  qDebug()<<"Menu Clicked!";
}

我仔细做了但无法测试它(因为它不是 MCVE)。所以,请带上 "grain of salt".

注:

我刚刚发现您使用 "pre-Qt5" 连接信号处理程序的方式。 Qt5引入了一个不错的extension to its signals supporting function pointers as well as method pointers. Due to this, I was able to do the signal handlers simply as lambdas。 (lambda 是这些 [](bool){/*...*/} 我连接到动作槽。我一直对这些新的 lambda 持怀疑态度,直到我意识到它们是一个很好的工具,可以为信号处理程序编写非常简单的适配器以及制作非常紧凑的样本代码。)

关于 Lambda 的更新:

方括号[]可用于向lambda的环境(或上下文)添加变量。如果它是空的(如我所用),则只能使用全局变量和函数。使用 [this] 意味着当前的 this 指针被添加到环境中。因此,lambda 可以访问当前 class 实例的成员变量。

当我第一次认识 lambda 时,我经常看到 [=] 表示 lambda 获取调用函数的当前上下文(例如,每个局部函数变量的副本)。我个人认为这是危险的,尤其是对于信号处理程序。我会详细说明一下:

lambda 环境允许在 lambda 主体中访问 "variables from outside"。变量可以按值或按引用添加。如果一个变量是通过引用提供的,它可以在 lambda 主体中访问,但它不会保证 "outside" 变量的生命周期足够长。 (如果不是,它会导致 "dangling reference" 具有与悬挂指针相同的 未定义行为 。)它是否有助于完全防止引用?不,这对原始类型(例如 intfloat)没问题,但对对象来说可能效率低下(甚至语义错误)。使用指向对象的指针与使用引用一样危险。因此,lambda 的环境应该与 care.

一起使用

根据经验,我个人使用以下方法来实现 lambda:我总是从空环境开始 ([])。如果我在 lambda 中识别出我需要的变量,我将其添加到环境中,据此我认为它有足够的生命周期(或寻找替代方案)。

更新:

因为我是SVG的个人粉丝,我准备了一个(类似的)菜单图标作为SVG文件:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
  "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  width="32" height="32">
  <title>Start Menu</title>
  <desc>Start Menu</desc>
  <rect id="line"
    x="4" y="6" width="24" height="4"
    style="stroke:none;fill:#444"/>
  <use xlink:href="#line" transform="translate(0,8)"/>
  <use xlink:href="#line" transform="translate(0,16)"/>
 </svg>

您可以将其保存在文本文件中 ic_menu_black.svg 并尝试将其加载到您的应用程序中。

不幸的是,Qt 的导入器对 SVG 的支持有限。因此,并不是所有在 SVG 中可行的技巧在 Qt 中都能正常工作。因此,我从我们的 Qt 应用程序中修改了一个图标的副本,以确保 this 可以工作。