Table Of Contents

Previous topic

3. Reading an image from a file

Next topic

5. DrawQt

Download

4. Managing a graphical interface with Qt

spot

This session will detail in a step-by-step fashion how to write an application leveraging the Qt framework and then how to modify a graphical user interface such as DrawQt in order to add buttons which select various and different graphical shapes.

You should strive for systematically using all the tools you know, such as:

  • CMT
  • svn
  • Doxygen and the proper syntax to decorate your code with comments (which are then picked up by Doxygen)

4.1. Step 1: a first Qt application (5 min)

This first exercize is a variation over hello world.

We start with configuring the svn environment (replace the ens<n> with your team id. e.g. ens2)

$> cd ~/Project
$> svn mkdir https://svn.lal.in2p3.fr/projects/Etudiants/ens<n>/TpQt
$> svn mkdir https://svn.lal.in2p3.fr/projects/Etudiants/ens<n>/TpQt/trunk \
             https://svn.lal.in2p3.fr/projects/Etudiants/ens<n>/TpQt/branches \
             https://svn.lal.in2p3.fr/projects/Etudiants/ens<n>/TpQt/tags \
             -m "Create TpQt project"
$> svn co https://svn.lal.in2p3.fr/projects/Etudiants/ens<n>/TpQt/trunk TpQt

For this exercize, we’ll need the Interfaces package to gain access to the system libraries as well as the Qt environment. If the directory Project/Interfaces isn’t already present, create it like so:

$> cd ~/Project
$> svn export https://svn.lal.in2p3.fr/projects/Enseignement/LAL-Info/tags/head/Interfaces \
              Interfaces

Then, create a new project with CMT:

$> cd ~/Project
$> cmt create TpQt v1
$> cd TpQt
$> svn add cmt src

The last piece of boilerplate is the cmt/requirements file which should look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package TpQt

# to gain access to doxygen
use Platform v1r* ${HOME}/Project/Interfaces

# for the Qt environment
use Qt v2r*   ${HOME}/Project/Interfaces

# tools for the build (shared libraries, rules, ...)
use dld v2r*   ${HOME}/Project/Interfaces

# add our directory of headers for the compiler
include_dirs $(TPQTROOT)/include

# rule to create a 'moc' file (needed for the code generation for 
# the signal/slots events)
document moc moc_myWindow   \
         FROM=../include/myWindow.h \
         TO=../src/moc_myWindow.cpp

# describe how the library holding our class shall be built
library TpLib \
        ../src/myWindow.cpp \
        ../src/moc_myWindow.cpp

# arguments and options for the compilation and link of 'myWindow' class
macro lib_TpLib_cflags   " ${Qt_cflags}"
macro lib_TpLib_cppflags " ${lib_TpLib_cflags}"
macro TpLib_shlibflags   " ${Qt_linkopts} ${dld_linkopts}"
macro TpLib_linkopts     " -L${TPQTROOT}/$(Platform_bin) -lTpLib" \
    WIN32                  " ${TPQTROOT}\$(Platform_bin)\TpLib.lib"

macro_append QtTestlinkopts " ${TpLib_linkopts}"

# describe how the QtTest.exe application shall be built
application QtTest QtTest.cpp

# boilerplate needed to package the application for MacOS
document darwin_app TpQt   FROM=QtTest TO=../app/QtTest

# update the DYLD_LIBRARY_PATH environment variable 
# (needed by the MacOS dynamic linker to find our new shared library)
path_append DYLD_LIBRARY_PATH "" \
            Darwin "${TPQTROOT}/$(Platform_bin)"

# rule to create the documentation
document doxygen doc -group=documentation TO=../doc 

## EOF ##

Note

Notice the line:

use Qt v2r* Interfaces

at the beginning of the requirements file. This line tells CMT to retrieve (and use) the definitions and configurations relevant for the Qt/v2r* package which can be located thanks to the $CMTPATH environment variable. (this env. variable has been automatically defined for you when the Terminal is launched – thanks to the configuration put in the .zshrc profile file)

Now comes the task of creating the myWindow class. First the header file ../include/myWindow.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#ifndef TPQT_MYWINDOW_H
#define TPQT_MYWINDOW_H 1

// qt related includes
#include <QtGui/QMainWindow>
#include <QtGui/QPushButton>
 
/**
 * @file   myWindow.h
 * @author LAL ens <ens@lal.in2p3.fr>
 * @date   March 2007
 * 
 * @brief  first class HelloWorld in Qt
 * 
 * 
 */
