Деректерді Qt ішінде тасымалдау үшін сигналдар мен слоттар жүйесі пайдаланылады, бірақ бұл ескі дәлелденген әдісті, атап айтқанда CallBack функцияларын пайдалану мүмкін емес дегенді білдірмейді. Мәселе мынада: Кері қоңырау функциясын пайдалану сигналдар мен ұяшықтарға қарағанда біршама жылдамырақ. Сондай-ақ оны пайдалану оңайырақ болуы мүмкін, өйткені сигнал жіберетін нысан бағдарламада жойылып, енді пайдаланылмай қалған кезде сигналдарды ұялардан ажыратқан жөн. Бұл тармақ әсіресе C++ тілінде Java немесе C# сияқты қоқыс жинаушы болмағандықтан маңызды.
Қайта шақыру функциясы қалай жұмыс істейді
CallBack функциясы қалай жұмыс істейді Нәтижені қайтаратын сыныпта CallBack функцияларын пайдалану үшін CallBack функциясы ретінде пайдаланылатын функциямен бірдей қолтаңбасы бар функция көрсеткішін жариялау керек. Функцияға көрсеткіш орнату үшін сол көрсеткішті орнату үшін класс әдісін пайдалану керек. Яғни, функцияға көрсеткіш осы әдіске жіберіледі, ол CallBack әрекетінің нәтижесін қайтаратын класс көрсеткішіне орнатылады. Сонымен қатар, осы сыныпта бұл көрсеткіш ағымдағы сыныпта CallBack функциясы ретінде орнатылған сыныпта көрсетілген әрекеттерді орындайтын тұрақты функция ретінде пайдаланылады.
Мысалы, графикалық көріністе шаршы сызатын және W, A, S, D пернелері арқылы басқарылатын класс пайдаланылады.Жылжыған кезде шаршы өзінің координаталары туралы мәліметтерді өзі құрылған классқа жіберуі керек. Яғни, ол осы класстың функциясын өзінің CallBack функциясы ретінде шақыруы керек.
Жоба құрылымы
Жоба құрылымы CallBack функциясының жұмысымен танысу үшін біз келесі құрылымы бар жобаны қолданамыз:
- mainwindow.h - қолданбаның негізгі терезесінің тақырып файлы;
- mainwindow.cpp - қолданбаның негізгі терезесіне арналған бастапқы код файлы;
- square.h - нысаны CallBack функциясын пайдаланатын сыныптың тақырып файлы.
- square.cpp - осы сынып үшін бастапқы код файлы;
mainwindow.ui
Дизайнердегі негізгі терезеде біз графикалық көрініс, және координаталары болатын QLineEdit класының нысандарын тастаймыз. көрсетілген, біз осы терезеде қолмен жасаймыз және орнатамыз. Өйткені бұл нысандар статикалық ретінде жариялануы керек. Дәл осындай шарт Кері шақыру функциясына да қолданылуы керек. Ол сондай-ақ статикалық ретінде жариялануы керек.
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
шаршы.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 функциясы бар қолданба
Хорошо объяснено, но картинку я бы заменил =) А зачем создавать статические объекты?
В данном конкретном случае сделать 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. Тогда всё можно будет сделать без статики.
Спасибо огромное!