Recognize if a QLineEdit loses a focus

5.8k Views Asked by At

In one of my projects I have a series of QLineEdit Widgets, that should accept double numbers, that are lying in a certain range. For certain reasons I can not use QDoubleSpinBox.

Now I'm using QDoubleValidator to check, if my number lies in the given range. Unfortunately, the signal editingFinished is only emitted if QValidator gives QValidator::Acceptable.

Now assume, that I might have a series of such QLineEdit widgets. In one I enter a bad number and then I switch the focus to another widget. The user is left with a bad value inside the QLineEdit.

My desired behavior instead would be to set the focus to the widget containing the bad input and giving a warning.

For some reasons, I'm failing to implement this feature. Even after I consulted the Qt docs.

Here is my full code.

ValidatedDoubleEditorWidget.h

#pragma once

#include <QWidget>

namespace Ui {
    class validatedDoubleEditorWidget;
}

class QDoubleValidator;

    class ValidatedDoubleEditorWidget : public QWidget {
        Q_OBJECT
    public:
        ValidatedDoubleEditorWidget(double min, double max, double value);

        double getValue() const;
        void setValue(const double value);

    private slots:
        void on_lineEdit_editingFinished();

    protected:
        virtual void focusOutEvent(QFocusEvent *event) override;


        virtual void focusInEvent(QFocusEvent *event) override;

    private:
        Ui::validatedDoubleEditorWidget* mWidget = nullptr;
        double mValue = 0.;
        double mMin = 0.;
        double mMax = 0.;
        QDoubleValidator* mValidator = nullptr;

    };

ValidatedDoubleEditorWidget.cpp

#include "ValidatedDoubleEditorWidget.h"
#include <QDoubleValidator>
#include <QMessageBox>
#include <QDebug>
#include "ui_ValidatedDoubleEditorWidget.h"

ValidatedDoubleEditorWidget::ValidatedDoubleEditorWidget(double min, double max, double value)
{
    mWidget = new Ui::validatedDoubleEditorWidget;
    mWidget->setupUi(this);
    mValue = value;
    mWidget->lineEdit->setText(QString("%1").arg(value));
    mValidator = new QDoubleValidator(min, max, 20, this);
    mWidget->lineEdit->setValidator(mValidator);
    setFocusProxy(mWidget->lineEdit);
    setFocusPolicy(Qt::StrongFocus);
}

double ValidatedDoubleEditorWidget::getValue() const
{
    return mValue;
}

void ValidatedDoubleEditorWidget::setValue(const double value)
{
    mValue = value;
    mWidget->lineEdit->setText(QString("%1").arg(value));
}


void ValidatedDoubleEditorWidget::on_lineEdit_editingFinished()
{
    QString text = mWidget->lineEdit->text();
    qDebug() << "Editing finished";
    bool ok;
    double value = text.toDouble(&ok);
    if (!ok) {
        //
    }
    else {
        mValue = value;
    }
}

void ValidatedDoubleEditorWidget::focusOutEvent(QFocusEvent *event)
{
    qDebug() << "OutFocus";
    QString text = mWidget->lineEdit->text();
    int i;
    auto state=mValidator->validate(text, i);
    if (state != QValidator::Acceptable) {
        QMessageBox::warning(this, tr("Invalid Input!"), tr("Please check your input."), QMessageBox::Ok); 
        mWidget->lineEdit->setText(QString("%1").arg(mValue));
        mWidget->lineEdit->setFocus();
    }

}

void ValidatedDoubleEditorWidget::focusInEvent(QFocusEvent *event)
{
    qDebug() << "InFocus";
}

TestRunner.cpp

#include <QApplication>
#include <QMap>
#include <QFrame>
#include <QHBoxLayout>
#include "ValidatedDoubleEditorWidget.h"

int main(int argc, char** args) {
    QApplication app(argc, args);
    QFrame frame;
    frame.setLayout(new QHBoxLayout);
    frame.layout()->addWidget(new ValidatedDoubleEditorWidget(-1., 4., 1.));
    frame.layout()->addWidget(new ValidatedDoubleEditorWidget(-2., 4., 5.));

    frame.show();
    app.exec();
    return 0;
}
2

There are 2 best solutions below

0
On BEST ANSWER

You can subclass QLineEdit and reimplement focusOutEvent method. Call QLineEdit::hasAcceptableInput to check if the input is valid. If it is not, you can call setFocus to regain input focus. You can also show a warning dialog or emit some signal. Here is an example:

#include <QtWidgets>

class CustomLineEdit : public QLineEdit
{
    Q_OBJECT
public:
    CustomLineEdit(QWidget *parent = nullptr) : QLineEdit(parent){}
protected:
    void focusOutEvent(QFocusEvent *event)
    {
        QLineEdit::focusOutEvent(event);
        if(!hasAcceptableInput())
        {
            setFocus();
            emit validationError();
        }
    }
signals:
    void validationError();
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow m;
    m.setCentralWidget(new QWidget);
    m.centralWidget()->setLayout(new QVBoxLayout);

    QDoubleValidator d_validator(0, 10, 2);
    CustomLineEdit l1;
    CustomLineEdit l2;
    l1.setValidator(&d_validator);
    l2.setValidator(&d_validator);

    QObject::connect(&l1, &CustomLineEdit::validationError, [=]{qDebug() << "Validation error!";});
    QObject::connect(&l2, &CustomLineEdit::validationError, [=]{qDebug() << "Validation error!";});

    m.centralWidget()->layout()->addWidget(&l1);
    m.centralWidget()->layout()->addWidget(&l2);

    m.show();
    return a.exec();
}

#include "main.moc"
2
On

SetFocusProxy() does not mean the parent widget will get focus events for the child widget. It means that the child widget will get focus when the parent is given focus.

So in your case your focusIn() and focusOut() event handlers are for the parent widget, but the parent widget has been given a line edit as its focus proxy meaning the line edit will be the widget you want to check for focus in and out events.

One way to detect the relevant focus in and out events for the line edit would be to use event filters. Basically WidgetA can install an event filter on WidgetB and then be notified when ever WidgetB is about to revieve an event (e.g. focus in or focus out events).

So in your case you can install an event filter on the line edit in your constructor for the ValidatedDoubleEditorWidget class as follows:

mWidget->lineEdit->installEventFilter( this );

And then you can add the event filter implementation (again to your ValidatedDoubleEditorWidget class):

// Declaration
virtual bool eventFilter(QObject *watched, QEvent *event) override;

// Implementation
bool ValidatedDoubleEditorWidget::eventFilter(QObject *watched, QEvent *event)
{
    if( event->type() == QEvent::FocusIn ) {

        qDebug() << "Line edit focus in event";
    }

    else if( event->type() == QEvent::FocusOut ) {

        qDebug() << "Line edit focus out event";
    }
    return false; // We return false to ignore the event and allow the child to recieve the event normally
}

This should get you to a point where you can detect when a ValidatedDoubleEditorWidget has lost focus and does not contain a valid input.