Для передачі даних у 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;
квадрат.ч
Цей клас успадковується від 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. Тогда всё можно будет сделать без статики.
Спасибо огромное!