To transfer data in Qt uses signals and slots system, but this does not mean that you can not use the old proven method, namely the use CallBack functions. The fact that the use CallBack function is somewhat fast option than signals and slots. And also can be easier to use with regard to the fact that it is desirable to disconnect signals from the slots when an object sends a program signal is destroyed and is no longer used. This point is particularly relevant when one considers that, in C ++ there is no garbage collection, like Java or C# .
The principle of CallBack
The principle of CallBack To use CallBack functions in a class that will have to return a result, you must declare a pointer to a function with the same signature as the function that will be used as a CallBack function. But to place the pointer on the function you want to use a class method to apply this pointer. That is, this method is passed a pointer to a function, which is set in the CallBack class pointer, which will return the result of its activities. At the same time in this class, this pointer is used as an ordinary function that will perform the action specified in the class from which this feature has been installed as a CallBack function in the current class.
For example, the class will be used, which draws a square on the graphic scene and Control keys W, A, S, D. When moving square should send data on their coordinates in the class that was created. That is, should call this class function as its CallBack function.
Project Structure
Project Structure For a work function CallBack use project with the following structure:
- mainwindow.h - The header of the main window;
- mainwindow.cpp - source files of the main application window;
- square.h - Header class file, the object of which is to use the CallBack function.
- square.cpp - file source code of the class;
mainwindow.ui
In the main window in the designer throws a graphic scene , and objects QLineEdit class, which will display the coordinates manually create and set in this window. Since the data objects must be declared as static. The same condition should apply for CallBack function. It must also be declared as static .
mainwindow.h
Also, in the header of the main window to declare the object class 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; // We declare the square, which will transmit the callback function static QLineEdit *line1; // We declare a static QLineEdit, which will run the callback function static QLineEdit *line2; // We declare a static QLineEdit, which will run the callback function private: // Declare a callback function static void getPosition(QPointF point); }; #endif // MAINWINDOW_H
mainwindow.cpp
In addition to static objects QLineEdit their ads more necessary and implemented as functions in the source file, otherwise the compiler will announce an error. The fact that static objects necessarily need to be initialized.
#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(); // Init graphics scene ui->graphicsView->setScene(scene); // Set scene into the graphicsView scene->setSceneRect(0,0,300,300); square = new Square(); square->setCallbackFunc(getPosition); // Set callback function into th square square->setPos(100,100); scene->addItem(square); } MainWindow::~MainWindow() { delete ui; } /* callback function receives a square position and places it in a position line1 and 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
This class is inherited from QGraphicsItem and it is declared a pointer for the CallBack function and function for its installation. The function return values must be specified without fail.
#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(); // Function for setting of callback function 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; // Pointer to callback function void (*callbackFunc)(QPointF point); private slots: void slotTimer(); }; #endif // SQUARE_H
square.cpp
This example is similar in mechanics to the game, so no wonder the presence of the game timer. In the slot, connected to the signal from timer we implement movement square in the graphic scene and to transfer its coordinates in the CallBack function. And to check the status of target use functional buttons 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() { // Depending on the button pressed move the square around the stage 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); } // Call the callback function to transmit the coordinates of a square callbackFunc(this->pos()); } void Square::setCallbackFunc(void (*func)(QPointF point)) { // Set the pointer to the callback function callbackFunc = func; }
Result
As a result, you should get the app, which made management a green square, and through the CallBack feature available data on its coordinates in the class of the main window.
Хорошо объяснено, но картинку я бы заменил =) А зачем создавать статические объекты?
В данном конкретном случае сделать 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. Тогда всё можно будет сделать без статики.
Спасибо огромное!