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

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
OI
  • Ora Iro
  • 24 декабря 2024 г. 6:38

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

  • Результат:40баллов,
  • Очки рейтинга-8
AD

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

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 1:37

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

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 11:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 октября 2024 г. 14:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 8:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 7:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 11:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
Evgenii Legotckoi
Evgenii Legotckoi24 июня 2024 г. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 6:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 3:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 октября 2024 г. 9:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

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