【问题标题】:QLineEdit: Set cursor location to beginning on focusQLineEdit:将光标位置设置为焦点开始
【发布时间】:2014-04-27 06:38:24
【问题描述】:

我有一个带有输入掩码的QLineEdit,以便可以轻松输入(或粘贴)某种代码。由于您可以将光标放在QLineEdit 中的任何位置,即使没有文本(因为输入掩码中有一个占位符):

如果人们不够粗心和不专心,这会导致他们在文本框的中间输入,而他们应该从开头开始输入。我尝试了通过安装事件过滤器确保光标在焦点开始的简单方法:

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

这适用于键盘焦点,即按下 + 时,但是当用鼠标单击时,光标总是结束在我点击的地方。我的猜测是QLineEdit 在获得焦点后点击自身时设置光标位置,从而撤消我的位置更改。

再深入一点,点击时会引发以下事件¹,从而按顺序改变焦点:

  1. FocusIn
  2. MouseButtonPress
  3. MouseButtonRelease

我无法在事件过滤器中准确捕捉鼠标点击,所以有没有一种很好的方法可以将光标位置设置为在控件被聚焦时(无论是通过鼠标还是键盘) ?


¹旁注:我讨厌 Qt 没有任何关于此类常见场景的信号/事件顺序的文档。

【问题讨论】:

  • 事件顺序可能取决于不同的因素(包括用户输入)。因此无法描述任何“标准”方式。
  • 回复。旁注:我同意。至少获得批准的文档补丁应该不难,如果您想将其写下来并包含在后代中。
  • “我无法在事件过滤器中准确捕捉鼠标点击” 为什么?它们以QMouseEvent 的形式交付。
  • 在事件过滤器中做这样的事情不是很明智。原因是您没有过滤掉默认行为。所以在你做完你的事情之后,它会在之后执行默认的focusInEvent 行为。您应该按照 Dmitry 的建议覆盖 focusInEventHere你可以看到默认行为。
  • @Kuba:当然可以,但是我必须检查一些仅在鼠标单击之前发生焦点更改的情况下处理鼠标单击的方法,并且如果有人用键盘更改焦点并且不知何故不会搞砸然后单击控件。它的毛比需要的多。

标签: c++ qt focus qlineedit


【解决方案1】:

下面是一个被分解到一个单独的类中的实现。它将光标的设置推迟到为对象发布任何未决事件之后,从而回避事件顺序问题。

#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"

【讨论】:

  • 克里基。我认为自己是 C++ 和 Qt 的绝对初学者(到现在已经使用了大约 1.5 年,给予或接受)。这……暂时超出了我的想象。我想我现在坚持使用子类。主要是因为我了解它在做什么(因此我可以相应地记录它)。不过,感谢您的解决方案。
  • @Joey:现在记录在案 :)
  • 你真的,真的要我用这个方法,对吧? :D 好吧,我让步了。在复制实现并基本上阅读每一行之后,它现在更有意义了。恐怕我自己还是写不出来。
  • @Joey 我努力成为一名教育工作者 :) 我不在乎是否有人使用我的代码,只要他们在此过程中学到一些东西(即使是我错了 - 高 -代表答案有时是错误的!)。我生活中最大的满足来自有人告诉我“哦,我现在明白了”。所以无论如何,你给了我一些快乐,我为此感谢你。
  • @Joey 以免我忘记:在生产代码中,您希望将实现与接口分开。因此,类和方法声明进入一个 .h 文件,而方法定义进入一个 .cpp 文件。这“不用说”,但我还是会说 :) 为了简洁、易于理解和易于部署(复制粘贴到新项目中,运行),我将其全部发布为单个文件。
【解决方案2】:

您可以在focusInEvent 中使用QTimer 来调用将光标位置设置为0 的槽。

这很有效,因为单次计时器会在对象事件队列的末尾发布一个计时器事件。鼠标发起的焦点事件必然具有已经发布到事件队列的鼠标点击。因此,您可以保证在任何挥之不去的鼠标按下事件之后都会调用计时器的事件(以及由此产生的插槽调用)。

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

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

【讨论】:

  • 哦,我没有想到 singleShot 零延迟直接发布到事件队列中,并且设置了超时的计时器对我来说感觉很 hacky(更不用说用户对光标的混淆了)跳来跳去)。
  • 有趣的是,这不适用于事件过滤器,只能在子类中使用。
  • @Joey 如果有办法将函子传递给singleShot,它将与事件过滤器一起使用。然后看起来像这样:QTimer::singleShot(0, this, [this]{ ui-&gt;lineEdit.setCursorPosition(0); });
  • @Joey 鉴于QTimer::singleShot 的作用,它完全等同于将QMetaObject::invokeMethodQMetaMethod::invoke 与队列连接一起使用。这就是我在回答中所利用的。请记住:零长度计时器事件只是将 QMetaCallEvent 放入事件队列中
【解决方案3】:

设置验证器而不是 inputmask 。 http://doc.qt.io/qt-5/qregularexpressionvalidator.html

【讨论】:

  • 输入掩码的好处是可以立即使用户清楚地了解预期的模式。验证器不这样做,只验证内容。这是两个非常不同的问题。
  • 但我不认为这是一个错误的答案,或者你投了反对票。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多