how to create menu in statusbar in qt like PC star

2019-02-18 22:14发布

问题:

In my qt application, i have designed status bar with few icons. On the left of my status bar i have added menu icon (QPixmap) when i click that icon i need to show a menu similar to PC start menu. I searched a lot but i did not found Qwidget for this.

This is my qt application window

As per my recent comment i added my edited code below since i cannot add the code in comment section, plz suggest a solution

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

}

popup menu on click of menu button

回答1:

I took your sample code and changed it to illustrate what I described in my latest comments:

Add a member variable to the MainWindow declaration:

QMenu *menuStart;

The changed sample code:

// 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()));

Note:

I set menuBtn as parent of the menuStart. Thus, menuBtn should manage the destruction of menuStart when it is destroyed itself.

The signal handler becomes shorter respectively:

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

I did it carefully but couldn't test it (as it's no MCVE). So, please, take this with a "grain of salt".

Note:

I just realized that you use the "pre-Qt5" style of connecting signal handlers. Qt5 introduced a nice 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. (The lambdas are these [](bool){/*...*/} I connected to the action slots. I was always suspicious about these new lambdas until I realized that they are an excellent tool to write very simple adapters for signal handlers as well as to make quite compact sample code.)

Update about Lambdas:

The brackets [] can be used to add variables to the environment (or context) of the lambda. If it is empty (as I used it) only global variables and functions can be used. Using [this] means the current this pointer is added to the environment. Thus, the lambda may access member variables of the current class instance.

When I recognized lambdas the first time, I saw often [=] meaning the lambda gets the current context of the calling function (e.g. copies of every local function variable). I personally consider this as dangerous, especially for signal handlers. I will elaborate this a little bit:

The environment of a lambda allows to access "variables from outside" in the lambda body. Variables may be added by value or by reference. If a variable is provided by reference, it may be accessed in the lambda body but it will not grant that the life-time of the "outside" variable is long enough. (If it is not it results in a "dangling reference" with the same Undefined Behavior like a dangling pointer.) Does it help to prevent references at all? No. It is fine for primitive types (e.g. int or float) but could be inefficient (or even semantically wrong) for objects. Using pointer to objects instead, is as dangerous as using references. Thus, the enviroment of a lambda should be used with care.

As a rule of thumb, I personally use the following method to implement lambdas: I always start with an empty environment ([]). If I recognize a variable which I need in the lambda I add it to the environment whereby I consider its sufficient lifetime (or look for an alternative).

Update:

As I'm a personal fan of SVG, I prepared a (similar) menu icon as SVG file:

<?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>

You may save this in a text file ic_menu_black.svg and try to load it in your app.

Qt's importer has unfortunately only limited support of SVG. Thus, not every trick which is possible in SVG will work correctly in Qt. Therefore, I modified a copy of an icon out of our Qt application to be sure that this will work.



回答2:

Reading your question, I was not quite sure what your actual problem is. Unfortunately, due to the security policy of our company, i.stack.imgur.com is blocked and I'm just in the office. Probably, your snapshot had clarified the things for me. (I could have a look at it when I'm home again.)

However, out of curiosity, I tried my luck and want to show what I've got:

Actually, it is quite easy to add a widget to a status bar using QStatusBar::insertPermanentWidget().

Unfortunately, permanent widgets are placed on the right. If the requirement (to place it at left) is essential, this can be tweaked by nesting two status bars into each other. (Actually, every other layout should be capable also but the QMainWindow::setStatusBar() requires a QStatusBar*.)

The inner status bar is the one which has to be used for temporary messages. To prevent the funny effect of duplicated size grips, the size grip is disabled for the inner status bar (using QStatusBar::setSizeGripEnabled()).

Another side effect of nested status bars is the visualization of separators at the left and right of the inner status bar. I considered this as not that bad (concerning the aesthetical aspect) and didn't care about it.

May be, I could have used simply a QLabel (instead of the inner status bar) for message display (losing the additional capabilities provided by QStatusBar e.g. the timeout for auto-clear of a message).

To provide the start button, I originally used a QPushButton. Remembering that I have to place the start menu somehow I replaced it with a QToolBar as it may contain a QAction which in turn is ready equipped to manage a sub-menu.

As I got this working I realized the indicator which appeared at the start button when I set the menu. To keep the sample as short as possible I decided to live with this for now.

After having some communication with the questioner, I added an alternative implementation using a class derived from QLabel. The menu placement has to be done on its own. But finally, it's not that complicated...

The 2nd version is activated if USE_LABEL is defined (near beginning of source code).

My sample 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();
}

I compiled with VS2013 and Qt5.6 on Windows 10 (64 bit). This is how it looks:

The updated version with the derived QLabel looks like this:



标签: c++ qt statusbar