Qt verwendet das Signal- und Slot-System, um Daten weiterzugeben, aber das bedeutet nicht, dass Sie nicht die alte bewährte Methode verwenden können, nämlich die Verwendung von CallBack -Funktionen. Der Punkt ist, dass die Verwendung der CallBack -Funktion eine etwas schnellere Option ist als Signale und Slots. Und es kann auch einfacher zu verwenden sein, da es wünschenswert ist, Signale von Slots zu trennen, wenn das das Signal sendende Objekt im Programm zerstört und nicht mehr verwendet wird. Dieser Punkt ist besonders relevant, wenn man bedenkt, dass es in C++ keinen Garbage Collector gibt, wie in Java oder C# .
So funktioniert die CallBack-Funktion
Funktionsweise der CallBack-Funktion Um CallBack -Funktionen in einer Klasse zu verwenden, die ein Ergebnis zurückgeben soll, müssen Sie einen Funktionszeiger mit derselben Signatur wie die Funktion deklarieren, die als CallBack -Funktion verwendet wird. Und um einen Zeiger auf eine Funktion zu setzen, müssen Sie eine Klassenmethode verwenden, um diesen Zeiger zu setzen. Das heißt, dieser Methode wird ein Zeiger auf eine Funktion übergeben, die auf CallBack gesetzt ist, ein Zeiger einer Klasse, die das Ergebnis ihrer Aktivität zurückgibt. Darüber hinaus wird dieser Zeiger in dieser Klasse als normale Funktion verwendet, die die angegebenen Aktionen in der Klasse ausführt, von der aus diese Funktion als CallBack -Funktion in der aktuellen Klasse festgelegt wurde.
Zum Beispiel wird eine Klasse verwendet, die ein Quadrat in einer Grafikszene zeichnet und von den Tasten W, A, S, D gesteuert wird. Beim Verschieben muss das Quadrat Daten über seine Koordinaten an die Klasse senden, in der es erstellt wurde. Das heißt, sie muss die Funktion dieser Klasse als ihre CallBack -Funktion aufrufen.
Projektstruktur
Projektstruktur Um die Arbeit der CallBack -Funktion kennenzulernen, verwenden wir ein Projekt mit folgender Struktur:
- mainwindow.h - Header-Datei des Hauptanwendungsfensters;
- mainwindow.cpp - Datei mit Quellcodes des Hauptfensters der Anwendung;
- square.h - Header-Datei der Klasse, deren Objekt die CallBack -Funktion verwendet.
- square.cpp - Quellcodedatei für diese Klasse;
mainwindow.ui
Im Hauptfenster des Designers legen wir die grafische Szene, und Objekte der QLineEdit -Klasse ab, in denen die Koordinaten angezeigt werden in diesem Fenster manuell erstellt und eingestellt. Denn diese Objekte müssen als statisch deklariert werden. Dieselbe Bedingung muss für die Funktion CallBack gelten. Es muss auch als statisch deklariert werden.
mainwindow.h
Deklarieren Sie außerdem in der Header-Datei des Hauptfensters ein Objekt der Klasse 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
Neben der Deklaration statischer QLineEdit -Objekte müssen diese auch als Funktionen in der Quellcodedatei implementiert werden, da sonst der Compiler einen Fehler deklariert. Der Punkt ist, dass statische Objekte initialisiert werden müssen.
#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;
quadrat.h
Diese Klasse erbt von QGraphicsItem und deklariert einen Zeiger für die Funktion CallBack sowie eine Funktion zum Setzen dieser. Die Funktion für Rückgabewerte muss unbedingt angegeben werden.
#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
quadrat.cpp
Dieses Beispiel ist dem Spiel mechanisch ähnlich, daher überrascht uns das Vorhandensein eines Spieltimers nicht. Durch den Slot, der mit dem Signal des Timers verbunden ist, implementieren wir die Bewegung des Quadrats über die Grafikszene und übergeben seine Koordinaten an die Funktion CallBack . Und um den Status der Zielschaltflächen zu überprüfen, verwenden wir die WinAPI. -Funktionalität.
#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; }
Ergebnis
Als Ergebnis sollten Sie eine Anwendung haben, in der das grüne Quadrat gesteuert wird und über die Funktion CallBack Daten über seine Koordinaten an die Klasse des Hauptfensters gesendet werden.
Das Video-Tutorial erklärt und demonstriert auch die Bedienung der Anwendung.
Anwendung mit CallBack-Funktion
Хорошо объяснено, но картинку я бы заменил =) А зачем создавать статические объекты?
В данном конкретном случае сделать 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. Тогда всё можно будет сделать без статики.
Спасибо огромное!