How to add a list of QActions to a QMenu and handl

2019-01-25 07:44发布

问题:

First, I have a list of QWidgets that I won't know the length of until runtime. I then create a QListWidget where I show them and when someone clicks them I use the signal currentItemChanged(QListWidgetItem*, QListWidgetItem*) to catch it and get the clicked item's index.

Now I want to do a similar thing in the QMenu. I will know the list when the QMenu and its actions get built, but I won't be able to hard code this.

How can I create actions, catch their signals and connect them to the same slot which does different things depending on the action's position (index) in the menu list? There must be some way to solve this since other applications use this. I tried to look at mapping but I couldn't get my head around how to use it for this.

I tried to grab the sender in the slot but was not able to get any useful information from it.

回答1:

You can associate an index (or any other data) to each action when they are created with QAction::setData and connect the signal QMenu::triggered(QAction*) to your slot.

You'll then be able to retrieve the data through the QAction::data() function of your slot parameter.

MyClass::MyClass() {
    // menu creation
    for(...) {
        QAction *action = ...;
        action->setData(10);
        ...
        menu->addAction(action);
    }
    // only one single signal connection
    connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(mySlot(QAction*)));
}

void MyClass::mySlot(QAction *action) {
   int value = action->data().toInt();

}

Other methods: signal mapping or the use of sender(), are explained in that article of Qt Quaterly.



回答2:

A more generic (not specific to QMenu) way to approach this is the QActionGroup class. This allows you to isolate specific menu items as a related group, or group different widgets together.

void MyClass::InitMenu(QMenu* menu)
{
    QActionGroup* actions1 = new QActionGroup(menu);
    actions1->setExclusive(false);
    actions1->addAction(menu->addAction(tr("Action1")))->setData(1);
    actions1->addAction(menu->addAction(tr("Action2")))->setData(2);
    actions1->addAction(menu->addAction(tr("Action3")))->setData(3);
    actions1->addAction(menu->addAction(tr("Action4")))->setData(4);
    actions1->addAction(menu->addAction(tr("Action5")))->setData(5);
    connect(actions1, SIGNAL(triggered(QAction*)), SLOT(MySlot(QAction*)));

    QActionGroup* actions2 = new QActionGroup(menu);
    actions2->addAction(menu->addAction(tr("Undo Action1")))->setData(1);
    actions2->addAction(menu->addAction(tr("Undo Action2")))->setData(2);
    //...
    connect(actions2, SIGNAL(triggered(QAction*)), SLOT(MyUndoSlot(QAction*)));
}

and in the slot:

void MyClass::MySlot(QAction* triggeredAction)
{
    // use either the action itself... or an offset
    int value = triggeredAction->data().toInt()
}


回答3:

You can also have a QMap of QActions and ints and as soon as you add your action to the menu you can also add it to your map with a value that is +1 different from the previous one. You can then wire QAction::triggered to a generic slot, from where you can get the sender of the signal by calling sender(), dynamic cast it to a QAction and then look up with value in your map:

class MyClass {
public:
    void Init();
private slots:
    void onTriggered();
private:
    QMap<QAction*, int> _actionToInt;
}


MyClass::Init() {
    QMenu* menu = new QMenu();
    // Loop for illustration purposes
    // For general purpose keep an index and increment it every time you add
    for(int i=0; i<10; ++i) {
        QAction* action = menu->addAction("Item1");
        _actionToInt.insert(action, i);
        connect(action, &QAction::triggered, this, &MyClass::onTriggered);
    }
}

void MyClass::onTriggered() {
    QAction* action = qobject_cast<QAction*>(sender());
    //For safety purposes
    if (action && _actionToInt.contains(action) {
        //And here you have your index!
        int index = _actionToInt.value(action);
    }
}


标签: c++ qt slot qmenu