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.
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.
Name | Text | menuText |
aExit | Exit | E&xit |
aAddContact | Add Contact | &Add Contact |
aEditContact | Edit Contact | &Edit Contact |
aRemoveContact | Remove Contact | &Remove Contact |
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.
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).
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.
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.
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() |
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 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.
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
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.
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();
}
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 ) );
}
}
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;
}
}
}
}
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;
}
}
}
}
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();
}
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.
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.
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.
This is a part of digitalfanatics.org and is valid XHTML.
Copyright (c) 2002-2004 by Johan Thelin (e8johan -at- digitalfanatics.org). This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 (local copy) or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.