Для передачи данных в Qt используется система сигналов и слотов, но это не означает, что нельзя применить старый проверенный способ, а именно использование CallBack функций. Дело в том, что использование CallBack функции является несколько более быстродействующим вариантом, чем сигналы и слоты. А также может быть более легким в применении, что касается того, что сигналы желательно отсоединять от слотов, когда объект посылающий сигнал уничтожается в программе и больше не используется. Этот момент особенно актуален, если учитывать то, что в C++ отсутствует сборщик мусора, как в Java или C# .
Принцип работы CallBack функции
Принцип работы CallBack функции Для использования CallBack функций в классе, который должен будет возвращать результат, необходимо объявить указатель на функцию с такой же сигнатурой, как и у функции, которая будет использована в качестве CallBack функции. А для установки указателя на функцию необходимо использовать метод класса для установки этого указателя. То есть в этот метод передаётся указатель на функцию, который устанавливается в CallBack указатель класса, который будет возвращать результат своей деятельности. При этом в данном классе этот указатель используется как обычная функция, которая будет совершать заданные действия в классе, из которого эта функция была установлена в качестве CallBack функции в текущем классе.
Для примера будет использован класс, который отрисовывает квадрат на графической сцене и управляется клавишами W, A, S, D. При движении квадрат должен отсылать данные о своих координатах в класс, в котором был создан. То есть должен вызывать функцию данного класса в качестве своей CallBack функции.
Структура проекта
Структура проекта Для ознакомления с работой CallBack функции используем проект со следующей структурой:
- mainwindow.h - Заголовочный файл главного окна приложения;
- mainwindow.cpp - Файл исходных кодов главного окна приложения;
- square.h - Заголовочный файл класса, объект которого будет использовать CallBack функцию.
- square.cpp - файл исходных кодов данного класса;
mainwindow.ui
В главном окне в дизайнере закидываем графическую сцену, а объекты класса QLineEdit, в которых будут отображаться координаты создаём и устанавливаем вручную в данное окно. Поскольку данные объекты должны быть объявлены как static. Это же условие должно применяться и для CallBack функции. Она также должна быть объявлена как static .
mainwindow.h
Также в заголовочном файле главного окна объявляем объект класса Square.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QGraphicsScene> #include <QLineEdit> #include <square.h> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; QGraphicsScene *scene; // Объявляем графическую сцену Square *square; // Объявляем квадрат, в который будем передавать callback функцию static QLineEdit *line1; // Объявляем static QLineEdit, с которым будет работать callback функция static QLineEdit *line2; // Объявляем static QLineEdit, с которым будет работать callback функция private: // Объявляем callback функцию static void getPosition(QPointF point); }; #endif // MAINWINDOW_H
mainwindow.cpp
Помимо объявления статических объектов QLineEdit их ещё необходимо и реализовать как функции в файле исходных кодов, иначе компилятор будет объявлять ошибку. Дело в том, что статические объекты обязательно необходимо инициализировать.
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // Инициализируем объекты QLineEdit line1 = new QLineEdit(); line2 = new QLineEdit(); // Устанавлвиваем их в gridLayout ui->gridLayout->addWidget(line1,0,1); ui->gridLayout->addWidget(line2,0,2); scene = new QGraphicsScene(); // Инициализируем графическю сцену ui->graphicsView->setScene(scene); // Устанавливаем сцену в graphicsView scene->setSceneRect(0,0,300,300); // Устанавливаем область сцены square = new Square(); // Инициализируем квадрат square->setCallbackFunc(getPosition); // Устанавливаем в квадрат callback функцию square->setPos(100,100); // Устанавливаем стартовую позицию квадрата scene->addItem(square); // Добавляем квадрат на графическую сцену } MainWindow::~MainWindow() { delete ui; } /* callback функция получает позицию квадрата * и помещает его координаты в line1 и line2 * */ void MainWindow::getPosition(QPointF point) { line1->setText(QString::number(point.x())); line2->setText(QString::number(point.y())); } QLineEdit * MainWindow::line1; QLineEdit * MainWindow::line2;
square.h
Данный класс наследуется от QGraphicsItem и в нём объявляется указатель для CallBack функции, а также функция для её установки. Функцию для возвращаемых значений необходимо указывать в обязательном порядке.
#ifndef SQUARE_H #define SQUARE_H #include <QObject> #include <QGraphicsItem> #include <QPainter> #include <QTimer> #include <QPointF> class Square : public QObject, public QGraphicsItem { Q_OBJECT public: explicit Square(QObject *parent = 0); ~Square(); // Функция для установки callback функции void setCallbackFunc(void (*func) (QPointF point)); signals: public slots: protected: QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); private: QTimer *timer; // игровой таймер // Указатель на callback функцию void (*callbackFunc)(QPointF point); private slots: void slotTimer(); // слот игрового таймера }; #endif // SQUARE_H
square.cpp
Данный пример сходен по механике с игрой, поэтому не удивляемся наличию игрового таймера. По слоту, подключённому к сигналу от таймера мы будем реализовывать движения квадрата по графической сцене и передавать его координаты в CallBack функцию. А для проверки состояния целевых кнопок воспользуемся функционалом WinAPI.
#include "square.h" #include <windows.h> Square::Square(QObject *parent) : QObject(parent), QGraphicsItem() { // Инициализируем и настраиваем игровой таймер timer = new QTimer(); connect(timer, &QTimer::timeout, this, &Square::slotTimer); timer->start(1000/33); } Square::~Square() { } QRectF Square::boundingRect() const { return QRectF(-15,-15,30,30); } void Square::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { painter->setPen(Qt::black); painter->setBrush(Qt::green); painter->drawRect(-15,-15,30,30); Q_UNUSED(option); Q_UNUSED(widget); } void Square::slotTimer() { // В зависимости от нажатых кнопок перемещаем квадрат по сцене if(GetAsyncKeyState('A')){ this->setX(this->x() - 2); } if(GetAsyncKeyState('D')){ this->setX(this->x() + 2); } if(GetAsyncKeyState('W')){ this->setY(this->y() - 2); } if(GetAsyncKeyState('S')){ this->setY(this->y() + 2); } // Вызываем callback функцию для передачи координат квадрата callbackFunc(this->pos()); } void Square::setCallbackFunc(void (*func)(QPointF point)) { // Устанавливаем указатель на callback функцию callbackFunc = func; }
Итог
В результате у Вас должно получиться Приложение, в котором производится управление зеленым квадратиком, а через CallBack функцию высылаются данные о его координатах в класс главного окна.
В видеоуроке также даны пояснения и продемонстрирована работа Приложения.
Приложение с CallBack функцией
Хорошо объяснено, но картинку я бы заменил =) А зачем создавать статические объекты?
В данном конкретном случае сделать QLineEdit статическим является самым простым способом в принципе работать с этим объектом внутри статической функции. Если удалить static у QLineEdit, то метод getPosition выбросит ошибку, что невозможно чего-то там... точной формулировки не помню.
Недостаток статических методов в том, что они не будут работать с полями членами класса, если те не будут статическими. Есть конечно, ещё варианты несколько иначе получать id окна, с помощью API операционной системы, по нему кастовать полученный объект в MainWindow, а потом пройтись по child Объектам, найти нужный QLineEdit. Слишком геморно и вообще статья не о том.
У меня еще вопросы! 1.
"записали" координаты в указатель на функцию без реализации. 2.1. Что значит без реализации? Это функция имеет реализацию. И реализацией это функции является метод:
Да, это работать не будет, если не будет установлена соответствующая функция перед тем, как её использовать, но она в этом примере устанавливается:
2. Вообще никакой логики:
В корне неправильный подход. func - Это аргумент, который передаётся в класс для установки в качестве callback . А переменная callbackFunc - это просто указатель на функцию, в который нужно установить эту callback функцию, то есть аргумент func . Изначально там содержится nullptr , а если будет вызываться nullptr , то будет краш программы. А если Вы попытаетесь установить nullptr в указатель, который указывает на метод класса, который реализован, то будет краш программы.
Проще говоря, работать даже не будет. Это ещё с объектами такое можно будет сделать, когда например забрать указатель на какой-то объект класса извне. Да и то это в корне неправильный подход. Для такого существуют функции геттеры ( SomeClass* getSomeObject() , например )
А что означает
? Я просто меняю значение аргументов у указателя?callbackFunc - это указатель на некую функцию, сигнатура которой указана в заголовочном файле класса Square
В качестве callbackFunc выступает метод void MainWindow::getPosition(QPointF point) . Посмотрите сигнатуру (сигнатура, то есть объявление идентично сигнатуре callbackFunc ) и реализацию этого метода. Вот что в нём реализовано то он и делает. А аргументы в функцию передаются, а что с ними уже происходит - это вопрос реализации.
I don’t understand in Mainwindow.cpp lines 40 + 41 what or how these lines work? They look like a declaration but they are in the implementation which doesnt make sense to me. Please explain:
There are static members of class. There in cpp file it isn`t declaration of these members, it`s implementation without assigning a value. Some value will be assigned to these members in constructor later.
It is especciality of workflow with static members.
And I think using of nullptr instead of NULL is better. Because of using of nullptr is modern standard of C++.
День добрый! Можешь выложить форму mainwindow.ui от урока? Заранее спасибо
Сам разборался, спасибо.
Добрый день, объясните, пожалуйста, почему функция объявлена статической?
Если не объявлять статической, то не соберётся. Не получится сделать привязку метода.
Дело в том, что в процессе компиляции производится сборка с указанием конкретных участков кода в данном случае. А передача в качестве аргумента нестатического метода приводит к привязке к динамически вызываемой части кода, поскольку объект должен быть создан в процессе работы программы.
Обычные callback функции не должны быть частью класса, но статические методы являются глобальными для класса. Поэтому есть возможность их передавать в качестве callback.
Но вообще это вполне возможно сделать без статического объявления функции, если использовать в качестве передаваемого аргумента std::function объект . И в данном случае уже передавать лямбда функцию с замыканием на конкретный объект, то есть на объект MainWindow. Тогда всё можно будет сделать без статики.
Спасибо огромное!