Sometimes I get very frustrated with the online Qt4 documentation. Sure, the API docs are massive but for many parts they provide only very basic information. Unfortunately, many Qt books, too, often stop exactly at the point where it gets interesting.
One example for this are context menus. The API docs just show you how menus in general are created and how they are connected to the application: Basically, all menus are instances of QMenu which are filled with instances of QAction. QActions are used as representation of any kind of action than can be triggered from the GUI.
The standard method to connect QActions to the GUI controlling code is to use one of their signals, e.g. triggered(). This signal can be connected to a slot of your own class where you can then execute the corresponding action. This works fine as long as you have a limited set of actions that you all know at coding time. For example, a menu in your tool bar which contains actions Undo/Redo/Cut/Copy/Paste can be created very easily.
But there are use cases where you do not know in advance how many actions there will be in your menus. For example, in an application that provides a GUI for composing a complex data structure you may want to provide the user assisting context menus for adding new data parts depending on what parts already exist. Suddenly, you have to connect many actions to one slot and then you somehow have to know which QAction the user actually clicked.
Btw, let’s all recall the Command Pattern for a moment… ok, now on to some solutions.
Method 0 – QAction::setData: The QAction class provides method setData(), which can be used to store custom data in a QAction instance using QVariant as data wrapper. If you then use QMenu’s triggered signal, which gives you a pointer to the QAction that was clicked, you can extract your data from the QAction. I find this a little bit ugly since you have to wrap your data into QVariant which can get messy, if you want to provide more than one data element
Method 1 – Enhancing QAction::triggered(): By sub-classing QAction you can provide your own triggered() signal which you can enhance with all parameters you need in your slot.
class MyAction : public QAction { Q_OBJECT public: MyAction(QString someActionInfo) : someActionInfo_(someActionInfo) { connect(this, SIGNAL(triggered()), this, SLOT(onTriggered())); } signals: void triggered(QString someActionInfo); private slots: void onTriggered() { emit triggered(someActionInfo_); } private: QString someActionInfo_; };
This is nice and easy but limited to what data types can be transported via signal/slot parameters.
Method 2 – QSignalMapper: From the Qt4 docs on QSignalMapper:
This class collects a set of parameterless signals, and re-emits them with integer, string or widget parameters corresponding to the object that sent the signal.
… which is basically the same as we did in method 1.
Method 3 – Separate domain specific action classes: By the time the context menu is created you add QActions to the menu using QMenu’s addAction methods. Then you create instances of separate Command-like classes (as in Command Pattern) and connect them with the QAction’s triggered() signal:
// Command-like custom action class. No GUI related stuff here! class MySpecialAction : public QObject { Q_OBJECT public: MySpecialAction(QObject* parent, <all necessary parameters to execute>); public slots: void execute(); ... }; // create context menu QAction* specialAction = menu->addAction("Special Action Nr. 1"); MySpecialAction* mySpecialAction = new MySpecialAction(specialAction, ...); connect(specialAction, SIGNAL(triggered()), mySpecialAction, SLOT(execute()));
As you can see, QAction specialAction is parent of mySpecialAction, thereby taking ownership of mySpecialAction. This is my preferred approach because it is the most flexible in terms of what custom data can be stored in the command. Furthermore, the part that contains the execution code – MySpecialAction – has nothing at all to do with GUI stuff and can easily be used in other parts of the system, e.g. non-GUI system interfaces.
How have you solved this problem?
Your first approach leaks memory. QAction has a non-virtual destructor and parenting it to some other QObject will delete it polymorphically. Tough luck.
Scratch this. It inherits from QObject which of course has a virtual destructor. Move along. Nothing to see here.