Имитация нажатия клавиш TAB или SPACE изнутри приложения Qt - PullRequest
0 голосов
/ 13 сентября 2018

Цель состоит в том, чтобы выбрать и щелкнуть виджеты (так же, как пользователь может делать нажатия клавиш TAB и SPACE) приложения Qt из второго приложения. Оба приложения находятся в одной сети и «общаются» посредством QTcpSocket, но это не проблема. Таким образом, приложение 2nb можно рассматривать как панель с 3 кнопками (TAB, Shift TAB и SPACE), которая должна управлять первым приложением. Это 1-е приложение также можно использовать напрямую, как и любое другое приложение Qt. Внутри 1-го приложения и при получении адекватного сообщения я попытался отправить событие нажатия клавиши, но ничего не происходит:

void Appli1::onAppli2_TabClickedMessage()
{
    QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier);
    QCoreApplication::sendEvent((QObject*)appli1.MainWindow(), &keyEvent);
}

Полагаю, это неправильный способ. Я проверил, что я выполняю этот слот в отладчике, но ничего не происходит, точно так же, как события уходят в бесконечную пустоту ...

Любая помощь (метод или идея) приветствуется

Ответы [ 2 ]

0 голосов
/ 13 сентября 2018

Имитация нажатия клавиш кажется ненужной.Получатель обрабатывает три команды:

  • Если главное окно неактивно, оно активируется, и если ни один виджет не имеет фокуса, первый виджет, который получает его, явно фокусируется, а команда игнорируется.

  • NEXT_FOCUS: перемещает фокус на следующий элемент в цепочке фокусировки (как было бы с Tab)

  • PREVIOUS_FOCUS: перемещает фокуск предыдущему элементу в цепочке фокусировки (как и Shift-Tab)

  • CLICK: вызывает метод click() фокусированного виджета - все кнопки Qt поддерживают его, так как он находится вих базовый QAbstractButton класс.

Таким образом, что-то вроде приведенного ниже должно сработать, и вам не нужно эмулировать клавиатуру - тем более, что совсем не гарантируется, чтоконкретные ключи, которые вы упоминаете, имеют ожидаемые эффекты - поведение зависит от платформы и может даже зависеть от темы.Например, в OS X собственное поведение заключается в том, что пробел не имеет эффектов, потому что кнопки не могут быть сфокусированы.

enum Command {
   NEXT_FOCUS,      // focus next clickable widget
   PREVIOUS_FOCUS,  // focus previous clickable widget
   FOCUS,           // focus current clickable widget, or next one if none
   CLICK            // like FOCUS, but also clicks the widget
};

bool executeCommand(QWidget *window, Command command) {
   Q_ASSERT(window && window->isWindow());
   using Method = bool (QWidget::*)(bool);
   struct Helper : QWidget {
      static Method get_focusNextPrevChild() { return &Helper::focusNextPrevChild; }
   };
   static Method const focusNextPrevChild = Helper::get_focusNextPrevChild();

   if (!window->isActiveWindow()) {
      window->activateWindow();
      command = FOCUS;
   }

   for (QWidget *focused = nullptr;;) {
      if (command == NEXT_FOCUS) {
         if (!(window->*focusNextPrevChild)(true)) return false;
      } else if (command == PREVIOUS_FOCUS) {
         if (!(window->*focusNextPrevChild)(false)) return false;
      }
      auto *newFocused = window->focusWidget();
      if (!newFocused) return false;
      if (focused && focused == newFocused)
         return false;  // no eligible widgets found
      else if (!focused)
         focused = newFocused;
      auto *newMetaObject = newFocused->metaObject();
      int const clickIdx = newMetaObject->indexOfMethod("click()");
      if (clickIdx >= 0)
         return command == FOCUS || newMetaObject->method(clickIdx).invoke(focused);
      if (command != NEXT_FOCUS && command != PREVIOUS_FOCUS)
         command = NEXT_FOCUS;  // iterate over widgets till a clickable one is found
   }
}
0 голосов
/ 13 сентября 2018

Inspectable упомянул, что правильным способом для OP является использование UIAutomation . (Конечно, это предполагает, что OP разрабатывается в MS Windows, но, безусловно, существуют аналогичные решения для других операционных систем / систем Windows.)


