Friday, March 18, 2011

Qt Creator Basics

Due to requests (actually only one) this post is about basic stuffs of Qt Creator. I won’t teach you C++, I will presume that you have a previously knowledge of it. What will be boarded in this chapter is how to use one of the coolest features of the Qt API: Signals and Slots (read: event oriented programming). If you're familiar to Java it will sound completely natural to you, it's exactly the same principle of the widely known Listener interface (and the stuffs that come along) in Java.

I will try to demonstrate how to use them with really simple and straightforward examples, combining both graphical interface and event oriented programming (actually graphical interface walks together with event oriented programming, but the opposite is not necessarily true). Keep in mind that the Qt's API reference is mandatory, you must keep it open all time - do it now, just click here.

We are going to build a graphical Hello World. You'll learn how to grab a button's pointer from the Window and link it's events (named signals, as clicked, hoved, etc) to a method (named slot) that will properly handle the action - in our example we will display a simple message box saying “Hello World". I recommend strongly that you use MVC design pattern. For our example where there is no Model at all it's not really needed, but you better get used to it from now on because things may get really confusing if you don't separate stuff properly.

To illustrate why we need to use MVC, imagine the following example: a programm that starts an engine through a button click on the graphical interface. Suppose initially that you have a window class, a car class and inside it an engine class. Where the method that starts the engine (Model) will relly on when the button (Viewer) is clicked? On the window class? Probably not, makes no sense that it have to have knowledgement of how the car works, it isn't it's role. On the car or engine class? Once again it wouldn't make sense, the model can't be concerned about how the interface is implemented, simply because any changes on it would mean that it's code should be rewritten too. What we need is a class that communicates with both worlds (Viewer and Model) and at the same keep that keeps both unknown to each other, thus remaining totally independent - we need a Controller class.

Alright, enough talk, let's get our hands dirty. Let's begin creating a new Project. To do this, click on File -> New File or Project, a window like the one bellow will be shown.

New Project window

On Projects select Qt C++ Project (left screen side) and on the right side select Qt Gui Application. Click Choose... Another window be shown, on the Name field type in HelloWorld, on Create In type in the directory where your projects are stored. Click the Next button until the project is created (more three clicks), and then finally click the Finish button.

Project type selection

Now the most basic files for a GUI projects come to existence, and your project explorer will look like this:

Project explorer right after creation.

Now, as said before on that long discussion about MVC design pattern, we have to create a Controller class. To do this, right click on the HelloWorld root folder, then select Add New.... A new window will be shown, under Files and Classes select C++, on the right side select C++ Class. Click Choose....

Adding the class Controller

On Class name type Controller. Observe that Header File and Source File fields are automatically filled. Your window will look like this:

Add caption

Just click Next once and then Finish. Done, now you have a Controller class in your project. Ok, we have all the classes that we need, a MainWindow class that was created simultaneously with the project and a Controller class that we have just created and added to the project.

We are going to insert a button on the window. Double click the mainwindow.ui on the project explorer. The project explorer will be closed and will give place to a list of visual components that can be dragged into the window - and that's exactly what we are about to do - place a PushButton into the window by dragging it from the visual components list.

PushButton being dragged into the window.

Ok, our button is placed on the window. It's caption is PushButton and we don't want that, what we want is a more meaningful name to correspond the action that it triggers when clicked. You have two options to change it: just double click it and change directly over the button (easiest way) or click once an observe what happens on the right side of your screen - there is a property window (this way is a little bit harder, but much more richer and will give you an snapshot of what you can modify in a button), which shows all the properties of the component currently selected. Scroll it down until you find the text property. Double click it and change to Hello World!.

Button's text property being altered to "Hello World!"

We need now to link the button's click event (signal) to a method (slot) that will handle it. But first the Controller class must be able to handle signals and slots. To make it able, just make it inherit from QObject. In addiction, you must add Q_OBJECT macro to the beggining of your class. This will tell QMake (a piece of software that runs before the real compiler) that this class needs to be parsed, and then QMake mounts the actual file that will be compiled by gcc further. So your Controller class (controller.h) must look like this:
#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QObject>

