Evgenii Legotckoi
Evgenii Legotckoi3 октября 2015 г. 12:56

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

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

Видеоурок

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

ЛП
  • 27 марта 2017 г. 4:17

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

QLineEdit * MainWindow::line1;

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

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

ЛП
  • 27 марта 2017 г. 6:59

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

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

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 г. 10:35

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

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

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

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

c
  • 21 апреля 2018 г. 11: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;
Evgenii Legotckoi
  • 22 апреля 2018 г. 6: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.

c
  • 22 апреля 2018 г. 7: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.
Evgenii Legotckoi
  • 22 апреля 2018 г. 7: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++.

А
  • 12 февраля 2019 г. 3:19
  • (ред.)

День добрый! Можешь выложить форму mainwindow.ui от урока? Заранее спасибо

А
  • 12 февраля 2019 г. 4:26

Сам разборался, спасибо.

R
  • 3 августа 2020 г. 11:56

Добрый день, объясните, пожалуйста, почему функция объявлена статической?

Evgenii Legotckoi
  • 4 августа 2020 г. 5:24

Если не объявлять статической, то не соберётся. Не получится сделать привязку метода.
Дело в том, что в процессе компиляции производится сборка с указанием конкретных участков кода в данном случае. А передача в качестве аргумента нестатического метода приводит к привязке к динамически вызываемой части кода, поскольку объект должен быть создан в процессе работы программы.
Обычные callback функции не должны быть частью класса, но статические методы являются глобальными для класса. Поэтому есть возможность их передавать в качестве callback.
Но вообще это вполне возможно сделать без статического объявления функции, если использовать в качестве передаваемого аргумента std::function объект . И в данном случае уже передавать лямбда функцию с замыканием на конкретный объект, то есть на объект MainWindow. Тогда всё можно будет сделать без статики.

R
  • 4 августа 2020 г. 5:53

Спасибо огромное!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
ОН

C++ - Тест 006. Перечисления

  • Результат:10баллов,
  • Очки рейтинга-10
K
  • KiRi4
  • 7 сентября 2023 г. 17:57

C++ - Тест 002. Константы

  • Результат:41баллов,
  • Очки рейтинга-8
K
  • KiRi4
  • 7 сентября 2023 г. 17:49

C++ - Тест 001. Первая программа и типы данных

  • Результат:66баллов,
  • Очки рейтинга-1
Последние комментарии
IscanderChe
IscanderChe13 сентября 2023 г. 19:11
Пример использования QScintilla C++ По горячим следам (с другого форума вопрос задали, пришлось в памяти освежить всё) решил дополнить. Качаем исходники с https://riverbankcomputing.com/software/qscintilla/downlo…
Evgenii Legotckoi
Evgenii Legotckoi6 сентября 2023 г. 17:18
Qt/C++ - Урок 048. QThread - работа с потоками с помощью moveToThread Разве могут взаимодействовать объекты из разных нитей как-то, кроме как через сигнал-слоты?" Могут. Выполняя оператор new , Вы выделяете под объект память в куче (heap), …
AC
Andrei Cherniaev5 сентября 2023 г. 13:37
Qt/C++ - Урок 048. QThread - работа с потоками с помощью moveToThread Я поясню свой вопрос. Выше я писал "Почему же в методе MainWindow::on_write_1_clicked() Можно обращаться к методам exampleObject_1? Разве могут взаимодействовать объекты из разных…
n
nvn31 августа 2023 г. 19:47
QML - Урок 004. Сигналы и слоты в Qt QML Здравствуйте! Прекрасный сайт, отличные статьи. Не хватает только готовых проектов для скачивания. Многих комментариев типа appCore != AppCore просто бы не было )))
NSProject
NSProject24 августа 2023 г. 23:40
Django - Урок 023. Like Dislike система с помощью GenericForeignKey Ваша ошибка связана с gettext from django.utils.translation import gettext_lazy as _ Поле должно выглядеть так vote = models.SmallIntegerField(verbose_name=_("Голос"), choices=VOTES) …
Сейчас обсуждают на форуме
IscanderChe
IscanderChe17 сентября 2023 г. 19:24
Интернационализация строк в QMessageBox Странная картина... Сделал минимально работающий пример - всё работает. Попробую на другой операционке. Может, дело в этом.
NSProject
NSProject17 сентября 2023 г. 18:49
Помогите добавить Ajax в проект В принципе ничего сложного с отправкой на сервер нет. Всё что ты хочешь отобразить на странице передаётся в шаблон и рендерится. Ты просто создаёшь файл forms.py в нём описываешь свою форму и в …
BlinCT
BlinCT15 сентября 2023 г. 22:35
Размеры полей в TreeView Всем привет. Пытаюсь сделать дерево вот такого вида Пытаюсь организовать делегат для каждой строки в дереве. ТО есть отступ какого то размера и если при открытии есть под…
IscanderChe
IscanderChe8 сентября 2023 г. 22:07
Кастомная QAbstractListModel и цвет фона, цвет текста и шрифт Похоже надо не абстрактный , а "реальный" типа QSqlTableModel Да, но не совсем. Решилось с помощью стайлшитов и setFont. Спасибо за отлик!
Evgenii Legotckoi
Evgenii Legotckoi6 сентября 2023 г. 16:35
Вопрос: Нужно ли в деструкторе удалять динамически созданные QT-объекты. Напр: Зависит от того, как эти объекты были созданы. Если вы передаёте указатель на parent объект, то не нужно, Ядро Qt само разрулит удаление, если нет, то нужно удалять вручную, иначе будет ут…

Следите за нами в социальных сетях