Из любопытства я немного поиграл с этим.

Мой получатель QLineEdit и единственный виджет & ndash; он получает фокус после запуска.

Тем не менее, у меня не получилось. Другие идеи, которые у меня были:

  • отправить QKeyPress и QKeyRelease симметрично
  • предоставляет аргумент QString в QKeyEvent, который опущен OP

но это не помогло.

Наконец, я заменил sendEvent() на postEvent(), так как был немного обеспокоен прямой отправкой события (кроме цикла событий приложения). Удивительно, но это работает.

testQSendEvent.cc:

#include <QtWidgets>

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  QLineEdit qEdt;
  qEdt.show();
  QTimer qTimer;
  qTimer.setInterval(1000/*ms*/);
  qTimer.start();
  int i = 0;
  QObject::connect(&qTimer, &QTimer::timeout,
    [&]() {
      qDebug() << "About to send key event for" << i;
#if 0 // 1st attempt
      { QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_0 + i, Qt::NoModifier,
          QString(QChar('0' + i)));
        QApplication::sendEvent(&qEdt, &keyEvent);
      }
      { QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_0 + i, Qt::NoModifier,
          QString(QChar('0' + i)));
        QApplication::sendEvent(&qEdt, &keyEvent);
      }
#else // 2nd attempt
      QApplication::postEvent(&qEdt,
        new QKeyEvent(QEvent::KeyPress, Qt::Key_0 + i, Qt::NoModifier,
          QString(QChar('0' + i))));
      QApplication::postEvent(&qEdt,
        new QKeyEvent(QEvent::KeyRelease, Qt::Key_0 + i, Qt::NoModifier,
          QString(QChar('0' + i))));
#endif // 0
      if (++i >= 10) i = 0;
    });
  return app.exec();
}

testQSendEvent.pro

SOURCES = testQSendEvent.cc

QT = widgets

Скомпилировано и протестировано в cygwin64 :

$ qmake-qt5 testQSendEvent.pro

$ make

$ ./testQSendEvent
Qt Version: 5.9.4
About to send key event for 0
About to send key event for 1
About to send key event for 2
About to send key event for 3
About to send key event for 4
About to send key event for 5
About to send key event for 6
About to send key event for 7
About to send key event for 8
About to send key event for 9
About to send key event for 0
About to send key event for 1
About to send key event for 2
About to send key event for 3
About to send key event for 4
About to send key event for 5

snapshot of testQSendEvent


Модифицированный образец с двумя QLineEdit с (где хотя бы один из них определенно не в фокусе):

#include <QtWidgets>

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  QWidget qWin;
  QVBoxLayout qVBox;
  QLineEdit qEdt1, qEdt2;
  qVBox.addWidget(&qEdt1);
  qVBox.addWidget(&qEdt2);
  qWin.setLayout(&qVBox);
  qWin.show();
  QTimer qTimer;
  qTimer.setInterval(1000/*ms*/);
  qTimer.start();
  int i = 0;
  QObject::connect(&qTimer, &QTimer::timeout,
    [&]() {
      QApplication::postEvent(&qEdt1,
        new QKeyEvent(QEvent::KeyPress, Qt::Key_0 + i, Qt::NoModifier,
        QString(QChar('0' + i))));
      QApplication::postEvent(&qEdt1,
        new QKeyEvent(QEvent::KeyRelease, Qt::Key_0 + i, Qt::NoModifier,
        QString(QChar('0' + i))));
      QApplication::postEvent(&qEdt2,
        new QKeyEvent(QEvent::KeyPress, Qt::Key_A + i, Qt::NoModifier,
        QString(QChar('A' + i))));
      QApplication::postEvent(&qEdt2,
        new QKeyEvent(QEvent::KeyRelease, Qt::Key_A + i, Qt::NoModifier,
        QString(QChar('A' + i))));
      if (++i >= 10) i = 0;
    });
  return app.exec();
}

snapshot of testQSendEvent (2nd version)

