© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB

Qt/C++ - Урок 026. Применение CallBack функции

callback c++, callback, callback функция, Qt callback, Qt урок

Для передачи данных в 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 функцией

Видеоурок

Комментарии

27 марта 2017 г. 8:17

Хорошо объяснено, но картинку я бы заменил =) А зачем создавать статические объекты?

QLineEdit * MainWindow::line1;
27 марта 2017 г. 8:45

В данном конкретном случае сделать QLineEdit статическим является самым простым способом в принципе работать с этим объектом внутри статической функции. Если удалить static у QLineEdit, то метод getPosition выбросит ошибку, что невозможно чего-то там... точной формулировки не помню.

Недостаток статических методов в том, что они не будут работать с полями членами класса, если те не будут статическими. Есть конечно, ещё варианты несколько иначе получать id окна, с помощью API операционной системы, по нему кастовать полученный объект в MainWindow, а потом пройтись по child Объектам, найти нужный QLineEdit. Слишком геморно и вообще статья не о том.

27 марта 2017 г. 10:59

У меня еще вопросы! 1.

callbackFunc(this->pos());
"записали" координаты в указатель на функцию без реализации. 2.
 
void Square::setCallbackFunc(void (*func)(QPointF point))
{
    // Устанавливаем указатель на callback функцию
    func = callbackFunc ; // не логично ли такое присваивание?
}
27 марта 2017 г. 11:47

1. Что значит без реализации? Это функция имеет реализацию. И реализацией это функции является метод:

void MainWindow::getPosition(QPointF point)

Да, это работать не будет, если не будет установлена соответствующая функция перед тем, как её использовать, но она в этом примере устанавливается:

square->setCallbackFunc(getPosition);   // Устанавливаем в квадрат callback функцию

2. Вообще никакой логики:

void Square::setCallbackFunc(void (*func)(QPointF point))
{
    // Устанавливаем указатель на callback функцию
    func = callbackFunc ; // не логично ли такое присваивание?
}

В корне неправильный подход. func - Это аргумент, который передаётся в класс для установки в качестве callback . А переменная callbackFunc - это просто указатель на функцию, в который нужно установить эту callback функцию, то есть аргумент func . Изначально там содержится nullptr , а если будет вызываться nullptr , то будет краш программы. А если Вы попытаетесь установить nullptr в указатель, который указывает на метод класса, который реализован, то будет краш программы.

Проще говоря, работать даже не будет. Это ещё с объектами такое можно будет сделать, когда например забрать указатель на какой-то объект класса извне. Да и то это в корне неправильный подход. Для такого существуют функции геттеры ( SomeClass* getSomeObject() , например )

27 марта 2017 г. 14:35

А что означает

callbackFunc(this->pos())
? Я просто меняю значение аргументов у указателя?
27 марта 2017 г. 15:46

callbackFunc - это указатель на некую функцию, сигнатура которой указана в заголовочном файле класса Square

В качестве callbackFunc выступает метод void MainWindow::getPosition(QPointF point) . Посмотрите сигнатуру (сигнатура, то есть объявление идентично сигнатуре callbackFunc ) и реализацию этого метода. Вот что в нём реализовано то он и делает. А аргументы в функцию передаются, а что с ними уже происходит - это вопрос реализации.

21 апреля 2018 г. 15:53

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:


  1. QLineEdit * MainWindow::line1;
  2. QLineEdit * MainWindow::line2;
22 апреля 2018 г. 10:34

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.

22 апреля 2018 г. 11:26
That is what I thought however do not understand why it is necessary. I guess the format was new and unfamiliar to me. Found other examples online where the value was assigned to NULL. That worked as well.
22 апреля 2018 г. 11:30

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++.

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
14 августа 2018 г. 11:29
Марк Федяшов

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат 70баллов,
  • Очки рейтинга1
14 августа 2018 г. 11:05
Марк Федяшов

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат 50баллов,
  • Очки рейтинга-4
14 августа 2018 г. 11:00
Марк Федяшов

C++ - Тест 003. Условия и циклы

  • Результат 42баллов,
  • Очки рейтинга-8
Последние комментарии
10 августа 2018 г. 13:40
Alex

Работа с триггерными функциями в PostgreSQL

Приветствую! Если вы создаете новую таблицу, почему бы просто не сделать вьюху ? Просто от одного названия "триггер" как-то не хочется его использовать, а уж кода сколько писа...
10 августа 2018 г. 11:46
Евгений Легоцкой

Bash скрипт для создания и скачивания дампа базы данных и медиа файлов с удаленного сервера

Вон оно что. Не сталкивался с таким, надо будет глянуть исходники дефолтного менеджера объектов. Возможно там кеширование просто. Пробовали добавить запись через adminer, перезапусти...
10 августа 2018 г. 11:34
Alex

Bash скрипт для создания и скачивания дампа базы данных и медиа файлов с удаленного сервера

допустим у нас есть любая таблица, созданная джангой. через админку добавляем пару записей. все ок. далее, лично в моем случае , я открываю adminer, и в эту таблицу добавляю еще одну зап...
Сейчас обсуждают на форуме
14 августа 2018 г. 7:02
Ruslan-maniak

Переключение страниц и перевод фокуса на потомка новой страницы

Большое спасибо. Подтолкнули меня на мысль вынести обработку клавиш из PathView на всю страницу. И тогда - да, ваша подсказка работает. добавил в StackView onCurrentItemChanged: currentItem.fo...
14 августа 2018 г. 6:39
Евгений Легоцкой

Как сделать аудиовизуализацию для плеера на qt?

Добрый день. Просмотрите пример в Qt Creator, который на QML, там реализовано визуализация, возможно вам понравится использовать, QML, да и кастомные интерфейсы на нём всё-таки лучше...
11 августа 2018 г. 10:12
Евгений Легоцкой

Qt C++ vs QML

Добрый день. Если Андроид предполагается, то конечно нужно использовать QML. Я занимался разработкой арканоида на QML и ещё одной игры. Пытался реализовывать логику на QML, но это ...
11 августа 2018 г. 9:24
Евгений Легоцкой

Помогите со слоями

Проверочное сообщение
9 августа 2018 г. 13:27
Иринка Садыкова

как выделять текст мышкой в qml ?

я ведь использую те же средства. единственное отличие -  ввожу текст с клавиатуры в TextArea

Рекомендуемые страницы