Question

In my application I have a QToolButton related to the presence of an USB Pen Drive. When the Pen drive is inserted I would like to show the QToolButton and create a context menu associated to the content of the pen drive.I have a different menu dynamically created to be assigned to the Button.

My code works well for the first time, but when I create a new menu it doesn't appear. In this last version of code, when I show the button for the second time I get the the previous menu (Dismount is the only item present) and when i click on the item it doesn't do anything.

EDIT: If I use the QAction instead of the QWidgetAction the code works fine. So it seems something related to the QWidgetAction of QLabel used inside of it.

The following is a simplified version of my code:

/* member variables */
QMenu *m_pqmConMenUSB;
QLabel m_MenuItem;

/* costructor */    
ui->tbDriveUSB->setContextMenuPolicy(Qt::CustomContextMenu);
m_pqmConMenUSB = NULL;
QObject::connect(ui->tbDriveUSB, SIGNAL(customContextMenuRequested(const QPoint&)),this, SLOT(showUSBCM(const QPoint&)));
m_MenuItem.setStyleSheet("QLabel { background-color : black; color : white; }");
m_MenuItem.setText("Dismount");
QFont fonte = m_MenuItem.font();
fonte.setPixelSize(16);
m_MenuItem.setFont(fonte);
QPalette ChePalette = m_MenuItem.palette();
m_MenuItem.setMinimumSize(0,32);
ChePalette.setColor(m_MenuItem.backgroundRole(), Qt::black);
ChePalette.setColor(m_MenuItem.foregroundRole(), Qt::white);
m_MenuItem.setPalette(ChePalette);

/*member functions*/
void  MainWindow::showUSBCM(const QPoint& pos)
{
    // copied from an example
    if (pos != QPoint(0,0)) {
        // Execute context menu
        if (m_pqmConMenUSB!=NULL) m_pqmConMenUSB->exec(pos);
    }
}

void MainWindow::OnUSBMounted()
{
    /* this static boolean is used to simulate a change in the menu content */
    static bool tryToChange = false;
    ui->tbDriveUSB->show();
    m_pqmConMenUSB = new QMenu(this);
    QWidgetAction *menuItemW = new QWidgetAction(this);
    menuItemW->setDefaultWidget(&m_MenuItem);
    menuItemW->setText("Dismount");
    connect(menuItemW,SIGNAL(triggered()), this, SLOT(DoDismount()));
    m_pqmConMenUSB->addAction(menuItemW);
    if (tryToChange)
    {
        menuItemW = new QWidgetAction(this);
        menuItemW->setDefaultWidget(&m_MenuItem);
        menuItemW->setText("Update");
        connect(menuItemW,SIGNAL(triggered()), this, SLOT(Update()));
        m_pqmConMenUSB->addAction(menuItemW);
    }
    tryToChange = !tryToChange;
    ui->tbDriveUSB->setMenu(m_pqmConMenUSB);
}

void MainWindow::OnUSBDismounted()
{
   ui->tbDriveUSB->hide();

   /* the first version of the code tries to destroy the menu with the following code, but it doesn't work
   /*ui->tbDriveUSB->setMenu(NULL);
   QAction *pAction;
   foreach (pAction, m_pqmConMenUSB->actions())
       pAction->disconnect(this);
       delete(m_pqmConMenUSB);
    m_pqmConMenUSB = NULL;*/

}

Was it helpful?

Solution 2

As I mentioned yesterday, the problem was related to QLabels. In my code I used two member variables of QLabel type. The QLabels weren't pointers. When I delete the action, the QLabels weren't able to show them again. I suppose it was related to the removeAction(d->menuAction); function which destroys QWidget associated to the QWidgetAction. That function was called when the ui->tbDriveUSB->setMenu(NULL); was called. I choose to use the QLabel just for stylesheet and size, but it's possibile to set that properties in the menu. This is enough for me. I think that, making a new QLabel when the QWidgetAction is created and delete it when the QWidgetAction is deleted, could make works the previous code. I haven't tested it.

In order to complete the answer, the following is my current code that works well

/* member variable */
QMenu *m_pqmConMenUSB;

/* constructor */
ui->tbDriveUSB->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(ui->tbDriveUSB, SIGNAL(customContextMenuRequested(const QPoint&)),this, SLOT(showUSBCM(const QPoint&)));
m_pqmConMenUSB = new QMenu(this);
QFont fonte = m_pqmConMenUSB->font();
fonte.setPixelSize(16);
m_pqmConMenUSB->setFont(fonte);
m_pqmConMenUSB->setStyleSheet("QMenu { background-color : black; color : white; }");
m_pqmConMenUSB->setMinimumSize(0,32);

/*member functions*/
void  MainWindow::showUSBCM(const QPoint& pos)
{
    if (pos != QPoint(0,0)) 
    {
         // Execute context menu
         if (m_pqmConMenUSB!=NULL) m_pqmConMenUSB->exec(pos);
    }
}

void MainWindow::OnUSBMounted()
{
    static bool tryToChange = true;
    ui->tbDriveUSB->show();
    QAction *menuItem = new QAction("Dismount",this);
    connect(menuItem,SIGNAL(triggered()), this, SLOT(DoDismount()));
    m_pqmConMenUSB->addAction(menuItem);
    if (tryToChange)
    {
        QAction *menuItem2 = new QAction("upDate",this);
        connect(menuItem2,SIGNAL(triggered()), this, SLOT(Update()));
        m_pqmConMenUSB->addAction(menuItem2);
    }
    tryToChange = !tryToChange;
    ui->tbDriveUSB->setMenu(m_pqmConMenUSB);
}

void MainWindow::OnUSBDismounted()
{
    printf("SEI UNO SMONTATO\n\r");
    ui->tbDriveUSB->setMenu(NULL);
    QAction *pAction;
    foreach (pAction, m_pqmConMenUSB->actions())
    {
        pAction->disconnect(this); // receiver
        delete pAction;
    }
    ui->tbDriveUSB->hide();
    m_pqmConMenUSB->clear();
}

OTHER TIPS

A useful pattern to dynamically populate a menu associated with a QToolButton is to first create the menu and attach it to the button, but do not populate it. Then connect a slot to the QMenu::aboutToShow() signal. In the slot implementation, clear the contents of the menu and populate it as needed for the current state of the application.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top