class myWindow : public QMainWindow
{
  // the macro Q_OBJECT is mandatory to hint Qt with the signal/slot
  // communication between (instances of) classes.
  Q_OBJECT

  public:
 
  /** @brief Constructor of our main class
   * @param parent : Parent widget of the class. In our case this will be the
   * main window (so the parent will be "NULL") "NULL"
   * @param fl : Creation flags for the window. This is useful to create a
   * window which can't be resized, or without a 'quit' button, etc...
   */
  myWindow( QMainWindow* parent = 0, Qt::WFlags fl = Qt::Window );
 
  /** @brief Destructor.
   */
  virtual ~myWindow();
   
private:

  /** @brief The 'hello' PushButton
   */
  QPushButton* m_hello;
   
};
 

#endif // !TPQT_MYWINDOW_H

Then the implementation, which we save under ../src/myWindow.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
 * @file   myWindow.cpp
 * @author LAL ens <ens@lal.in2p3.fr>
 * @date   March 2007
 * 
 * @brief  first class HelloWorld in Qt
 * 
 * 
 */
 
// Interface
#include <myWindow.h>
 
/** @brief Constructor for the myWindow class
 *
 * Our class, in order to be a display window, needs to inherit from the 
 * @c QMainWindow class of the Qt framework.

 * @param parent : Parent widget of the class. In our case, this will be the
 * main window, so the parent is actually a NULL pointer.
 * @param fl : Creation flags for the window. This is useful to create a
 * window which can't be resized, or without a 'quit' button, etc...
 */
myWindow::myWindow( QMainWindow* parent, Qt::WFlags fl )
  : QMainWindow( parent, fl )
{
  // Create the push button
  m_hello = new QPushButton(...); 
  // To complete the above line, heed towards the Qt documentation
  // the documentation of QPushButton is available here:
  // http://doc.qt.digia.com/4.0/qpushbutton.html

  
  // display our button (in a central position)
  setCentralWidget( m_hello );
}
 
 
/**
 *  @brief Destroy an instance of myWindow (and all the objects it may hold)

 *  i.e. the button we created previously.
 */
myWindow::~myWindow()
{
  delete m_hello;
}

Eventually comes the main function, which is put in QtTest.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * @file   QtTest.cpp
 * @author LAL ens <ens@lal.in2p3.fr>
 * @date   March 2007
 * 
 * @brief  first HelloWorld en Qt
 * 
 * 
 */

// qt-related includes
#include <QtGui/QApplication>
#include <QtGui/QPushButton>
 
// local includes
#include "myWindow.h"
 
int main(int argc, char *argv[])
{
  QApplication app(argc, argv);
  myWindow * mw = new myWindow;
  mw->show();
  return app.exec();
}

Before being able to build and test our new application, we need to execute the cmt/setup.sh script which will correctly configure a few environment variables (Qt environment, libraries handling, ...) Heed towards the cmt directory and issue:

$> cmt config
$> source ./setup.sh

Now you can modify the myWindow.cpp file to make it compilable... Then:

$> cmt make
$> cmt make doc

Important

During the execution of these 2 commands and during this whole session (and all the following ones!) do NOT just look at their output without trying to analyze the messages logged on the screen. Do not forget that if a syntax error was inserted in your code or if a file is missing, these commands won’t clobber (nor replace) the previously built executable (or documentation), thus one might (wrongly!) be led to believe no modifications were taken into account by the build system...

Finally, we can test the application (via 2 methods):

  • from the Finder application (i.e. the file browser), launch the file ../app/QtTest.app, or
  • from your Terminal:
$> open ../app/QtTest.app

Warning

The text output from the standard output std::cout are not shown in a graphical application. Indeed, such an application works in a completely decoupled fashion from the Terminal. However, it is possible to retrieve these outputs via the system console:

$> open /Applications/Utilities/Console.app

This console is used for all the system outputs so it is hardly surprising if you find a great number of messages coming from other applications. It is possible to filter the messages (top-right), though. (beware: it isn’t case sensitive.)

We can then save and commit this first and initial version in our svn repository, after having removed the files we don’t care to track and version:

$> rm -r ../Darwin ../doc ../app
$> rm Darwin.make Makefile *.csh *.sh
$> svn add cmt src
$> svn commit -m "initial version" .

We can then go back under the src directory where all the source files we modify are located:

$> cd ./src
_images/validate-step.jpg

Validate this step


4.2. Step 2: attaching a signal/slot to a button (10 min)

