7. The Address Book

This will be our first real application: an address book. We will design and implement the basic classes and the user interface in this chapter.

Let us begin by creating an empty C++ project in Qt Designer. I choose to call my project adbook.pro, but you can choose any name.

The first thing that we want to do is to add a new Main Window. The window we will use is supposed to be empty, so make sure to uncheck all alternatives in the wizard as shown in figure 7-1.

The Main Window Wizard with no options checked

Figure 7-1 The Main Window Wizard with no options checked.

Now change the name of the form to frmMain and set the caption to 'Address Book'.

Instead of connecting buttons and such to the slots directly as we have done this far we will use action groups. This gives us one signal to handle for multiple events such as a menu item, a keyboard accelerator (i.e. a short-cut) and a toolbar button. Add four actions using the new action button (highlighted in figure 7-2) according to table 7-1. To do this, simply click the new action button, then alter the action using the property editor. The resulting actions are shown in figure 7-2.

The &-characters tells Qt to underline the following character and to use it as a keyboard shortcut.

The new action button

Figure 7-2 The new action button.

Name Text menuText
aExit Exit E&xit
aAddContact Add Contact &Add Contact
aEditContact Edit Contact &Edit Contact
aRemoveContact Remove Contact &Remove Contact

Table 7-1

The actions

Figure 7-3 The actions.

Click with the right mouse button on the form to bring up the context menu. From this, select the add menu item entry. Do this twice so that you end up with a form containing two menu entries.

Now it is time to rename the menu items. In early 3.x Qt versions this is done by clicking with right mouse button on each of the newly created menu entries and picking the rename option from the context menu. In later versions, single click on the item and use the property editor to change the name. Either way, rename them to "&File" and "&Contacts" from the left to the right.

Now, simply drag and drop the actions to the menus. Place the aExit action to the File menu and the rest of actions to the Contacts menu. Figure 7-4 shows the result.

The Contacts menu

Figure 7-4 The Contacts menu.

Now, add a list view control to the form and name it lvContacts. Change the allColumnsShowFocus property to True. When this has been done, right-click on the list view and choose to edit it. Clear the list from items (figure 7-5) and create the columns "Name", "E-Mail" and "Phone Number" (figure 7-6).

The list view items

Figure 7-5 The list view items.

The list view columns

Figure 7-6 The list view columns.

To make the list view the main widget of the form, select the form and apply a grid layout. In the properties, alter the layoutMargin to zero to make the list view to reach the edges of the form. The resulting form is shown in preview mode in figure 7-7.

The main window in preview mode

Figure 7-7 The main window in preview mode.

Now it is time to add some functionality to the project. We start by adding the following private slots to the form: init(), addContact(), editContact() and removeContact(). The resulting slot editing dialog is shown in figure 7-8.

The slots

Figure 7-8 The slots.

Click on the connect current action button in the action editor and connect the signals as shown in table 7-2. To verify that this has been properly done, right-click on the form (for example on the menu bar outside any menu items) and select Connections from the context menu. The resulting dialog should look something like figure 7-9. (Please notice that the look of this dialog differs slightly between different Qt versions. The contents is the same in all versions that I have seen).

Source Signal Slot (always of frmMain)
aExit activated() close()
aAddContact activated() addContact()
aEditContact activated() editContact()
aRemoveContact activated() removeContact()

Table 7-2

The connections

Figure 7-9 The connections.

The next step is to create a dialog to edit new and old contacts. Figure 7-10 shows what the dialog will look like when it is done.

The contact editing dialog

Figure 7-10 The contact editing dialog.

The instructions for the creation of this dialog are brief as all the steps have been described in chapter 5.

Begin by adding a new dialog to the project. Name it dlgContact and set the caption to Contact. In the dialog, add two push buttons and name them pbOk and pbCancel. Then add a group box. In the group box, add three labels and three line edits. Name the latter to leName, leEMail and lePhone. Do not forget to add two spacers as shown in figure 7-11. Also, set the caption of the buttons and labels according to what is shown in the figure.

The widgets before applying the layouts

Figure 7-11 The widgets before applying the layouts.

Now, we will continue with the layouts. Select the line edits and apply a vertical layout. Do the same thing to the labels. Then select the buttons and the horizontal spacer and apply a horizontal layout. Now, select the frame and apply a horizontal layout. Finally, select the form and apply a vertical layout. All layout work that is left now is to resize the dialog so that it looks good.