class Controller : public QObject
{
    Q_OBJECT

    public:
        Controller();
};

#endif // CONTROLLER_H
If you try to compile it now, you will get the following error: undefined reference to `vtable for Controller'. This is because the Controller's files generated by QMake are out of date. You must run QMake manually to re-parse them, just click Build -> Run qmake.

Wait a minute... I said so much about MVC... This thing still doesn't look MVC. Why? Because Controller class doesn't know about the existence of the MainWindow class. If you take a look at main.cpp you'll see that the MainWindow class is being instantiated in there - this is not what we want. It's pretty easy to go around this little problem. Just remove MainWindow's instantiation from the main.cpp. We are going to place it where it should be: inside of Controller class. Don't forget that Controller must keep stored a reference of MainWindow after it's instantiation, otherwise we won't be able to handle it during the rest of the program's execution (not even mentioning that it would be destroyed right after the end of the constructor method). Your controller.h will look like this:
#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QObject>
#include "mainwindow.h"

class Controller : public QObject
{
    Q_OBJECT

    public:
        Controller();

    private:
        MainWindow *mainWindow; // MainWindow's reference!

};

#endif // CONTROLLER_H
And controller.cpp:
#include "controller.h"

Controller::Controller()
{
    this->mainWindow = new MainWindow();
    this->mainWindow->show();
}
Well, if we removed MainWindow's instantiation from the main.cpp and placed inside of Controller's constructor it means that MainWindow will be built when Controller is. But who will instantiate the Controller class? As main.cpp were instantiating MainWindow class before, now it must instantiate the Controller class. Your main.cpp will look like:
#include <QtGui/QApplication>
#include "controller.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Controller controller;
    return a.exec();
}
I bet you're curious to see your app running. It's true that it still doesn't do anything, but to see it running for the first time is always exciting. Go ahead, click the green arrow to compile and run (at least if you followed all the steps until here religiously you should be able to).

Let's review what we did until this point:
- We built our project, which initially came just with a MainWindow class and a main.cpp. We have...
- ...added a Controller class in order to turn in an MVC program.
- ...placed a button into the window and changed it's name.
- ..."fixed" the instantiation of the MainWindown class, moving it from the main.cpp to the Controller's constructor.

What's missing: to link button's click action (signal) to a method (slot) to handle it.
That's exactly what we are going to do now. The Controller class will be responsible for this connection between the Viewer and the action of showing a message box (remember, to keep simple our project has no Models class).

To do that we have in first place to grab a button reference in order to link it to the slot. Click on mainwindow.ui at the file explorer. The designer interface will be opened again. Take a look at the right upper side of your screen. It will look like this image bellow:


Take a look at the hierarchy of the visual components that live in the MainWindow class - this will give us a clue of the "path" that has to be followed to grab the button. At Controller::Controller() method, type:
QPushButton *pushButton; 
pushButton = this->mainWindow->centralWidget()->findChild<QPushButton *>("pushButton");
Don't forget to type at the top of controller.cpp:
#include <QPushButton>
Right, now we have a reference to the button. We need a method on Controller to link button's click signal to it. Type on controller.h:
private slots:
void pushButtonAction();
And on controller.cpp:
void Controller::pushButtonAction()
{
   
}
For now we won't worry about the pushButtonAction() content, let's just connect it to the button's click signal. Once again at Controller::Controller(), type:
connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonAction());
Alright! Now our pushButtonAction() slot will be called whenever the button is clicked. We are almost there. We still need to type some code inside the slot to make a message box pop on the screen.

Inside the pushButtonAction(), type:
QMessageBox message;
message.setText("Hello World!");
message.exec();
Again, don't forget to type at the top of controller.cpp:
#include <QMessageBox>
That's all! Compile, run, click the button and watch the message box being displayed.



3 comments: