הלינקייה: מגזין חודשי למפתחים

רוצה לשמוע על כל האירועים, המדריכים, הקורסים והמאמרים שנכתבו החודש ?
הלינקייה הינו מגזין חופשי בעברית שמשאיר אותך בעניינים.
בלי ספאם. בלי שטויות. פעם בחודש אצלך בתיבה.

Input Validation

One surprising fact about users is that they always seem to type in inputs the developer did not expect. It's not really their fault, though, just give someone a line edit to type in her name and try to extract the first and last name from the input.
The problems start with users having just one word in their name, a middle name, those adding a title before the name and so on.
Input validation is the developer's way to tell the world: Please type in your name, but use only two words. Although this won't help Madonna, at least she'll know what went wrong.

Qt developers have a wide range of input validators to choose from. In this article I'll review the various input validators available, and show the ways to help our users using QCompleter.

Mask Based Validation

The easiest way to validate input in a QLineEdit is to use a mask. A mask is a special string that tells the line edit what type of characters the user can type next. If a user types a character that is not in range, it is simply ignored by the line edit.

A mask has both normal characters and special character. Each special character represents a character group. For example, the following code creates a QLineEdit that takes a numeric value in the form: 1234-123-1234-123 (four digits, then a dash, then 3 digits, another dash, four more digits, a dash and end with 3 digits):

#include <QtGui/QtGui>

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

    QLineEdit e;
    e.setInputMask("9999-999-9999-999");
    e.show();

    app.exec();
}

The number 9 in the example above is a special character which stands for "a digit is required". There are also special characters for optional digit, alphabetic character, hexadecimal digit and more. Check the documentation of QLineEdit to get the full list.

Using masks is a simple way to communicate an input format to your users. It's useful for inputs with rigid formats such as IP addresses and serial numbers. For anything more complex, we need a more flexible solution: validators.

QValidator Based Validation

A validator is an algorithm class responsible for deciding whether a given input is valid or not. It is used by both QLineEdit and QComboBox.

There are 3 stock validators shipped with Qt: QIntValidator which validates integer values, QDoubleValidator for validating floating point values and QRegExpValidator which provides validation of any text using regular expressions. All stock validators are used in the same way: create them with the relevant range in the constructor, and assign the validator to the line edit.

Let's create an integer only line edit using a validator:

#include <QtGui/QtGui>

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

    QLineEdit e;
    QIntValidator *v = new QIntValidator(0, 100);
    e.setValidator( v );
    e.show();

    app.exec();
}

As you can see, a validator is its own object. We create it on the heap because QLineEdit takes ownership on the validators assigned to it. If you run the above code, you get a line edit that allows only integers between 0 and 100. When you type in anything that invalidates this rule, the line edit discards the offending character.

QIntValidator and QDoubleValidator are useful for any numeric field that needs some enforcements, such as user's age, number of players in a game, amount of products to buy and so on.

The most flexible of the validators is the QRegExpValidator. It takes on any regular expression and validates against it, so only texts which match the expression are accepted (If you don't know what regular expressions are, Zed Shaw has written a very good free ebook available at: http://regex.learncodethehardway.org/book/).

With regular expressions, you can validate almost anything. Here's the same QLineEdit taking only valid identifier names:

#include <QtGui/QtGui>

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

    QLineEdit e;
    QRegExp re("[_a-zA-Z][_a-zA-Z0-9]+");
    QRegExpValidator *v = new QRegExpValidator(re);

    e.setValidator( v );
    e.show();

    app.exec();
}

When working with validators, it's important to note that leaving your QLineEdit in the middle of the editing can lead to invalid input in the line edit. Take for example a QIntValidator testing for numbers 50-100. When someone types in 5, the validator lets it pass through although 5 by itself is not valid. This is called Intermediate value because it's not yet valid but may be valid in the next key press.

If the user now presses tab, or uses the mouse to leave the line edit, it will remain in intermediate state. A QLineEdit in intermediate state will not emit returnPressed or editingFinished signals.

Helping Your Users: QCompleter

Input validation is just one way to help your users understand the application. A nicer is to auto complete their text. Qt provides a class that automatically suggests relevant words based on the input received so far called QCompleter. Let's start with an example. The following line edit will auto complete names of various colors:

#include <QtGui/QtGui>

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

    QStringList colors;
    colors << "blue" << "red" << "green" << "black" << "white" << "blue" << "pink" << "purple";
    QLineEdit e;

    QCompleter *c = new QCompleter(colors, &e);
    e.setCompleter( c );
    e.show();

    app.exec();
}

Note: Notice that setCompleter does not take ownership on the QCompleter. It's recommended to create the completer on the heap with your main window or widget as the parent.

A QCompleter can take on any QAbstractItemModel as its source of information, so you don't need to initialize the list in advance. Some popular models include: QFileSystemModel for filesystem based completion, and QSortFilterProxyModel for filtering our only valid entries from a model. Developers can also write their own models, and take the information from a remote web service.

It's also possible (and recommended) to combine a QCompleter with a QValidator. For example, you can suggest to your users texts they have used in the past, while validating new texts match a regular expressions or mask. The following code does just that - every time a user types in a text and presses "Enter" we add that text to a custom model, so next time it's available for auto completion. In addition, A validator makes sure all texts will be lowercased.

To make it work, I used a QWidget derived class that handles editingFinished() signal and adds the new item to the model. Here's the code for the 3 files:

// autosuggestwidget.h

#ifndef AUTOSUGGESTWIDGET_H
#define AUTOSUGGESTWIDGET_H

#include <QWidget>

class QLineEdit;
class QStandardItemModel;

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

signals:

public slots:
    void addTextToCompleter();

private:
    QLineEdit *m_edit;
    QStandardItemModel *m_model;
};

#endif // AUTOSUGGESTWIDGET_H


// autosuggestwidget.cpp

[cpp]
#include "autosuggestwidget.h"
#include <QtGui/QtGui>

AutoSuggestWidget::AutoSuggestWidget(QWidget *parent) :
    QWidget(parent)
{
    QLayout *layout = new QHBoxLayout ( this );

    m_edit      = new QLineEdit( );
    m_model     = new QStandardItemModel( this );

// Create the auto completer and the validator
    QRegExp re("^[a-z]+$");
    QValidator *v = new QRegExpValidator(re, this);
    QCompleter *completer = new QCompleter( this );

// Set a standard item model as the source of auto complete
    completer->setModel( m_model );
    m_edit->setCompleter( completer );
    m_edit->setValidator( v );

    layout->addWidget( m_edit );

// editingFinished() is called whenever the user pressed "Return" or leaves the line edit
    QObject::connect( m_edit, SIGNAL(editingFinished()), this, SLOT(addTextToCompleter()) );
}

void AutoSuggestWidget::addTextToCompleter()
{
// A QStandardItemModel holds multiple QStandardItem. It also allows
// adding new items easily
    QStandardItem *item = new QStandardItem( m_edit->text() );
    m_model->appendRow( item );

// When the item was added, it's safe to clear the text box
    m_edit->setText("");
}


// main.cpp

#include <QtGui/QtGui>
#include "autosuggestwidget.h"

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

    AutoSuggestWidget w;
    w.show();

    app.exec();
}

Input validation is an important step in making your application accessible and friendly to more users. With Qt, validating input and communicating your intentions to users is easier than ever. By adding validations, you can prevent many user complaints for misunderstanding input fields and provide a better user experience.