Over to the signals and slots. All that we have to do is to connect the push buttons' clicked() signals to the accept and reject slots of the dialog. After you've done this, the dialog is ready. This shows how quick and easy it is to create a simple dialog.

Before we can add any code to the main window we need a structure to hold our contacts. Actually, it is not necessary with the functionality that we are going to introduce in this chapter, but it will be used later. Now, add a C++ header file to the project and put the code from example 7-1 in it and save it as contact.h.

#ifndef CONTACT_H
#define CONTACT_H

#include <qstring.h>

class Contact
{
public:
  QString name,
    eMail,
    phone;
};

#endif

Example 7-1

In order to be able to use this structure from our main window it has to be included. We will put the contacts in a QValueList, so we need to declare the list as QValueList<Contact> m_contacts in the class variables list. There are three more files that have to be included, the dialog - dlgcontact.h, a message box - qmessagebox.h and the line edit class - qlineedit.h. If the line edit file is omited it will not be possible to access any line edit members from code in the main window since the main window not contains and line edits it self. All widgets in a window, dialog or composed widget will automatically be included. All the inclusions and the declaration are shown in figure 7-12.

The file inclusions and declarations

Figure 7-12 The file inclusions and declarations.

Finally the time for some "active" code has come. Lets begin by initializing the dialog in the init() slot. The code shown in example 7-2 simply clears the list view. This is not necessary, but it shows where to put initialization code.

void frmMain::init()
{
  // Clear the list
  lvContacts->clear();
}

Example 7-2

Next, we add the ability to add contacts. Example 7-3 holds the code and it is pretty straight forward and shows how dialogs can be used efficiently. First initialize, the show it, if the result is accepted, check the data and take action. In this case we add a new contact to our list.

void frmMain::addContact()
{
  // Create a new dialog
  dlgContact dlg( this );

  // Show it and wait for Ok or Cancel
  if( dlg.exec() == QDialog::Accepted )
  {
    // We got Ok
    Contact c;

    // Extract the information from the dialog
    c.name = dlg.leName->text();
    c.eMail = dlg.leEMail->text();
    c.phone = dlg.lePhone->text();

    // Check for duplicate names
    for( QValueList<Contact>::iterator it = m_contacts.begin(); it != m_contacts.end(); ++it )
    {
    if( (*it).name == c.name )
      {
        // Warn for duplicates and fail
        QMessageBox::warning( this, tr("Duplicate"), tr("The person ") + (*it).name + tr(" allready exists.") );
        return;
      }
    }
    // No duplicates found, add the contact to the list and show it in the listview
    m_contacts.append( c );
    lvContacts->insertItem( new QListViewItem( lvContacts, c.name , c.eMail, c.phone ) );
  }
}

Example 7-3

This is alot of code at once, so here comes the explanation. The links lead to the official Qt documentation so you can follow them for further details.

The first thing we do is to instanciate our QDialog sub class, dlg. The call to the exec() method will wait for the dialog to close and returns a status. We are interested in knowing if the dialog result was accepted by the user, hence the if statement.

The Contact, c, is filled out with the information from the dialog. We get the text from the line edits using the text() method.

After that a Qt container, QValueList, is used. Qt is compatible with STL, but Qt's containers are more versitile. If a duplicate is found, a warning dialog is shown using the warning() method of QMessageBox.

Finally, the contact is added to the list of contacts and a new QListViewItem is added to the list view.

We continue with the code in example 7-4. It allows us to remove contacts from the list. Please notice how easy it is to remove items from a list view, just delete the item. There is no need to worry about freeing memory and such thanks to Qt.

void frmMain::removeContact()
{
  // Get a pointer to the selected item
  QListViewItem *item = lvContacts->currentItem();

  // Check if there is a valid select item
  if( item )
  {
// Find the corresponding item in the internal list
    for( QValueList<Contact>::iterator it = m_contacts.begin(); it != m_contacts.end(); ++it )
    {
      if( (*it).name == item->text(0) )
      {
        // We have found it, remove it from the list and listview
        m_contacts.remove( it );
        delete item;
        return;
      }
    }
  }
}

Example 7-4

Now we only miss code for one more slot. Example 7-5 shows the code used to edit contacts. It can be seen as a blend of the adding and removing cases. It first locates the selected entry, setups the dialog, shows it and alters the entry if everything is ok. The duplicate test algorithm might need a short comment. If the name has been changed, then the list cannot hold any duplicates, but if the name is the same, then we allow one occurence which is the original entry.

