QLineEdit: establece la ubicación del cursor al comienzo del enfoque

5

Tengo una QLineEditcon una máscara de entrada, por lo que se puede ingresar (o pegar) fácilmente algún tipo de código. Dado que puede colocar el cursor en cualquier lugar QLineEditincluso si no hay texto (porque hay un marcador de posición de la máscara de entrada):

ingrese la descripción de la imagen aquí

Si las personas son lo suficientemente descuidadas y desatentas, esto los lleva a escribir en el medio del cuadro de texto, mientras que deberían comenzar a escribir desde el principio. Probé la forma trivial de asegurarme de que el cursor esté al principio en el foco instalando un filtro de eventos:

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

Esto funciona bien con el enfoque del teclado, es decir, al presionar o + , pero al hacer clic con el mouse, el cursor siempre termina donde hice clic. Mi conjetura sería que QLineEditestablece la posición del cursor al hacer clic en sí mismo después de que se enfoca, deshaciendo así mi cambio de posición.

Profundizando un poco más, los siguientes eventos surgen al hacer clic¹ y, por lo tanto, se cambia el enfoque, en ese orden:

  1. FocusIn
  2. MouseButtonPress
  3. MouseButtonRelease

No puedo captar exactamente los clics del mouse en el filtro de eventos, por lo tanto, ¿existe un buen método para configurar la posición del cursor para que se inicie solo cuando el control se está enfocando (ya sea con el mouse o el teclado)?


¹ Nota al margen: Odio que Qt no tenga documentación alguna sobre órdenes de señales / eventos para escenarios comunes como este.

7
  • El orden de los eventos puede depender de diferentes factores (incluida la entrada del usuario). Por tanto, no es posible describir ninguna forma "estándar". 20 de marzo de 2014 a las 12:37
  • Re. nota al margen: estoy de acuerdo. Al menos conseguir que se aprueben los parches de documentación no debería ser difícil, si tiene ganas de escribirlo e incluirlo para la posteridad. 20 de marzo de 2014 a las 12:56
  • "No puedo captar exactamente los clics del mouse en el filtro de eventos" ¿Por qué? Se entregan como QMouseEvent. 20 de marzo de 2014 a las 12:59
  • Hacer cosas como esta en el filtro de eventos no es muy inteligente. La razón es que no está filtrando el comportamiento predeterminado. Entonces, después de hacer tus cosas, hará el focusInEventcomportamiento predeterminado después. Lo que debe hacer es anular el focusInEventcomo lo sugiere Dmitry. Aquí puede ver el comportamiento predeterminado.
    thuga
    20 de marzo de 2014 a las 13:00
  • @Kuba: Claro, pero luego tendría que verificar alguna forma de manejar solo un clic del mouse si fue precedido por un cambio de enfoque y de alguna manera no estropearlo si alguien cambia el enfoque con el teclado y luego hace clic en el control. Es más peludo de lo necesario.
    Joey
    20 de marzo de 2014 a las 13:05
9

A continuación se muestra una implementación que se incluye en una clase separada. Difiere la configuración del cursor después de que se publiquen los eventos pendientes para el objeto, evitando así la cuestión del orden de los eventos.

#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"
7
  • Crikey. Me consideraría un principiante absoluto con respecto a C ++ y Qt (he estado usando ambos durante aproximadamente 1 año y medio, más o menos). Esto ... está muy por encima de mi cabeza por ahora. Creo que me quedo con la subclase por ahora. Principalmente porque entiendo lo que está haciendo (y por lo tanto puedo documentarlo en consecuencia). Sin embargo, gracias por tu solución.
    Joey
    20/0314 a las 15:39
  • @Joey: Documentado ahora :) 20 de marzo de 2014 a las 16:04
  • De verdad, de verdad quieres que use este método, ¿verdad? : D Está bien, cedí. Después de copiar la implementación y básicamente leer cada línea, ahora tiene más sentido. Me temo que todavía no pude escribirlo yo mismo.
    Joey
    20/03/2014 a las 16:27
  • @Joey Trato de ser un educador :) No me importa mucho si alguien usa mi código siempre y cuando aprenda algo en el camino (incluso si es que estaba equivocado, ¡las respuestas con muchas repeticiones son incorrectas a veces!). Mi mayor satisfacción en la vida proviene de alguien que me dice "oh, lo entiendo ahora". Así que me diste una cantidad de alegría de todos modos, y te agradezco por eso. 20/0314 a las 16:32
  • @Joey Para que no me olvide: en el código de producción, desea separar la implementación de la interfaz. Por tanto, las declaraciones de clases y métodos van a un archivo .h, mientras que las definiciones de métodos van a un archivo .cpp. Esto es "evidente", pero lo diré de todos modos :) Lo publico como un solo archivo para mayor brevedad, facilidad de comprensión y facilidad de implementación (copiar y pegar en un proyecto nuevo, ejecutar). 20/0314 a las 16:37
3

Puede usar QTimerin focusInEventpara llamar a una ranura que establece la posición del cursor en 0.

Esto funciona bien porque un temporizador de disparo único publica un evento de temporizador al final de la cola de eventos del objeto. Un evento de foco que se origina en el mouse necesariamente tiene los clics del mouse ya publicados en la cola de eventos. Por lo tanto, tiene la garantía de que el evento del temporizador (y la llamada de tragamonedas resultante) se invocará después de cualquier evento persistente de presión del mouse.

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

void LineEdit::resetCursorPos()
{
    setCursorPosition(0);
}
4
  • Oh, no pensé en singleShotpublicar con retraso cero directamente en la cola de eventos y un temporizador con un tiempo de espera establecido me pareció bastante complicado (sin mencionar la confusión del usuario del cursor saltando).
    Joey
    20 de marzo de 2014 a las 13:49
  • Curiosamente, esto no funciona con un filtro de eventos, solo en una subclase.
    Joey
    20/0314 a las 14:21
  • @Joey Funcionaría con un filtro de eventos si hubiera una forma de pasar un functor a singleShot. Entonces se vería así:QTimer::singleShot(0, this, [this]{ ui->lineEdit.setCursorPosition(0); }); 20/03/2014 a las 14:31
  • @Joey Dado lo que QTimer::singleShothace, es perfectamente equivalente a usar QMetaObject::invokeMethodo QMetaMethod::invokecon una conexión en cola. Eso es lo que aprovecho en mi respuesta. Recuerde: un evento de temporizador de longitud cero simplemente se QMetaCallEventcoloca en la cola de eventos . 20/0314 a las 14:32
0

establezca un validador en lugar de inputmask. http://doc.qt.io/qt-5/qregularexpressionvalidator.html

2
  • La máscara de entrada tiene la ventaja de aclarar inmediatamente al usuario el patrón esperado. Un validador no hace eso y solo valida el contenido. Esas son dos preocupaciones muy distintas.
    Joey
    10/09/15 a las 11:40
  • Pero tampoco creo que sea una respuesta incorrecta que hayas votado en contra.
    neau
    15 de septiembre de 2015 a las 9:04