【问题标题】:how to create menu in statusbar in qt like PC start menu如何在qt的状态栏中创建菜单,如PC开始菜单
【发布时间】:2017-06-09 03:56:43
【问题描述】:

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

这是我的 qt 应用程序窗口

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

//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!";

}

点击菜单按钮弹出菜单

【问题讨论】:

  • 在查看了我自己的资源后,我们如何放置上下文菜单(例如在 QTreeView 中),我发现有两件事我认为值得一提:菜单是使用 QMenu::popup() 打开的,它接受一个位置.另一件事是QWidget::mapToGlobal(),它很有用。如果菜单应在按钮上方打开,则必须适当考虑这一点(例如,通过从标签/按钮的映射到全局 y 位置减去菜单的高度)。
  • 我不确定qMenuStart.exec()。恐怕这会在分配给menuBtn 之前打开菜单,因此不会考虑任何定位。为什么不让QMenu 成为MainWindow 的成员并配置一次(连同menuBtnstatusBar)。在这种情况下,qMenuStart.exec() 已过时。
  • 我相信,我知道你为什么插入qMenuStart.exec();。您可能想知道qMenuStart.popup() 上没有显示菜单。实际上,它确实如此,但它在之后立即被销毁(从MainWindow::showPopupMenu() 返回),因为它是一个本地实例。 (我的第一个猜测是崩溃,但同时我相信 Qt 能够管理它。)但是,解决方案仍然像我之前评论中建议的那样。

标签: c++ qt statusbar


【解决方案1】:

我采用了您的示例代码并对其进行了更改,以说明我在最新的 cmets 中描述的内容:

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 自身被销毁时管理它的销毁。

信号处理程序分别变短:

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)。所以,请把这个和“一粒盐”放在一起。

注意:

我刚刚意识到您使用“pre-Qt5”风格连接信号处理程序。 Qt5 引入了一个不错的extension to its signals 支持函数指针和方法指针。因此,我能够像lambdas 一样简单地执行信号处理程序。 (这些 lambdas 是这些 [](bool){/*...*/} 我连接到操作插槽。我一直对这些新的 lambdas 持怀疑态度,直到我意识到它们是一个极好的工具,可以为信号处理程序编写非常简单的适配器以及制作非常紧凑的示例代码。 )

关于 Lambda 的更新:

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

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

lambda 的环境允许访问 lambda 主体中的“外部变量”。变量可以通过值或引用添加。如果一个变量是通过引用提供的,它可以在 lambda 主体中访问,但它不会保证“外部”变量的生命周期足够长。 (如果不是,它会导致“悬空引用”具有与悬空指针相同的未定义行为。)它有助于防止引用吗?不会。对于原始类型(例如 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 能够正常工作。

【讨论】:

  • 非常感谢,您的解决方案符合我的预期
  • 相同的 Qt 应用程序在 windows 10 中运行良好,但是当我们在由 arm 编译器编译的 Linux(嵌入式设备)中运行相同的 Qt 应用程序时。编译成功后,QPixmap 图像单独不显示并抛出以下错误,'QPixmap::scaled: Pixmap is a null pixmap' plzSuggest
  • @DurgaSekar 图像文件的加载(QIconQPixMap 等)由插件完成(例如qjpeg.dll 用于 JPEG)。可能是,分别。 Linux(嵌入式设备)上缺少插件。顺便提一句。想知道为什么我没有找到qpng.dll(尽管它确实在我的第一个样本中起作用)我找到了Qt Image Formats。因此,一些装载机似乎是“被烤熟的”。 IE。将文件转换为 png(并分别更改文件名)应该使您独立于插件。
  • 我想不必提及 Windows 有 DLL,但 Linux 有 SO。因此,Linux 上的插件应该命名为qjpeg.so 或类似名称。 (我查看了我的 cygwin 的 /lib/qt5/plugins/imageformats(我手头最喜欢的 Linux):有一个 cygqjpeg.dll。因此,插件名称可能在平台之间略有不同......)
  • 我在 ../plugins/imageformats 文件夹中搜索,它只有 3 个 .so 文件(libqico.so、libqsvg.so、libqtga.so)。没有找到任何与 png 相关的文件。我正在寻找相应的 SO 文件。
【解决方案2】:

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

但是,出于好奇,我尝试了自己的运气并想展示我所拥有的:

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

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

内部状态栏是必须用于临时消息的状态栏。为防止重复尺寸夹点的滑稽效果,内部状态栏的尺寸夹点被禁用(使用QStatusBar::setSizeGripEnabled())。

嵌套状态栏的另一个副作用是内部状态栏左右分隔符的可视化。我认为这还不错(就美学方面而言)并且不在乎。

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

为了提供开始按钮,我最初使用了QPushButton。记住我必须以某种方式放置开始菜单,因此我将其替换为 QToolBar,因为它可能包含 QAction,而 QAction 又准备好管理子菜单。

当我开始工作时,我意识到设置菜单时出现在开始按钮上的指示器。为了使示例尽可能短,我决定暂时使用它。

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

如果定义了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 的更新版本如下所示:

【讨论】:

  • 解决方案很好,但实际上我需要实现的是状态栏中左角图标上的单击事件。我已经通过 qlabel 中的 Qpixmap 在状态栏中创建了开始菜单图标。 //Menu Icon menuIcon-&gt;setPixmap(QPixmap(":/new/prefix1/ic_menu_black.png")); menuIcon-&gt;setScaledContents(false); statusBar()-&gt;addWidget(menuIcon); 有没有可能在这个 menuIcon(Qlabel) 上添加点击操作
  • 我相信,应该是可以的: 1. 创建一个QLabel 的派生类。 2. 至少重载mousePressEvent()。在这种情况下,您必须在重载方法中show() 菜单,并且可能要计算并设置其正确位置。我不太确定是否需要额外的东西以便QLabel 接受鼠标事件。我相信 - 不是。
  • 因此,我的第一个意图是改用QPushButton。这提供了一个随时可用的信号clicked。因此,不需要重载。如有必要,可以调整按钮的外观以修改其属性/样式。但是,仍然必须在单击信号处理程序中放置和显示菜单。一般来说:如果我有疑问,我会搜索 woboq.com 它最初是如何在 Qt 中完成的,但有时会有点乏味。
  • 我根据我们的沟通更新了答案。
  • 我按照你的解决方案试过了,现在我的问题是点击菜单按钮(QPushButton)弹出菜单显示在屏幕左上角但不在菜单按钮附近,我附加了GUI窗口截图请参考并提出解决方案,
猜你喜欢
  • 1970-01-01
  • 2013-12-24
  • 1970-01-01
  • 2012-10-02
  • 2011-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多