Шаг 131.
Библиотека Qt.
Искусственное создание событий

    На этом шаге рассмотрим возможность искусственного создания событий из самой программы.

    Иногда возникает необходимость в событиях, созданных искусственно. Например, это полезно при отладке вашей программы, для того чтобы имитировать действия пользователя.

    Для генерации события можно воспользоваться одним из двух статических методов класса QCoreApplication — sendEvent() или postEvent(). Оба метода получают в качестве параметров указатель на объект, которому посылается событие, и адрес объекта события. Разница между ними состоит в том, что метод sendEvent() отправляет событие без задержек, т. е. его вызов приводит к немедленному вызову метода события, в то время как метод postEvent() помещает его в очередь для дальнейшей обработки.

    Рассмотрим это на примере приложения, имитирующего нажатие пользователем клавиш от <0> до <9> (рис. 1).


Рис.1. Программа, демонстрирующая подмену события клавиатуры

    Приведем содержимое файла приложения, имитирующего события клавиатуры.

QApplication app (argc, argv);
app.setApplicationDisplayName("Имитация");
//Создается объект txt класса QLineEdit,
//который будет выступать в качестве поля ввода
QLineEdit txt("Пользователь ввел: ");
txt.show();
txt.resize(280, 20);
int i;

for (i = 0; i < Qt::Key_9 - Qt::Key_0 + 1; ++i) {
    QChar ch      = 48 + i;
    int   nKey    = Qt::Key_0 + i;
    //В цикле происходит создание событий типа QKeyEvent.
    //Первый параметр, передаваемый конструктору, задает тип события клавиатуры
    //(здесь он соответствует событию нажатия клавиши клавиатуры QEvent::KeyPress).
    //Второй параметр задает саму нажатую клавишу.
    //Третий — указывает на клавиши-модификаторы, которые могли быть совместно 
    //нажаты, в нашем случае это значение равно Qt::NoModifier и означает,
    //что никаких клавиш-модификаторов нажато не было.
    //Четвертый параметр указывает на представление клавиши в ASCII-коде
    //(в примере это число начинается с 48, что соответствует цифре "0",
    //и циклически увеличивается на единицу)
    QKeyEvent* pePress =
        new QKeyEvent(QEvent::KeyPress, nKey, Qt::NoModifier, ch);
    QApplication::sendEvent(&txt, pePress);
    //Если хотим имитировать клавиатуру, то после каждого события KeyPress
    //должно следовать событие KeyRelease (событие отпускания клавиши клавиатуры)
    //в противном случае много виджетов, которым будет послано только одно событие
    //нажатия, будут вести себя неправильно, например в QLineEdit 
    //перестает мигать курсор ввода
    QKeyEvent* peRelease =
        new QKeyEvent(QEvent::KeyRelease, nKey, Qt::NoModifier, ch);
    QApplication::sendEvent(&txt, peRelease);
}

    Файлы приложения можно взять здесь.

    Если бы нам понадобилось симулировать нажатие мыши на какой-либо виджет, то реализация функций для этого могла бы выглядеть следующим образом:

//Первым параметром принимаем указатель на виджет, с которым мы хотим 
//провести симуляцию нажатия, вторым и третьим идут координаты позиции,
//в которой было выполнено нажатие.
//Четвертый и пятый параметры не обязательные и по умолчанию инициализируются
//нажатием на левую кнопку, что является самым частым действием при нажатии 
void mousePress(QWidget* pwgt,
                int x,
                int y,
                Qt::MouseButton bt = Qt::LeftButton,
                Qt::MouseButtons bts = Qt::LeftButton
                )
{
   //Внутри функции проверяем действительность указателя на объект виджета,
   //после чего создаем объект события QmouseEvent, инициализируем переданными
   //в функцию параметрами
    if (pwgt) {
      QMouseEvent* pePress =
                new QMouseEvent(QEvent::MouseButtonPress,
                                QPoint(x, y),
                                bt,
                                bts,
                                Qt::NoModifier
                                );
      //пересылаем событие виджету (указатель pwgt) вызовом метода postEvent()
      QApplication::postEvent(pwgt, pePress);
   }
}

    Модифицировать объекты событий возможно не всегда. При совместном создании искусственных событий с фильтрами можно осуществить подмену самих объектов событий. В качестве показательного примера использования подобного перехвата события с целью его подмены можно назвать изменение назначения клавиш клавиатуры. Так как класс события клавиатуры не обладает методами, позволяющими его модифицировать, то каждое сообщение клавиатуры можно получить в объекте фильтра и перед передачей дальше подменить его другим.

    В следующем примере происходит подмена клавиши <9> на клавишу <0> (рис. 2). Таким образом, нажатие пользователем клавиши <9> повлечет за собой отображение цифры 0 в поле ввода. Таким образом можно, например, имитировать измененную раскладку клавиатуры.


Рис.2. Программа, демонстрирующая подмену символа

    Рассмотрим текст заголовочного файла:

class KeyFilter : public QObject {
protected:
    bool eventFilter(QObject* pobj, QEvent* pe)
    {
       //в методе eventFilter() отслеживается идентификатор события QEvent::KeyPress,
       //который соответствует событию нажатия клавиши клавиатуры.
       if (pe->type() == QEvent::KeyPress) {
            //При обнаружении этого события его объект преобразовывается к типу
            //QKeyEvent, чтобы иметь возможность вызова метода key(), который
            //определен в этом классе и позволяет получить код нажатой клавиши.
            if (((QKeyEvent*)pe)->key() == Qt::Key_9) {
                //Затем создается и высылается новое событие нажатия клавиши <0>.
                QKeyEvent* pe = new QKeyEvent(QEvent::KeyPress,
                                              Qt::Key_0,
                                              Qt::NoModifier,
                                              "0"
                                             );
                QApplication::sendEvent(pobj, pe);
                //После этого возвращается значение true, и это означает,  
                //что событие не должно передаваться дальше.
                return true;
            }
        }
//Если событие, переданное в параметрах
//метода eventFilter(), не удовлетворяет двум поставленным условиям,
//то этот метод вернет значение false, и, тем самым, событие будет передано дальше
      return false;
    }
public:
    KeyFilter(QObject* pobj = 0)
        : QObject(pobj)
    {
    }
};

    Приведем часть текста программы с комментариями:

//создается объект класса QLineEdit и вызывается метода show()
QLineEdit txt;
txt.show();
//создается объект фильтра событий клавиатуры pFilter, в конструктор которого
//в качестве объекта-предка передается адрес на однострочное
//текстовое поле txt
KeyFilter* pFilter = new KeyFilter(&txt);
//созданный фильтр привязывается к текстовому полю методом installEventFilter()
txt.installEventFilter(pFilter);

    Файлы приложения можно взять здесь.

    На следующем шаге рассмотрим правила создания диалоговых окон.




Предыдущий шаг Содержание Следующий шаг