void frmMain::editContact()
{
  // Get a pointer to the selected item
  QListViewItem *item = lvContacts->currentItem();
  
  // Check if there is a valid select item
  if( item )
  {
    // Find the corresponding item in the internal list
    for( QValueList<Contact>::iterator it = m_contacts.begin(); it != m_contacts.end(); ++it )
    {
      if( (*it).name == item->text(0) )
      {
        // We have found it, show the dialog with it
        dlgContact dlg( this );
        
        dlg.leName->setText( (*it).name );
        dlg.leEMail->setText( (*it).eMail );
        dlg.lePhone->setText( (*it).phone );
        
        // Show it and wait for Ok or Cancel
        if( dlg.exec() == QDialog::Accepted )
        {
          // Check for duplicate names by counting the number of occurencies
          int occurencies = 0;
          for( QValueList<Contact>::iterator i2 = m_contacts.begin(); i2 != m_contacts.end(); ++i2 )
            if( (*i2).name == dlg.leName->text() )
              occurencies++;
          
          // If the name is changed we allow no occurencies, otherwise one
          if( (dlg.leName->text() != (*it).name && occurencies > 0) || (occurencies > 1) )
          {
            // Warn for duplicates and fail
            QMessageBox::warning( this, tr("Duplicate"), tr("The person ") + dlg.leName->text() + tr(" allready exists.") );
            return;
          }
          
          // Update the internal list
          (*it).name = dlg.leName->text();
          (*it).eMail = dlg.leEMail->text();
          (*it).phone = dlg.lePhone->text();
          
          // Update the list view
          item->setText( 0, (*it).name );
          item->setText( 1, (*it).eMail );
          item->setText( 2, (*it).phone );
        }
        
        return;
      }
    }
  }
}

Example 7-5

Before we can produce a working application we need to add a trivial main() function. Add a new C++ source file named main.cpp to the project and put the code from example 7-6 in it. The code shows a main window and waits for the last, i.e. the one, main window to be closed.

#include <qapplication.h>

#include "frmmain.h"

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  frmMain *m = new frmMain();
  m->show();

  a.connect( &a, SIGNAL(lastWindowClosed()), &a, SLOT(quit()) );

  return a.exec();
}

Example 7-6

Figure 7-13 shows the resulting application in action. To build it, please run qmake adbook.pro && make from a console, then start the resulting application and enjoy.

The application in action

Figure 7-13 The application in action.

Main window, dialog and widget
Many new Qt users are confused by the variety of methods to show a window with some widgets in it. Qt's first examples uses a widget directly from the main() function while others use dialogs. Add to this the option to use a main window and the confusion is total.

To make a long story short, use main windows in big applications and dialogs in small applications. If you want my reasons for this, read on.

The ability to use widgets directly from a main() function is very useful when testing widgets and writing really small applications without any active code at all. As soon as you want to add slots for actions such as adding a new entry, clearing a list or performing some other application specific action the widget approach should be avoided. As most useful applications provides some function, this can be put as do not use widget directly unless you are testing the widget itself.

Dialog based applications are great since they are easy to put together and can be used to perform some simple task not requiring a main window. It is not necessary to limit a dialog-based application to one dialog, but as soon as you begin to have a dialog showing a "document" on which the other dialogs work you should really consider using a main window instead.

As stated in the preceeding paragraph a main window approach is recommended as soon as you start working on a "document". This does not mean an actual WYSIWYG (What You See Is What You Get, like any modern word processor) text document, but rather a data collection (as in the model - view - controller, MVC, design pattern). An example of a document is the list of contacts used in the addressbook. There are several advantages of using a main window approach. The one that I like the most is that it is easy to create applications allowing the user to work with several documents, simply create new instaces of your main window. This means that each document gets a window of its' own and this is the approach that human-computer interaction research recommends. Until recently most windows applications such as Office used an MDI interface (lots of documents in one big main window), but in later versions a SDI interface (one window per document). An SDI interface is what you get from using the main window approach.

Summary

The example code for this chapter can be found here.

This chapter showed how a complete application is built using a main window and a dialog to work with the "document" presented in the main window. We also clearified the main window versus dialog choice.

Exercises

  1. How do you add, alter and remove items in a list view?
  2. For what application do you choose a main window, dialog or widget approach? A word processor? A ispell front-end? A drawing application? A file renamer? Why?
  3. In the addressbook application, add a field called ICQ# to each contact and show it in the list view.

This is a part of digitalfanatics.org and is valid XHTML.