user3338859
user3338859

Reputation: 53

Wait for last character typed in QLineEdit::onTextChanged

I am working on a Point of Sale application, I have a function that takes the text from a QLineEdit (the product's bar code) and runs a query looking for the product to be displayed. The problem is I am running a query every time the text changes, that is, every time a new character is typed. Is there a way to wait for the user to stop typing and then run the query? I will be using a handheld scanner so it would be like 100ms between each character being typed.

I think I need something like this:

void PoS::on_lineEdit_textEdited()
{
    //check for keys still being pressed
    //if 100ms have passed without any key press, run query
}

I have tried to use a timer and also threads (I am very new to Qt 5) but have failed so far.

Upvotes: 5

Views: 2216

Answers (3)

Nate Wanner
Nate Wanner

Reputation: 199

Understanding this wasn't tagged for pyqt, I am providing a Python 3 / PqQt5 example for anyone that may find this in the future. This restarts the timer each time the text changes (every character). Once the user has stopped typing for 1 second, it will execute SQL or other function.

class MyClass(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.txt_edit = QtWidgets.QPlainTextEdit()
        self.typing_timer = QtCore.QTimer()
        self.typing_timer.setSingleShot(True)
        self.typing_timer.timeout.connect(self.make_changes)
        self.txt_edit.textChanged.connect(self.start_typing_timer)

    def start_typing_timer(self):
        """Wait until there are no changes for 1 second before making changes."""
        self.typing_timer.start(1000)

    def make_changes(self):
        txt = self.txt_edit.toPlainText()
        # RUN SQL OR DO SOMETHING ELSE WITH THE TEXT HERE #

Upvotes: 4

There are two approaches:

  1. Query 100ms after last change. For this, you (re)start a 100ms timer on the key press. When it expires, you run the query.

  2. Query every 100ms while the user is making changes. Start a 100ms time on the key press, but only if it's not yet running. When it expires, you run the query.

If you implement the query processor in a separate QObject, you can then trivially move it to a separate thread - but be sure that you've created the database connection in that thread too. The only means of communication between the UI object and the query processor should be via signals/slots - this takes care of synchronized data exchange between threads.

You could remove the moveToThread call: it would still work. Then you run the query executor in the GUI thread, so the user experience may be worse for it as the database drivers usually block while the query is processing.

#include <QApplication>
#include <QTextEdit>
#include <QBasicTimer>
#include <QSqlDatabase>
#include <QThread>

class ProductData {
};
Q_DECLARE_METATYPE(ProductData)

class PoS : public QWidget {
   Q_OBJECT
   enum QueryBehavior { FinalQuery, MultipleQuery };
   QBasicTimer m_queryTimer;
   QueryBehavior m_queryBehavior;
   Q_SLOT void on_lineEdit_textEdited() {
      if (m_queryBehavior == FinalQuery || !m_queryTimer.isActive())
         m_queryTimer.start(100, this);
   }
   void timerEvent(QTimerEvent * ev) {
      if (ev->timerId() != m_queryTimer.timerId()) return;
      m_queryTimer.stop();
      emit queryRequest();
   }
public:
   Q_SIGNAL void queryRequest();
   Q_SLOT void queryResponse(const ProductData &) { /* ... */ }
   // ...
};

class QueryExecutor : public QObject {
   Q_OBJECT
   QSqlDatabase m_dbConnection;
public:
   Q_SLOT void queryRequest() {
      if (!m_dbConnection.isOpen()) {
         // Open the database connection here, NOT in the constructor.
         // The constructor executes in the wrong thread.
         // ...
      }
      ProductData pdata;
      // ...
      emit queryResponse(pdata);
   }
   Q_SIGNAL void queryResponse(const ProductData &);
};

//! A thread that's always safe to destruct.
class Thread : public QThread {
private:
   using QThread::run; // This is a final class.
public:
   Thread(QObject * parent = 0) : QThread(parent) {}
   ~Thread() { quit(); wait(); }
};

int main(int argc, char ** argv) {
   QApplication app(argc, argv);
   qRegisterMetaType<ProductData>();
   PoS pos;
   QueryExecutor executor;
   Thread thread; // must come after the executor!
   thread.start();
   executor.moveToThread(&thread);
   executor.connect(&pos, SIGNAL(queryRequest()), SLOT(queryRequest()));
   pos.connect(&executor, SIGNAL(queryResponse(ProductData)), SLOT(queryResponse(ProductData)));
   pos.show();
   return app.exec();
}

#include "main.moc"

Upvotes: 1

Nazar554
Nazar554

Reputation: 4195

You will need a timer. Read more about QTimer here: http://qt-project.org/doc/qt-5.1/qtcore/qtimer.html

Add a QTimer *mTimer as private member variable. Create a slot named for example do_query where you will do your query . Put this somewhere in constructor:

mTimer = new QTimer(this); // make a QTimer
mTimer->setSingleShot(true); // it will fire once after it was started
connect(mTimer, &QTimer::timeout, this, &PoS::do_query); // connect it to your slot

Now in your function:

void PoS::on_lineEdit_textEdited()
{
    mTimer->start(100); // This will fire do_query after 100msec. 
    // If user would enter something before it fires, the timer restarts
}

And do your query:

void PoS::do_query()
{
    // your code goes here
}

Upvotes: 8

Related Questions