Question

I have a QLineEdit with an input mask, so that some kind of code can easily be entered (or pasted). Since you can place the cursor anywhere in the QLineEdit even if there is no text (because there is a placeholder from the input mask):

enter image description here

If people are careless and unattentive enough this leads to them typing in the middle of the text box whereas they should start typing at the beginning. I tried the trivial way of ensuring that the cursor is at the start upon focus by installing an event filter:

bool MyWindowPrivate::eventFilter(QObject * object, QEvent * event)
{
    if (object == ui.tbFoo && event->type() == QEvent::FocusIn) {
        ui.tbFoo->setCursorPosition(0);
    }
    return false;
}

This works fine with keybaord focus, i.e. when pressing or +, but when clicking with the mouse the cursor always ends up where I clicked. My guess would be that QLineEdit sets the cursor position upon click itself after it got focus, thereby undoing my position change.

Digging a little deeper, the following events are raised when clicking¹ and thus changing focus, in that order:

  1. FocusIn
  2. MouseButtonPress
  3. MouseButtonRelease

I can't exactly catch mouse clicks in the event filter, so is there a good method of setting the cursor position to start only when the control is being focused (whether by mouse or keyboard)?


¹ Side note: I hate that Qt has no documentation whatsoever about signal/event orders for common scenarios such as this.

Was it helpful?

Solution

Below is an implementation that's factored into a separate class. It defers the setting of the cursor to after any pending events are posted for the object, thus sidestepping the issue of event order.

#include <QApplication>
#include <QLineEdit>
#include <QFormLayout>
#include <QMetaObject>

// Note: A helpful implementation of
// QDebug operator<<(QDebug str, const QEvent * ev)
// is given in http://stackoverflow.com/q/22535469/1329652

/// Returns a cursor to zero position on a QLineEdit on focus-in.
class ReturnOnFocus : public QObject {
   Q_OBJECT
   /// Catches FocusIn events on the target line edit, and appends a call
   /// to resetCursor at the end of the event queue.
   bool eventFilter(QObject * obj, QEvent * ev) {
      QLineEdit * w = qobject_cast<QLineEdit*>(obj);
      // w is nullptr if the object isn't a QLineEdit
      if (w && ev->type() == QEvent::FocusIn) {
         QMetaObject::invokeMethod(this, "resetCursor",
                                   Qt::QueuedConnection, Q_ARG(QWidget*, w));
      }
      // A base QObject is free to be an event filter itself
      return QObject::eventFilter(obj, ev);
   }
   // Q_INVOKABLE is invokable, but is not a slot
   /// Resets the cursor position of a given widget.
   /// The widget must be a line edit.
   Q_INVOKABLE void resetCursor(QWidget * w) {
      static_cast<QLineEdit*>(w)->setCursorPosition(0);
   }
public:
   ReturnOnFocus(QObject * parent = 0) : QObject(parent) {}
   /// Installs the reset functionality on a given line edit
   void installOn(QLineEdit * ed) { ed->installEventFilter(this); }
};

class Ui : public QWidget {
   QFormLayout m_layout;
   QLineEdit m_maskedLine, m_line;
   ReturnOnFocus m_return;
public:
   Ui() : m_layout(this) {
      m_layout.addRow(&m_maskedLine);
      m_layout.addRow(&m_line);
      m_maskedLine.setInputMask("NNNN-NNNN-NNNN-NNNN");
      m_return.installOn(&m_maskedLine);
   }
};

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   Ui ui;
   ui.show();
   return a.exec();
}

#include "main.moc"

OTHER TIPS

You could use a QTimer in focusInEvent to call a slot that sets the cursor position to 0.

This works well because a single shot timer posts a timer event at the end of the object's event queue. A mouse-originating focus-in event necessarily has the mouse clicks already posted to the event queue. Thus you're guaranteed that the timer's event (and the resulting slot call) will be invoked after any lingering mouse press events.

void LineEdit::focusInEvent(QFocusEvent *e)
{
    QLineEdit::focusInEvent(e);
    QTimer::singleShot(0, this, SLOT(resetCursorPos()));
}

void LineEdit::resetCursorPos()
{
    setCursorPosition(0);
}

set a validator instead of inputmask . http://doc.qt.io/qt-5/qregularexpressionvalidator.html

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top