Show Tooltip on wrong input in QLineEdit



  • I have a QLineEdit, on which I have set a QRegExpValidator.
    Now I want that whenever the user tries to enter invalid input, the tooltip of the QLineEdit should show up, but I'm not getting any method to implement it.

    Thanx :)



  • I could be wrong, but I don't think it is possible (or reasonably so) to implement such a feature using the Validator. In order to do something like that, you would need a signal emitted from the QValidator when an incorrect input is tried. The only signal QValidator can emit is destroyed().

    I think the only way to handle this is by using the textEdited signal from QLineEdit, which will emit every time the value is changed (e.g, character entered). You could then check it against a regex and if it isn't valid, emit a custom signal to a child widget that would sit in the 'tooltip' position, and delete the character from the input. There may be a way to invoke the tooltop itself, but I'm not aware of it.



  • To give an example (I just test it)

    Assuming your QLineEdit widget is named 'username' and you use the default name for the slot to handle the signal,

    @
    void MyClass::on_username_textEdited(const QString &arg1){
    QString temp = arg1; /because arg1 is const/
    qDebug() << "Character input: " + temp.remove(0,arg1.length()-1);
    }@

    This outputs the individual character just input to the debug console. You could also use a QVerify instance to verify validity. How you implement the actual 'tooltip' is up to you. It could just be a QLabel that shows up above the field, and clears out on a QTimer::singleShot().



  • @
    QLineEdit *lineEdit = new QLineEdit;
    lineEdit->setValidator(new QRegExpValidator(QRegExp(...)));

    connect(lineEdit, SIGNAL(textEdited(QString)), this, SLOT(lineEditTextEdited()));

    void lineEditTextEdited()
    {
    if(lineEdit->hasAcceptableInput() == false)
    {
    QToolTip::showText(lineEdit->mapToGlobal(QPoint()), tr("Invalid Input"));
    }
    else
    {
    QToolTip::hideText();
    }
    }
    @



  • @Lukas Geyer
    I tried your code..
    the validator works fine, it doesn't allow to input any invalid text, but the tooltip is not shown on invalid input..



  • That's because textEdited isn't emitted if the input is invalid - the text doesn't actually change, and thus is not edited. So, its ALWAYS going to execute the 'else' condition. I could be wrong about that, depending on the implementation of setValidator, but you could check it with qDebug():

    @
    void lineEditTextEdited()
    {
    qDebug() << "Character entered!";
    if(lineEdit->hasAcceptableInput() == false)
    {
    QToolTip::showText(lineEdit->mapToGlobal(QPoint()), tr("Invalid Input"));
    }
    else
    {
    QToolTip::hideText();
    }

    }@

    Just add the qDebug line, and watch the output when you enter an invalid character. If it outputs the string while typing an invalid character, than I'm wrong.

    I really think you'll have to manually check each character entered against a validator yourself, without the setValidator method.

    Also, in order to properly handle cut/paste operations that change the text by whole strings instead of just one character at a time, you'll have to use a 'last value' variable to see if more than one character was added.

    Edit: I tested it myself, and textEdited is not emitted when an invalid character is input.

    So, I did it my way, and it works. You could then just use the tooltop code provided by Lukas to get what you want accomplished (assuming the tooltip function works - I did not test it).

    @void on_username_textEdited(const QString &arg1)
    {
    int length = arg1.length() -1;
    QString temp = arg1;
    QString inchar = temp.remove(0,length);
    QRegExpValidator validate(QRegExp("([a-z]|[A-Z])"));
    int pos = 1;
    if (validate.validate(inchar, pos) == QValidator::Acceptable){
    qDebug() << "Valid Character input: " + inchar;
    }
    else {
    qDebug() << "Invalid character input: " + inchar;
    temp = arg1;
    ui->username->setText(temp.remove(-1,1));
    }
    }
    @
    So, this example creates a new instance of QValidator on the stack with every single edit, but its not hard to change it to a persistent object on the heap, this is just simpler to write and read.



  • :@Keozon
    I tried it, & I appreciate it, good work..
    but it's not that effective..

    ya obviously it works for your case but it doesn't for my case..
    It works somewhat strange..
    try replacing, line no. 6 in you code with.

    @QRegExpValidator validate(QRegExp("([a-zA-Z0-9]+ ?)*"));@

    this regex doesn't allow more than one white space between two words..



  • Okay; if you need to not only validate individual characters but also the string as a whole, add another QValidator with the "string regex" (or use one that works for both character and string validation), and check the arg1 value as a whole with each change. Change the if () condition to have an && to make sure the string as a whole is also valid. Should work how you want.

    Edit: actually, you can probably skip validating the individual character and just validate the string as a whole, since if the string is valid, the character obviously is. So, just validate arg1 instead of inchar. Note that with string validation, you'll have to allow QVerify::Intermediate to qualify as valid, as well.

    Tested, it works:
    @
    void on_username_textEdited(const QString &arg1)
    {
    int length = arg1.length() -1;
    QString temp = arg1;
    QString inchar = temp.remove(0,length);
    QRegExpValidator validate(QRegExp("([a-zA-Z0-9]+ ?)*"));
    int pos = arg1.length();
    temp = arg1;
    if (validate.validate(temp, pos) == QValidator::Acceptable){
    debug("Valid character input: "+inchar);
    }
    else {
    debug("Invalid character input :"+inchar);
    ui->username->setText(temp.remove(-1,1));
    }
    }
    @

    Note that debug() is a method I defined previously.



  • [quote author="jaydeep17" date="1337073440"]I tried your code.. the validator works fine, it doesn't allow to input any invalid text, but the tooltip is not shown on invalid input..[/quote]
    You cannot enter invalid input if a validator is set, only intermediate (tooltip is shown) and acceptable.

    If you want the user to be able to actually enter invalid input as well you should not use setValidator() (as suggested by Keozon) but use the validator to validate the text manually.
    @
    QRegExpValidator lineEditValidator_ = new QRegExpValidator(QRegExp("([a-zA-Z0-9]+ ?)"));

    void lineEditTextEdited(const QString &text)
    {
    QString validatorText = text;
    int validatorPosition = 0;
    if(lineEditValidator_->validate(validatorText, validatorPosition) == QValidator::Invalid)
    {
    QToolTip::showText(lineEdit_->mapToGlobal(QPoint()), tr("Invalid Input"));
    }
    else
    {
    QToolTip::hideText();
    }
    }
    @



  • If you want to fix the 'copy and paste' problem, this is one (probably not the best) way of fixing it.
    Let me know if it doesn't make sense.
    @
    void on_username_textEdited(const QString &arg1)
    {
    int length = arg1.length() -1;
    QString temp = arg1;
    QString inchar = temp.remove(0,length);
    QRegExpValidator validate(QRegExp("[a-zA-Z]+([a-zA-Z0-9])*"));
    int pos = arg1.length();
    temp = arg1;
    if (validate.validate(temp, pos) == QValidator::Acceptable){
    debug("Valid character input: "+inchar);
    }
    else {
    debug("Invalid character input :"+inchar);
    ui->username->setText(temp.remove(-1,1));
    /Make sure the string still is valid; i.e, no more than one character was input at once./
    temp = ui->username->text();
    pos = temp.length();
    while (validate.validate(temp, pos) == QValidator::Invalid){
    ui->username->setText(temp.remove(-1,1));
    temp = ui->username->text();
    pos = temp.length();
    }
    }
    }
    @

    Edit:
    @Lukas, I believe he does not want invalid input to be shown (my example eliminates all invalid input), but doesn't want the user to be sitting there, scratching their head in confusion as to why the field is not updating. So, he wants the tool tip to show when they try. The issue was getting the tooltip to show on invalid input while still not allowing that input to show.



  • @Lukas,
    yes, Keozon is correct, I want to implement the same thing...

    @Keozon
    you know, the code that you posted in the last two comments, do the exact same thing, except that I had to change the regex...
    but (no need to worry)
    I got what I wanted..

    Thanks for the help.. :)



  • The last code segment I included handles "paste" into the field. The prior code segment only handles one character at a time. So, if a user were to type "name lastname" and then paste it in, the prior code would allow it, the second will not.



  • oh !!! :D
    sorry, I didn't notice that...



  • [quote author="Keozon" date="1337110320"]I believe he does not want invalid input to be shown, but doesn’t want the user to be sitting there, scratching their head in confusion as to why the field is not updating. So, he wants the tool tip to show when they try. The issue was getting the tooltip to show on invalid input while still not allowing that input to show.[/quote]
    I think he's got it ;-)

    Just add a persistent state then, which reset the text on invalid input.
    @
    void lineEditTextEdited(const QString &text)
    {
    static QString lineEditValidText = lineEdit_->text();

    QString validatorText = text;
    int validatorPosition = 0;
    if(lineEditValidator_->validate(validatorText, 
                                    validatorPosition) == QValidator::Invalid)
    {
       lineEdit_->setText(lineEditValidText);
       QToolTip::showText(lineEdit_->mapToGlobal(QPoint()), tr("Invalid Input"));
    }
    else
    {
        lineEditValidText = lineEdit_->text();
        QToolTip::hideText();
    }
    

    }
    @
    This is a perfect opportunity to refactor your code, as the implementation is currently limited to a single widget (which means duplicating the code for multiple widgets). One possibility is to use the charm pattern.
    @
    class ValidationCharm : public QObject
    {
    Q_OBJECT

    public:
    ValidationCharm(QObject *parent = 0) : QObject(parent) {}

    void activateOn(QLineEdit *lineEdit, QValidator *validator)
    {
        lineEdit->setProperty("previousText", 
                              QVariant::fromValue<QString>(lineEdit->text()));
        lineEdit->setProperty("validator", 
                              QVariant::fromValue<QValidator*>(validator));
        connect(lineEdit, SIGNAL(textEdited(QString)), this, SLOT(textEdited_(QString)));
    }
    
    void deactivateOn(QLineEdit *lineEdit)
    {
        disconnect(lineEdit, SIGNAL(textEdited(QString)), 
                   this, SLOT(textEdited_(QString)));
        lineEdit->setProperty("previousText", QVariant());
        lineEdit->setProperty("validator", QVariant());
    }
    

    private slots:
    void textEdited_(const QString &text)
    {
    QLineEdit lineEdit = qobject_cast<QLineEdit>(sender());

        QValidator *validator = lineEdit->property("validator").value<QValidator*>();
        QString validatorText = text;
        int validatorPosition = 0;
        if(validator->validate(validatorText, 
                               validatorPosition) == QValidator::Invalid)
        {
            lineEdit->setText(lineEdit->property("previousText").value<QString>());
            QToolTip::showText(lineEdit->mapToGlobal(QPoint()), tr("Invalid Input"));
        }
        else
        {
            lineEdit->setProperty("previousText", 
                                  QVariant::fromValue<QString>(lineEdit->text()));
            QToolTip::hideText();
        }
    }
    

    };

    Q_DECLARE_METATYPE(QValidator*)

    ...

    QValidator validator = new QRegExpValidator(QRegExp("([a-zA-Z0-9]+ +)"));
    ValidationCharm *validatonCharm = new ValidationCharm;

    validatonCharm->activateOn(lineEditA, validator);
    validatonCharm->activateOn(lineEditB, validator);
    validatonCharm->activateOn(lineEditC, validator);
    @

    [quote author="jaydeep17" date="1337106343"]
    <code>QRegExpValidator validate(QRegExp("([a-zA-Z0-9]+ ?)"))</code> doesn't allow more than one white space between two words.
    [/quote]
    Yes, because according to the regular expression multiple whitespaces are invalid (? quantifier means zero or one). If you want to allow mutliple whitespaces use the + quantifier instead <code>QRegExp("([a-zA-Z0-9]+ +)
    ")</code>.



  • @Lukas Geyer
    As i'm new to Opensource, I want to start some contribution, is it a good idea, if I make a patch for this tooltip concept and submit it to Qt ??

    I have also decided, that I'll not only limit this to tooltips, I'll allow the developer to decide if he wants a tooltip to be shown or an errorbox..

    Please give your views, on it..



  • "Contributing":http://qt-project.org/contribute is always a good idea! ;-)

    However, contributions have to follow the "contribution guidelines":http://qt-project.org/wiki/Qt-Contribution-Guidelines, both style-wise and design-wise, so make sure you've got in touch with the "maintainer":http://qt-project.org/wiki/Maintainers or the developer community ("IRC":irc://irc.freenode.net/qt-labs, "mailing list":http://lists.qt-project.org/mailman/listinfo) to discuss if such functionality is most likely to be accepted and how it is implemented the most beneficial way before starting any work.

    I personally think that having an option to provide visual feedback on failed validation is quite a good thing. This can be as easy as adding a textRejected() signal to input widgets supporting validation, but this is an implementation detail.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.