Даже QLineEdit, которые не имеют фокуса, похоже, обрабатывают ключевые события должным образом, если непосредственно отправлены на них. (Я не уверен, зависит ли это поведение от системы Windows. В моем случае это X11, как я тестировал в cygwin.)


Как уже упоминалось в OP QPushButton s, я снова адаптировал свой образец:

#include <QtWidgets>

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  QWidget qWin;
  QVBoxLayout qVBox;
  QPushButton qBtn1("Button 1");
  QPushButton qBtn2("Button 2");
  qVBox.addWidget(&qBtn1);
  qVBox.addWidget(&qBtn2);
  qWin.setLayout(&qVBox);
  qWin.show();
  QTimer qTimer;
  qTimer.setInterval(1000/*ms*/);
  qTimer.start();
  int i = 0;
  QObject::connect(&qTimer, &QTimer::timeout,
    [&]() {
      switch (i) {
        case 0:
          QApplication::postEvent(&qBtn1,
            new QKeyEvent(QEvent::KeyPress, Qt::Key_Space, Qt::NoModifier,
              QString(QChar(' '))));
          break;
        case 1:
          QApplication::postEvent(&qBtn1,
            new QKeyEvent(QEvent::KeyRelease, Qt::Key_Space, Qt::NoModifier,
              QString(QChar(' '))));
          break;
        case 2:
#if 0 // EXCLUDED
          QApplication::postEvent(&qBtn1,
            new QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier,
              QString(QChar('\t'))));
          QApplication::postEvent(&qBtn1,
            new QKeyEvent(QEvent::KeyRelease, Qt::Key_Tab, Qt::NoModifier,
              QString(QChar('\t'))));
#endif // 0
          break;
        case 3:
          QApplication::postEvent(&qBtn2,
            new QKeyEvent(QEvent::KeyPress, Qt::Key_Space, Qt::NoModifier,
              QString(QChar(' '))));
          break;
        case 4:
          QApplication::postEvent(&qBtn2,
            new QKeyEvent(QEvent::KeyRelease, Qt::Key_Space, Qt::NoModifier,
              QString(QChar(' '))));
          break;
        case 5:
#if 0 // EXCLUDED
          QApplication::postEvent(&qBtn2,
            new QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier,
              QString(QChar('\t'))));
          QApplication::postEvent(&qBtn2,
            new QKeyEvent(QEvent::KeyRelease, Qt::Key_Tab, Qt::NoModifier,
              QString(QChar('\t'))));
#endif // 0
          break;
      }
      if (++i > 5) i = 0;
    });
  QObject::connect(&qBtn1, &QPushButton::clicked,
    [&](bool) { qDebug() << "Button 1 clicked"; });
  QObject::connect(&qBtn2, &QPushButton::clicked,
    [&](bool) { qDebug() << "Button 2 clicked"; });
  return app.exec();
}

snapshot of testQSendEvent (3rd version)

Даже был издан сигнал QPushButton::clicked:

$ ./testQSendEvent
Qt Version: 5.9.4
Button 1 clicked
Button 2 clicked
Button 1 clicked
Button 2 clicked
Button 1 clicked
Button 2 clicked
Button 1 clicked

Я пытался с и без изменения фокуса. (До того, как я EXCLUDED события Tab , я мог видеть изменение фокуса.) Однако, как и в QLineEdit, события Пробел обрабатываются в QPushButton независимо фокус.

Последний пример кода, я снова скомпилировал в VS2013 с Qt 5.9.2 (для собственного WinAPI). Он вел себя точно , как тот, который я скомпилировал в cygwin с g++ и Qt 5.9.4 для X11. То есть отправка пробела ключевого события на QPushButton изменила его визуальный облик, и сигнал QPushButton::clicked был испущен правильно.

snapshot of testQEvent (3rd version) compiled with Qt/WinAPI


Я "случайно" заметил, что четвертый QString аргумент QKeyEvent должен быть установлен правильно. Я узнал об этом, когда отправил

 QKeyEvent(QEvent::KeyPress, Qt::Key_A + i, Qt::NoModifier,
   QString(QChar('0' + i)));

Принимающие QLineEdit вставленные цифры явно игнорируют Qt::Key_A + i.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...