Action!

Working with the menu bar, context menus and tool bars with Qt require a bit of planning and coding. Sure it is more fun and fast doing that graphically by Qt Designer, but doing it in the hard way, writing any required piece of code, it is useful to understand how it actually works.

So, let's say I want to put on my main window a File menu on the menu bar with just two commands: one to display a different message on the central widget (that is just a label), and one to terminate the application.

Then I want a context menu acting when I right click on the central widget, proposing a very simple popup menu containig just one item, the command to put a different message in the label.

And finally I want to create a tool bar where I'm going to put again the new message command.

All this stuff is not complicated, it just need to be done step by step.

First step: slots. Remember that a slot, in the Qt jargon, is a function that could be connected to a signal, to be executed consequentely. In this case we want just two of them: one for putting a new text on the label in the central widget and one for closing the application. For the latter there is nothing to do, there is already a QMainWindow::close() slot, we just have to use it. But still we have to create a slot for the first one. Let's call it newMessage() and declare it as a private slot in our class definition:

// ...

class WinMain : public QMainWindow
{
Q_OBJECT
public:
explicit WinMain(QWidget* parent = 0);

// ...

private slots:
void newMessage();
};

Then we implement it like this:

void WinMain::newMessage()
{
QLabel* pLb = dynamic_cast<QLabel*>(centralWidget());
if(pLb == 0)
QMessageBox::information(this, this->windowTitle(), "No label available", QMessageBox::Ok);
else
pLb->setText("Hello again Qt!");
}

I am quite sure that our central widget is actually a label, but better be a little paranoid, when it doesn't hurt, and besides, in the future someone could change the code for some unforseen reason, and could actually happen this function would be called when no central window is set, or when it refers to something else than a QLabel. So I perform a dynamic cast on the central widget object and I check if it succeed. In case of failure I display a pretty useless information message to the user, so he could aptly complain about that with the programmer. If everything works as expected we'll change the message in the label. It would be more useful to ask the user what message he actually wants to display, but that would be too much work for this post, we'll do that in the next one.

We can pass now to the second step: actions.

The class QAction is acting as a controller in our context. It mediates between the graphic representation (a menu item) and the actual slot we want to execute. Besides, it contains all the information that the view requires to display itself properly.

But I think it is easier to actually see the code for our two actions than talking more on it:

QAction* actNew = new QAction(tr("&New"), this);
actNew->setIcon(QIcon(":/images/new.png"));
actNew->setShortcut(QKeySequence::New);
actNew->setStatusTip(tr("Set new message text"));
connect(actNew, SIGNAL(triggered()), this, SLOT(newMessage()));

QAction* actExit = new QAction(tr("E&xit"), this);
connect(actExit, SIGNAL(triggered()), this, SLOT(close()));

The first action is a bit complex. It has an associated icon, a shortcut and even a status tip for being showed in the status bar, when available. But we don't really need always all this stuff and, as we see for the second action, we could just give the action a name - using, if we want, an ampersand to specify the key for speed selection - and then connect the triggered() signal of the action itself with the slot we want to execute.

The third step requires we create widgets that actually use our actions.

Here is what we do for the menu bar:

QMenu* pFMenu = menuBar()->addMenu(tr("&File"));
pFMenu->addAction(actNew);
pFMenu->addSeparator();
pFMenu->addAction(actExit);

The function QMainWindow::menuBar() makes available the window menu bar. We call on it the addMenu() function, specifying the name of the menu we want to create, and a new menu is born.
Then we add the actions we want to display in the menu and, as we see, we can even add a separator.

There are different ways to create a context menu, but probably the easiest one requires to set the context menu policy of the widget to ActionsContextMenu and simply add the actions we want to display to the widget itself.

Here is what to do to add the context menu to the central widget:

if(centralWidget())
{
centralWidget()->addAction(actNew);
centralWidget()->setContextMenuPolicy(Qt::ActionsContextMenu);
}

Again a (paranoid) check on the central widget. In any case, checking it is very cheap, and that could save us from an embarassing core dump.

Finally, let's have a look at the tool bar:

QToolBar* pTB = addToolBar(tr("&File"));
pTB->addAction(actNew);

A window could have many tool bars. To add a new one we just call the QMainWindow::addToolBar() function, and then add the action we want to the returned QToolBar.

No comments:

Post a Comment