As you may have noticed, no action was associated with the button of the previous application. In order to fix this issue, we’ll use the signal/slot mechanism of Qt.

  • the emitting object will be the hello button,
  • the signal being sent will be the mouse-click action,
  • the receiving object will be the myWindow class,
  • the slot to which the signal will be sent will be the close() method of the class QMainWindow.

To leverage all of this machinery, it’s sufficient to add the following line, just after the creation of the button, in myWindow.cpp:

1
2
3
 // Connection of the signal 'clicked()' (from the button)
 // to the slot 'close()' (of the QMainWindow)
 connect( m_hello, SIGNAL(clicked()), this, SLOT(close()) );

As usual, rebuild, test and then commit in the svn repository:

$> cmt make
$> cmt make doc
$> open ../app/QtTest.app
$> cd ..
$> svn commit -m "adding a signal/slot communication"

Note

It sometimes so happens that the compiler doesn’t completely take into account your last modification. In this case, the application will compile successfully but will crash at runtime (when you execute it.) This is because the automatically generated moc_myWindow file hasn’t been regenerated nor recompiled. To fix this, just delete it (in src) and recompile (it should be regenerated anew.)

_images/validate-step.jpg

Validate this step


4.3. Step 3: adding a menu (15 min)

We’ll now add a menu to our window.

Warning

On a Mac, menus are not displayed together with the window (at the top of the window as for most of the other graphical operating systems) but at the top of the screen.

Add the following includes in your myWindow.cpp file:

 #include <QMenu>
 #include <QMenuBar>

The following code is to be put in place of the previous version of the constructor of myWindow:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/** @brief Constructor for the myWindow class
 *
 * Our class, in order to be a display window, needs to inherit from the 
 * @c QMainWindow class of the Qt framework.

 * @param parent : Parent widget of the class. In our case, this will be the
 * main window, so the parent is actually a NULL pointer.
 * @param fl : Creation flags for the window. This is useful to create a
 * window which can't be resized, or without a 'quit' button, etc...
 */
myWindow::myWindow( QMainWindow* parent, Qt::WFlags fl )
  : QMainWindow( parent, fl )
{
  // Create the push button
  m_hello = new QPushButton("Hello world!"); 
  connect( m_hello, SIGNAL(clicked()), 
           this,    SLOT(close()) );

  // display our button (in a central position)
  setCentralWidget( m_hello );

  // create a menu bar
  QMenuBar *menubar = new QMenuBar(this);

  // create a "File" menu
  QMenu *fileMenu = new QMenu("File");

  // add this file menu to the main menu bar
  menubar->addMenu(fileMenu);

  // add a few items to the file-menu
  //  - an 'open' item
  fileMenu->addAction("Open");

  //  - a separator
  fileMenu->addSeparator();

  //  - a 'quit' item which we connect to the 'close' slot of the main window
  // Do not use the world "Quit" on mac OSX, see here why : 
  // http://doc.qt.nokia.com/4.7-snapshot/qmenubar.html#qmenubar-on-mac-os-x
  fileMenu->addAction("Bye application", this, SLOT(close()));

  // finally, add the menu bar to the main window
  setMenuBar(menubar);
}

Recompile, test and commit in svn.

_images/validate-step.jpg

Validate this step


4.4. Step 4: adding a new slot (15 min)

We wish now to add our own action when someone clicks on a menu.

Thus, we’ll first start by adding an action to our menu Open which we’ll connect to a method modify() (defined later on.)

After the creation of the menu Open, add the following lines:

1
2
 // add a new item "Modify" which we connect to the method modify()
 fileMenu->addAction( "Modify", this, SLOT(modify()) );

Then, we add the method modify() which will be called whenever the menu Modify is clicked on. This method will be called by an event (a click on the menu), so it isn’t a regular C++ method but really a new slot which we need to define.

In the myWindow.h header file, add the following lines in the body of the class:

1
2
 public slots:
  void modify();

Now, we need to implement it, in the myWindow.cpp source file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/** @brief This method modifies the text of the 'hello' button.

 * Documentation about @c QPushButton is available here:
 *   http://doc.qt.digia.com/4.0/qpushbutton.html

 * @c QPushButton is a special kind of button.
 * Indeed, buttons can be PushButtons, CheckButtons, RadioButtons, etc...
 * It inherits the properties of an abstract button type: QAbstractButton.

 * Documentation about @c QAbstractButton is available here:
 *   http://doc.qt.digia.com/4.0/qabstractbutton.html

 */
void myWindow::modify()
{
  // Complete this line according to the documentation
  m_hello->setText(...);
}

Rebuild, test and commit in svn.

_images/validate-step.jpg

Validate this step