#include <QGraphicsItem> #include "paintscene.h" #...
Créé le : 3 mars 2025
Créé le : 3 mars 2025
#include <QGraphicsItem>
#include "paintscene.h"
#include "rect.h"
#include "triangle.h"
#include "ellipse.h"
#include "rhombus.h"
#include "square.h"
#include "hexagon.h"
#include "fivepointedstar.h"
#include "sixpointedstar.h"
#include "eightpointedstar.h"
#include "circle.h"
PaintScene::PaintScene(QObject *parent) : QGraphicsScene(parent)
{
}
void PaintScene::setMode(Mode mode)
{ // if (figure)
// {
currentMode = mode;
}
int PaintScene::figureType() const
{
return tempFigureType;
}
void PaintScene::setFigureType(const int type)
{
tempFigureType = type;
}
QString PaintScene::calculatePerimeter()
{
QList<QGraphicsItem *> selectedItems = this->selectedItems();
textif (!selectedItems.isEmpty()) { QGraphicsItem *selectedItem = selectedItems.first(); Figure *figure = dynamic_cast<Figure *>(selectedItem); // if (figure) // { double perimeter = figure->calculatePerimeter(); QString perimeterText = QString::number(perimeter); qDebug() << "perimeter:" << perimeterText; emit perimeterCalculated(perimeterText); return perimeterText; // } } else return "0.0";
}
// QString PaintScene::calculatePerimeter(QGraphicsSceneMouseEvent *event)
// {
// QList<QGraphicsItem *> selectedItems = this->selectedItems();
// //selectedItem = itemAt(event->scenePos(), QTransform());
// if (!selectedItems.empty())
// {
// QGraphicsItem *selectedItem = selectedItems.first();
// //selectedItem->setSelected(true);
// if (selectedItem->type() == RectangleType || selectedItem->type() == SquareType)
// {
// widthItem = selectedItem->boundingRect().width();
// heightItem = selectedItem->boundingRect().height();
// perimeterValue = 2.0 * (widthItem + heightItem);
// perimeterValueText = QString::number(perimeterValue);
// }
// }
// else
// perimeterValueText = "0";
// emit perimeterCalculated(perimeterValueText);
// qDebug() << "signal " << perimeterValueText;
// return perimeterValueText;
// }
void PaintScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (currentMode == DrawingMode)
{
switch (tempFigureType)
{
case RectangleType:
{
Rect *item = new Rect(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case TriangleType:
{
Triangle *item = new Triangle(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case EllipseType:
{
Ellipse *item = new Ellipse(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case RhombusType:
{
Rhombus *item = new Rhombus(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case SquareType:
{
Square *item = new Square(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case HexagonType:
{
Hexagon *item = new Hexagon(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case Star5Type:
{
FivePointedStar *item = new FivePointedStar(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case Star6Type:
{
SixPointedStar *item = new SixPointedStar(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case Star8Type:
{
EightPointedStar *item = new EightPointedStar(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case CircleType:
{
Circle *item = new Circle(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
}
this->addItem(tempFigure);
}
else if (currentMode == MovingMode)
{
// Режим перемещения
selectedItem = itemAt(event->scenePos(), QTransform());
textif (selectedItem) { selectedItem->setSelected(true); // Выделяем элемент offset = event->scenePos() - selectedItem->pos(); } } else if (currentMode == RotatingMode) { rotatingItem = itemAt(event->scenePos(), QTransform()); if (rotatingItem) { isRotating = true; rotationCenter = rotatingItem->boundingRect().center(); rotatingItem->setTransformOriginPoint(rotatingItem->boundingRect().center()); startAngle = std::atan2(event->scenePos().y() - rotationCenter.y(), event->scenePos().x() - rotationCenter.x()); } } else if (currentMode == ScalingMode) { scalingItem = itemAt(event->scenePos(), QTransform()); if (scalingItem) { scalingItem->setTransformOriginPoint(scalingItem->boundingRect().center()); startPointScaling = event->scenePos(); currentScale = scalingItem->scale(); } }
}
void PaintScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
// tempFigure->setEndPoint(event->scenePos());
// this->update(QRectF(0, 0, this->width(), this->height()));
textif (currentMode == DrawingMode && tempFigure) { // Режим рисования tempFigure->setEndPoint(event->scenePos()); // this->update(QRectF(0, 0, this->width(), this->height())); this->update(); } else if (currentMode == MovingMode && selectedItem) { selectedItem->setPos(event->scenePos() - offset); // Перемещаем элемент this->update(); } else if (currentMode == RotatingMode && isRotating && rotatingItem) { qreal currentAngle = std::atan2(event->scenePos().y() - rotationCenter.y(), event->scenePos().x() - rotationCenter.x()); qreal angleDif = (currentAngle - startAngle) * 180 / M_PI; rotatingItem->setTransformOriginPoint(rotatingItem->boundingRect().center()); rotatingItem->setRotation(rotatingItem->rotation() + angleDif); startAngle = currentAngle; this->update(); } else if (currentMode == ScalingMode && scalingItem) { QPointF currentPoint = event->scenePos(); qreal scaleCoefficient = 1.0 + (currentPoint.y() - startPointScaling.y()) / 100.0; if (scaleCoefficient > 0.1) scalingItem->setScale(currentScale * scaleCoefficient); this->update(); }
}
void PaintScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (currentMode == MovingMode && selectedItem)
{
selectedItem->setSelected(false);
selectedItem = nullptr;
}
else if (currentMode == DrawingMode)
{
tempFigure = nullptr;
}
else if (currentMode == RotatingMode)
{
isRotating = false;
rotatingItem = nullptr;
}
else if (currentMode == ScalingMode && scalingItem)
{
scalingItem = nullptr;
}
QGraphicsScene::mouseReleaseEvent(event);
QString perimeter = calculatePerimeter();
qDebug() << "Signal emitted with perimeter:" << perimeter;
emit perimeterCalculated(perimeter);
}
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QPainter>
#include "paintscene.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
scene = new PaintScene();
text//connect(scene, &PaintScene::perimeterCalculated, this, &MainWindow::showPerimeterValue); ui->graphicsView->setScene(scene); ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); timer = new QTimer(); connect(timer, &QTimer::timeout, this, &MainWindow::slotTimer); timer->start(100);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::slotTimer()
{
timer->stop();
scene->setSceneRect(0, 0, ui->graphicsView->width() - 20, ui->graphicsView->height() - 20);
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
timer->start(100);
QMainWindow::resizeEvent(event);
}
// void MainWindow::showPerimeterValue(QGraphicsSceneMouseEvent *event)
// {
// //ui->perimeterWindow->setText(perimeterValueText);
// }
void MainWindow::on_figureType_activated(int index)
{
if (index == 0)
scene->setFigureType(PaintScene::TriangleType);
else if (index == 1)
scene->setFigureType(PaintScene::EllipseType);
else if (index == 2)
scene->setFigureType(PaintScene::RhombusType);
else if (index == 3)
scene->setFigureType(PaintScene::RectangleType);
else if (index == 4)
scene->setFigureType(PaintScene::HexagonType);
else if (index == 5)
scene->setFigureType(PaintScene::SquareType);
else if (index == 6)
scene->setFigureType(PaintScene::Star5Type);
else if (index == 7)
scene->setFigureType(PaintScene::Star6Type);
else if (index == 8)
scene->setFigureType(PaintScene::Star8Type);
else if (index == 9)
scene->setFigureType(PaintScene::CircleType);
}
void MainWindow::on_modeType_activated(int index)
{
if (index == 0)
{
scene->setMode(PaintScene::DrawingMode);
}
else if (index == 1)
{
scene->setMode(PaintScene::MovingMode);
}
else if (index == 2)
{
scene->setMode(PaintScene::RotatingMode);
}
else if (index == 3)
{
scene->setMode(PaintScene::ScalingMode);
}
}
void MainWindow::showPerimeterValue(const QString &perimeterValue)
{
ui->perimeterWindow->setText("rr" + perimeterValue);
}
#include "figure.h"
#include <QPainter>
Figure::Figure(QPointF point, QObject *parent) :
QObject(parent), QGraphicsItem()
{
this->setStartPoint(mapFromScene(point)); // координаты из graphiсscene к виджету graphicsview
this->setEndPoint(mapFromScene(point));
connect(this, &Figure::pointChanged, this, &Figure::updateArea);
}
Figure::~Figure()
{
}
QPointF Figure::startPoint() const
{
return tempStartPoint;
}
QPointF Figure::endPoint() const
{
return tempEndPoint;
}
void Figure::setStartPoint(const QPointF point)
{
tempStartPoint = point;
emit pointChanged();
}
void Figure::setEndPoint(const QPointF point)
{
tempEndPoint = point;
emit pointChanged();
}
// void Figure::calculatePerimeter()
// {
// }
QRectF Figure::boundingRect() const
{
// return QRectF((endPoint().x() > startPoint().x() ? startPoint().x() : endPoint().x()) - 5,
// (endPoint().y() > startPoint().y() ? startPoint().y() : endPoint().y()) - 5,
// qAbs(endPoint().x() - startPoint().x()) + 10,
// qAbs(endPoint().y() - startPoint().y()) + 10);
return QRectF(startPoint(), endPoint()).normalized();
}
void Figure::updateArea()
{
this->update((endPoint().x() > startPoint().x() ? startPoint().x() : endPoint().x()) - 5,
(endPoint().y() > startPoint().y() ? startPoint().y() : endPoint().y()) - 5,
qAbs(endPoint().x() - startPoint().x()) + 10,
qAbs(endPoint().y() - startPoint().y()) + 10);
}напиши функцию для расчета периметра для всех фигур, периметр меняется во время рисования, масштабирования. нужно чтобы периметр постоянно менялся с изменением фигуры и показывался на экране в perimeterWindow постоянно. чтобы перейти к другой фигуре нужно на нее нажать
Ниже приведён пример, как можно организовать динамический пересчёт периметра для всех фигур и отображение результата в perimeterWindow
при каждом изменении (рисовании, масштабировании, вращении, перемещении), а также при переключении на другую фигуру по клику.
calculatePerimeter()
в базовом классе FigureЧтобы каждая фигура умела считать свой периметр, в базовом классе Figure
объявляем чисто виртуальный метод:
cpp// figure.h #ifndef FIGURE_H #define FIGURE_H #include <QObject> #include <QGraphicsItem> class Figure : public QObject, public QGraphicsItem { Q_OBJECT public: explicit Figure(QPointF point, QObject *parent = nullptr); ~Figure(); // Чтобы каждая фигура считала свой периметр virtual double calculatePerimeter() const = 0; QPointF startPoint() const; QPointF endPoint() const; void setStartPoint(const QPointF point); void setEndPoint(const QPointF point); QRectF boundingRect() const override; signals: void pointChanged(); protected: QPointF tempStartPoint; QPointF tempEndPoint; // Обновляет область перерисовки при изменении точек void updateArea(); private: // ... }; #endif // FIGURE_H
Обратите внимание, что метод calculatePerimeter()
стал чисто виртуальным (= 0
) – каждая конкретная фигура сама будет его переопределять.
calculatePerimeter()
в конкретных фигурахВ каждой фигуре (Rectangle, Triangle, Ellipse, Rhombus, Square, Hexagon, Circle, Звёзды и т.д.) переопределяем метод. Простейший пример для прямоугольника:
cpp// rect.h #ifndef RECT_H #define RECT_H #include "figure.h" class Rect : public Figure { public: explicit Rect(QPointF point, QObject *parent = nullptr) : Figure(point, parent) {} // Переопределяем метод расчёта периметра double calculatePerimeter() const override { double w = qAbs(endPoint().x() - startPoint().x()); double h = qAbs(endPoint().y() - startPoint().y()); return 2 * (w + h); } protected: // Переопределяем paint, если нужно рисовать по-своему... void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override { // ... } }; #endif // RECT_H
Аналогично для остальных фигур. Например, треугольник (если это прямоугольный треугольник по диагонали boundingRect):
cpp// triangle.h (пример для прямоугольного треугольника) #ifndef TRIANGLE_H #define TRIANGLE_H #include "figure.h" #include <cmath> class Triangle : public Figure { public: explicit Triangle(QPointF point, QObject *parent = nullptr) : Figure(point, parent) {} double calculatePerimeter() const override { double w = qAbs(endPoint().x() - startPoint().x()); double h = qAbs(endPoint().y() - startPoint().y()); // Периметр прямоугольного треугольника: a + b + c // где a = w, b = h, c = sqrt(w^2 + h^2) return w + h + std::sqrt(w*w + h*h); } protected: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override { // ... } }; #endif // TRIANGLE_H
И так далее для Ellipse
, Rhombus
, Circle
, звёзд и прочих. Например, для эллипса (приближённая формула):
cppdouble Ellipse::calculatePerimeter() const { double w = qAbs(endPoint().x() - startPoint().x()); double h = qAbs(endPoint().y() - startPoint().y()); // a и b – полуоси double a = w / 2.0; double b = h / 2.0; // Простейшее приближение периметра эллипса return 2.0 * M_PI * std::sqrt((a*a + b*b) / 2.0); }
Для ромба с диагоналями w и h:
cppdouble Rhombus::calculatePerimeter() const { double w = qAbs(endPoint().x() - startPoint().x()); double h = qAbs(endPoint().y() - startPoint().y()); // Диагонали: w, h // Сторона ромба = sqrt((w/2)^2 + (h/2)^2) double side = std::sqrt((w/2)*(w/2) + (h/2)*(h/2)); return 4.0 * side; }
И т.п. Важно, чтобы каждую фигуру вы рисовали согласованно с тем, как считаете периметр.
PaintScene
В вашем PaintScene
уже есть метод:
cppQString PaintScene::calculatePerimeter() { QList<QGraphicsItem *> selectedItems = this->selectedItems(); if (!selectedItems.isEmpty()) { QGraphicsItem *selectedItem = selectedItems.first(); Figure *figure = dynamic_cast<Figure *>(selectedItem); if (figure) { double perimeter = figure->calculatePerimeter(); QString perimeterText = QString::number(perimeter); emit perimeterCalculated(perimeterText); return perimeterText; } } return "0.0"; }
Сейчас он вызывается лишь в mouseReleaseEvent()
. Чтобы периметр менялся постоянно (во время рисования, масштабирования и т. д.), нужно вызывать этот метод после каждого изменения:
В конце mouseMoveEvent()
, когда:
currentMode == DrawingMode
),MovingMode
),RotatingMode
),ScalingMode
).При нажатии на другую фигуру (чтобы её выделить и сразу увидеть её периметр).
Примерно так:
cppvoid PaintScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (currentMode == DrawingMode) { switch (tempFigureType) { // ... создаём новую фигуру ... } // Добавляем в сцену и сразу делаем её выделенной, // чтобы calculatePerimeter() получал корректную фигуру this->addItem(tempFigure); clearSelection(); tempFigure->setSelected(true); // После создания новой фигуры сразу считаем периметр QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } else if (currentMode == MovingMode) { selectedItem = itemAt(event->scenePos(), QTransform()); if (selectedItem) { clearSelection(); selectedItem->setSelected(true); offset = event->scenePos() - selectedItem->pos(); // Пересчитать периметр при нажатии на фигуру QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } } else if (currentMode == RotatingMode) { rotatingItem = itemAt(event->scenePos(), QTransform()); if (rotatingItem) { clearSelection(); rotatingItem->setSelected(true); isRotating = true; rotationCenter = rotatingItem->boundingRect().center(); rotatingItem->setTransformOriginPoint(rotationCenter); startAngle = std::atan2(event->scenePos().y() - rotationCenter.y(), event->scenePos().x() - rotationCenter.x()); // Пересчитать периметр при нажатии QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } } else if (currentMode == ScalingMode) { scalingItem = itemAt(event->scenePos(), QTransform()); if (scalingItem) { clearSelection(); scalingItem->setSelected(true); scalingItem->setTransformOriginPoint(scalingItem->boundingRect().center()); startPointScaling = event->scenePos(); currentScale = scalingItem->scale(); // Пересчитать периметр при нажатии QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } } QGraphicsScene::mousePressEvent(event); } void PaintScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (currentMode == DrawingMode && tempFigure) { tempFigure->setEndPoint(event->scenePos()); this->update(); // Постоянно пересчитываем QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } else if (currentMode == MovingMode && selectedItem) { selectedItem->setPos(event->scenePos() - offset); this->update(); // Обновляем периметр при каждом движении QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } else if (currentMode == RotatingMode && isRotating && rotatingItem) { qreal currentAngle = std::atan2(event->scenePos().y() - rotationCenter.y(), event->scenePos().x() - rotationCenter.x()); qreal angleDif = (currentAngle - startAngle) * 180 / M_PI; rotatingItem->setRotation(rotatingItem->rotation() + angleDif); startAngle = currentAngle; this->update(); // Периметр может не меняться при вращении (для круга – не изменится), // но если хотите всё равно пересчитывать: QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } else if (currentMode == ScalingMode && scalingItem) { QPointF currentPoint = event->scenePos(); qreal scaleCoefficient = 1.0 + (currentPoint.y() - startPointScaling.y()) / 100.0; if (scaleCoefficient > 0.1) scalingItem->setScale(currentScale * scaleCoefficient); this->update(); // При масштабировании периметр меняется QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } QGraphicsScene::mouseMoveEvent(event); } void PaintScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (currentMode == MovingMode && selectedItem) { selectedItem->setSelected(false); selectedItem = nullptr; } else if (currentMode == DrawingMode) { // Закончили рисовать tempFigure = nullptr; } else if (currentMode == RotatingMode) { isRotating = false; rotatingItem = nullptr; } else if (currentMode == ScalingMode && scalingItem) { scalingItem = nullptr; } QGraphicsScene::mouseReleaseEvent(event); // На случай, если хотим снова обновить после отпускания мыши QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); }
Таким образом, при каждом движении/нажатии/отпускании мыши мы пересчитываем периметр выбранной (или только что созданной) фигуры и отправляем сигнал perimeterCalculated
.
MainWindow
В MainWindow
уже есть слот showPerimeterValue(const QString&)
. Нужно убедиться, что сигнал/слот связаны:
cpp// В конструкторе MainWindow MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); scene = new PaintScene(); // Подключаем сигнал connect(scene, &PaintScene::perimeterCalculated, this, &MainWindow::showPerimeterValue); ui->graphicsView->setScene(scene); // ... }
А в самом слоте просто обновляем perimeterWindow
:
cppvoid MainWindow::showPerimeterValue(const QString &perimeterValue) { // Например, просто показываем число ui->perimeterWindow->setText(perimeterValue); }
Теперь, когда вы рисуете новую фигуру или кликаете на другую, сцена делает её выделенной и вызывает calculatePerimeter()
, посылает сигнал — и в MainWindow
вы видите результат в perimeterWindow
. При перемещении/масштабировании/вращении периметр будет пересчитываться на лету.
calculatePerimeter()
, который учитывает, как именно вы рисуете фигуру (какие стороны, формулы и т.д.).PaintScene
надо после каждого изменения (mousePress, mouseMove, mouseRelease — в зависимости от режима) звать calculatePerimeter()
для текущей выделенной фигуры.perimeterCalculated
вы передаёте результат в MainWindow
, где в слоте обновляете ui->perimeterWindow
.Таким образом, периметр будет динамически меняться и постоянно отображаться на экране. А при клике на другую фигуру произойдёт выделение этой фигуры, и вы сразу увидите её периметр.
perimeterWindow выводит 0.0 вне зависимости от изменения фигуры. как сделать так, чтобы значение периметра всегда менялось
Чтобы периметр действительно пересчитывался при каждом изменении и не был равен «0.0», нужно убедиться в двух ключевых вещах:
Каждая конкретная фигура (Rect, Triangle, Ellipse и т. д.) действительно переопределяет метод calculatePerimeter()
, и в нём возвращается корректное значение (учитывая startPoint()
, endPoint()
и логику вычисления периметра).
В сцене при любом изменении (рисование/масштабирование/вращение/перемещение) фигура действительно “выделена” (selected) и вызывается ваш метод calculatePerimeter()
, который берёт именно выделенный элемент через selectedItems()
. Если фигура не выделена, selectedItems()
будет пустым, и метод вернёт 0.0.
Ниже пошагово, как это исправить.
calculatePerimeter()
В файле figure.h
:
cppclass Figure : public QObject, public QGraphicsItem { Q_OBJECT public: // ... // Добавляем чисто виртуальный метод — каждая фигура переопределит. virtual double calculatePerimeter() const = 0; // ... };
В каждой наследованной фигуре (Rect, Triangle, Ellipse и т. п.) нужно переопределить этот метод:
cppdouble Rect::calculatePerimeter() const { double w = qAbs(endPoint().x() - startPoint().x()); double h = qAbs(endPoint().y() - startPoint().y()); return 2 * (w + h); }
Убедитесь, что у всех фигур формула периметра корректная, иначе при рисовании всегда будет возвращаться либо 0, либо неправильное значение.
В вашем mousePressEvent
, когда рисуется новая фигура, вы просто создаёте tempFigure
и делаете addItem(tempFigure)
. Но в PaintScene::calculatePerimeter()
идёт поиск выделенного (selectedItems()
).
Значит, нужно «снять выделение со старой» и выделить новую. Например, так:
cppvoid PaintScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (currentMode == DrawingMode) { switch (tempFigureType) { case RectangleType: { Rect *item = new Rect(event->scenePos()); item->setPos(event->pos()); tempFigure = item; break; } // ... и т.д. для других фигур ... } // Добавляем в сцену this->addItem(tempFigure); // Очистим предыдущее выделение clearSelection(); // Сразу выделяем только что созданную фигуру tempFigure->setSelected(true); // Сразу после создания можно пересчитать периметр QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } // ... // Остальные режимы: MovingMode, RotatingMode, ScalingMode // там аналогично можно делать clearSelection() и setSelected(true) // когда пользователь кликает на нужную фигуру // ... QGraphicsScene::mousePressEvent(event); }
Теперь tempFigure
гарантированно будет выделена (selected), и, следовательно, selectedItems().first()
вернёт именно её.
mouseMoveEvent
и mouseReleaseEvent
Чтобы периметр “жил” и менялся во время рисования и других операций, нужно после каждого изменения вызывать ваш метод calculatePerimeter()
. Например:
cppvoid PaintScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (currentMode == DrawingMode && tempFigure) { // Меняем endPoint у временной фигуры: tempFigure->setEndPoint(event->scenePos()); this->update(); // Пересчитываем периметр для выделенной фигуры QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } else if (currentMode == MovingMode && selectedItem) { selectedItem->setPos(event->scenePos() - offset); this->update(); // Пересчитываем QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } else if (currentMode == RotatingMode && isRotating && rotatingItem) { // ... rotatingItem->setRotation(rotatingItem->rotation() + angleDif); startAngle = currentAngle; this->update(); // Пересчитать периметр (на случай, если хотите отслеживать // изменения периметра при вращении) QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } else if (currentMode == ScalingMode && scalingItem) { // ... scalingItem->setScale(currentScale * scaleCoefficient); this->update(); // Масштабирование влияет на размеры и, соответственно, на периметр QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); } QGraphicsScene::mouseMoveEvent(event); }
И можно дополнительно в mouseReleaseEvent
:
cppvoid PaintScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mouseReleaseEvent(event); // В конце обновим периметр (на случай, если нужно зафиксировать финальное значение) QString perimeter = calculatePerimeter(); emit perimeterCalculated(perimeter); }
perimeterWindow
подключенВ MainWindow
в конструкторе:
cppMainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); scene = new PaintScene(this); // Подключаем сигнал к слоту connect(scene, &PaintScene::perimeterCalculated, this, &MainWindow::showPerimeterValue); ui->graphicsView->setScene(scene); // ... }
И в самом слоте showPerimeterValue
:
cppvoid MainWindow::showPerimeterValue(const QString &perimeterValue) { ui->perimeterWindow->setText(perimeterValue); }
Если всё сделано правильно, то:
tempFigure->setSelected(true)
.calculatePerimeter()
.calculatePerimeter()
находит выделенную фигуру, берёт у неё calculatePerimeter()
, возвращает число, вызывает emit perimeterCalculated(...)
.MainWindow
ловится этот сигнал, и perimeterWindow
обновляет значение.Таким образом значение не будет постоянно 0.0, а будет обновляться в соответствии с формулой вашей конкретной фигуры.
как легче и лучше всего вывести информацию о периметре, площади и центре масс выбранной фигуры
Самый простой и наглядный путь – завести в интерфейсе отдельную область (например, QLabel
, QLineEdit
или QTextEdit
) для вывода информации о выделенной фигуре. Туда вы сможете одним вызовом записать всю нужную информацию: периметр, площадь и координаты центра масс.
Ниже общий алгоритм с конкретными шагами:
В figure.h
помимо calculatePerimeter()
можно объявить ещё два виртуальных метода:
cppclass Figure : public QObject, public QGraphicsItem { Q_OBJECT public: explicit Figure(QPointF point, QObject *parent = nullptr); virtual ~Figure(); // Методы для вычисления периметра, площади и центра масс virtual double calculatePerimeter() const = 0; virtual double calculateArea() const = 0; virtual QPointF calculateCenter() const = 0; // ... };
Каждая конкретная фигура (Rect, Triangle, Ellipse, Rhombus и т.п.) переопределяет все три метода:
cppdouble Rect::calculatePerimeter() const override { double w = qAbs(endPoint().x() - startPoint().x()); double h = qAbs(endPoint().y() - startPoint().y()); return 2 * (w + h); } double Rect::calculateArea() const override { double w = qAbs(endPoint().x() - startPoint().x()); double h = qAbs(endPoint().y() - startPoint().y()); return w * h; } QPointF Rect::calculateCenter() const override { // Центр – это просто midpoint между startPoint() и endPoint() double cx = (startPoint().x() + endPoint().x()) / 2.0; double cy = (startPoint().y() + endPoint().y()) / 2.0; return QPointF(cx, cy); }
Аналогично в Triangle
, Circle
, Square
и т.д.:
startPoint
до endPoint
.PaintScene
для выдачи всей информацииВместо (или вместе с) метода calculatePerimeter()
можно сделать более общий метод, возвращающий три значения (либо формировать готовую строку). Например, сделаем сигналы:
cpp// paintscene.h signals: void figureInfoChanged(const QString &info);
Вместо perimeterCalculated(...)
будем всегда вычислять полную информацию о фигуре. Пример:
cppQString PaintScene::calculateFigureInfo() { QList<QGraphicsItem*> selectedItems = this->selectedItems(); if (selectedItems.size() > 0) { Figure *figure = dynamic_cast<Figure*>(selectedItems.first()); if (figure) { double perimeter = figure->calculatePerimeter(); double area = figure->calculateArea(); QPointF center = figure->calculateCenter(); // Сформируем удобную строку QString infoString = QString("Периметр: %1\n" "Площадь: %2\n" "Центр масс: (%3; %4)") .arg(perimeter, 0, 'f', 2) // 2 знака после запятой .arg(area, 0, 'f', 2) .arg(center.x(), 0, 'f', 2) .arg(center.y(), 0, 'f', 2); return infoString; } } return "Фигура не выбрана"; }
И затем при каждом изменении вместо emit perimeterCalculated(...)
делаем:
cppvoid PaintScene::updateSelectedFigureInfo() { QString info = calculateFigureInfo(); emit figureInfoChanged(info); }
Вызывать updateSelectedFigureInfo();
там же, где раньше вызывали calculatePerimeter()
, то есть в mousePressEvent
, mouseMoveEvent
, mouseReleaseEvent
– когда что-то меняется с фигурой (рисование, перемещение, поворот, масштаб).
MainWindow
и вывести в QLabel
/QLineEdit
В MainWindow
делаете слот, допустим, void MainWindow::onFigureInfoChanged(const QString &info)
. В нём берёте ui->labelFigureInfo
(или что у вас есть) и обновляете текст:
cpp// mainwindow.cpp void MainWindow::onFigureInfoChanged(const QString &info) { ui->labelFigureInfo->setText(info); }
В конструкторе MainWindow
подключаем сигнал/слот:
cppconnect(scene, &PaintScene::figureInfoChanged, this, &MainWindow::onFigureInfoChanged);
В дизайнере Qt или в коде убедитесь, что есть подходящий виджет (например, QLabel* labelFigureInfo;
) и он находится на форме (в ui->setupUi(this);
).
Теперь в местах, где вы меняете фигуру, вызывайте:
cppvoid PaintScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (currentMode == DrawingMode && tempFigure) { tempFigure->setEndPoint(event->scenePos()); update(); updateSelectedFigureInfo(); // Вызываем } else if (currentMode == MovingMode && selectedItem) { selectedItem->setPos(event->scenePos() - offset); update(); updateSelectedFigureInfo(); // Вызываем } // и т.д. QGraphicsScene::mouseMoveEvent(event); }
Аналогично – в mousePressEvent
(когда выделяем новую фигуру или кликаем по уже существующей) и в mouseReleaseEvent
.
Итого:
calculatePerimeter()
, calculateArea()
, calculateCenter()
.PaintScene
метод calculateFigureInfo()
берёт выделенный элемент (через selectedItems()
), приводит к Figure
, вызывает все три метода и собирает человекочитаемую строку.MainWindow
.MainWindow
ловим сигнал и выводим строку в специальный QLabel
или QLineEdit
.Такое решение простое, и всё всегда будет динамически обновляться.
QString PaintScene::calculateFigureInfo()
{
QList<QGraphicsItem *> selectedItems = this->selectedItems();
textif (!selectedItems.isEmpty()) { Figure *figure = dynamic_cast<Figure *>(selectedItem); if (figure) { double perimeter = figure->calculatePerimeter(); double area = figure->calculateArea(); QPointF center = figure->calculateCenter(); QString infoString = QString("Perimeter: %1\n" "Area: %2\n" "Center of mass: (%3, %4)") .arg(perimeter, 0, 'f', 2) .arg(area, 0, 'f', 2) .arg(center.x(), 0, 'f', 2) .arg(center.y(), 0, 'f', 2); return infoString; } } return "pupupu";
} почему постоянно выводит pupupu
Основная причина в том, что внутри метода вы берёте список выделенных элементов через:
cppQList<QGraphicsItem *> selectedItems = this->selectedItems();
но вместо того, чтобы вызывать dynamic_cast
к первому элементу из selectedItems
, вы делаете:
cppFigure *figure = dynamic_cast<Figure *>(selectedItem);
selectedItem
— это совсем другой указатель (вероятно, какое-то поле класса), который может быть не установлен или хранить не то, что вы ожидаете. В итоге dynamic_cast<Figure *>(selectedItem)
даёт nullptr
, и вы попадаете в ветку return "pupupu";
.
Чтобы исправить, нужно в самом методе брать именно тот элемент, который возвращается из selectedItems().first()
. Пример:
cppQString PaintScene::calculateFigureInfo() { QList<QGraphicsItem *> items = this->selectedItems(); if (!items.isEmpty()) { QGraphicsItem *item = items.first(); // берём первый выделенный Figure *figure = dynamic_cast<Figure *>(item); // делаем dynamic_cast if (figure) { double perimeter = figure->calculatePerimeter(); double area = figure->calculateArea(); QPointF center = figure->calculateCenter(); QString infoString = QString("Perimeter: %1\n" "Area: %2\n" "Center of mass: (%3, %4)") .arg(perimeter, 0, 'f', 2) .arg(area, 0, 'f', 2) .arg(center.x(), 0, 'f', 2) .arg(center.y(), 0, 'f', 2); return infoString; } } return "pupupu"; }
Теперь, если в сцене действительно что-то выделено и этот объект наследуется от Figure
, вы получите валидный указатель figure
, и метод вернёт правильные данные.
Вы выделяете (setSelected) нужную фигуру при её создании или при клике по ней:
cppclearSelection(); tempFigure->setSelected(true);
Иначе selectedItems()
просто окажется пустым.
Все ваши фигуры (Rect, Triangle, Ellipse и т. д.) действительно наследуются от Figure
и переопределяют calculatePerimeter()
, calculateArea()
и т. д. Иначе dynamic_cast
тоже будет возвращать nullptr
.
#include <QGraphicsItem>
#include "paintscene.h"
#include "rect.h"
#include "triangle.h"
#include "ellipse.h"
#include "rhombus.h"
#include "square.h"
#include "hexagon.h"
#include "fivepointedstar.h"
#include "sixpointedstar.h"
#include "eightpointedstar.h"
#include "circle.h"
PaintScene::PaintScene(QObject *parent) : QGraphicsScene(parent)
{
}
void PaintScene::setMode(Mode mode)
{ // if (figure)
// {
currentMode = mode;
}
int PaintScene::figureType() const
{
return tempFigureType;
}
void PaintScene::setFigureType(const int type)
{
tempFigureType = type;
}
void PaintScene::updateSelectedFigureInfo()
{
QString info = calculateFigureInfo();
emit figureInfoChanged(info);
}
QString PaintScene::calculateFigureInfo()
{
QList<QGraphicsItem *> items = this->selectedItems();
textif (!items.isEmpty()) { QGraphicsItem *item = items.first(); Figure *figure = dynamic_cast<Figure *>(item); if (figure) { double perimeter = figure->calculatePerimeter(); double area = figure->calculateArea(); QPointF center = figure->calculateCenter(); QString infoString = QString("Perimeter: %1\n" "Area: %2\n" "Center of mass: (%3, %4)") .arg(perimeter, 0, 'f', 2) .arg(area, 0, 'f', 2) .arg(center.x(), 0, 'f', 2) .arg(center.y(), 0, 'f', 2); return infoString; } } return "pupupu";
}
// QString PaintScene::calculatePerimeter(QGraphicsSceneMouseEvent *event)
// {
// QList<QGraphicsItem *> selectedItems = this->selectedItems();
// //selectedItem = itemAt(event->scenePos(), QTransform());
// if (!selectedItems.empty())
// {
// QGraphicsItem *selectedItem = selectedItems.first();
// //selectedItem->setSelected(true);
// if (selectedItem->type() == RectangleType || selectedItem->type() == SquareType)
// {
// widthItem = selectedItem->boundingRect().width();
// heightItem = selectedItem->boundingRect().height();
// perimeterValue = 2.0 * (widthItem + heightItem);
// perimeterValueText = QString::number(perimeterValue);
// }
// }
// else
// perimeterValueText = "0";
// emit perimeterCalculated(perimeterValueText);
// qDebug() << "signal " << perimeterValueText;
// return perimeterValueText;
// }
void PaintScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (currentMode == DrawingMode)
{
switch (tempFigureType)
{
case RectangleType:
{
Rect *item = new Rect(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case TriangleType:
{
Triangle *item = new Triangle(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case EllipseType:
{
Ellipse *item = new Ellipse(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case RhombusType:
{
Rhombus *item = new Rhombus(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case SquareType:
{
Square *item = new Square(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case HexagonType:
{
Hexagon *item = new Hexagon(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case Star5Type:
{
FivePointedStar *item = new FivePointedStar(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case Star6Type:
{
SixPointedStar *item = new SixPointedStar(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case Star8Type:
{
EightPointedStar *item = new EightPointedStar(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
case CircleType:
{
Circle *item = new Circle(event->scenePos());
item->setPos(event->pos());
tempFigure = item;
break;
}
}
this->addItem(tempFigure);
//
clearSelection();
tempFigure->setSelected(true);
textupdateSelectedFigureInfo(); } else if (currentMode == MovingMode) { // Режим перемещения selectedItem = itemAt(event->scenePos(), QTransform()); if (selectedItem) { selectedItem->setSelected(true); // Выделяем элемент offset = event->scenePos() - selectedItem->pos(); updateSelectedFigureInfo(); } } else if (currentMode == RotatingMode) { rotatingItem = itemAt(event->scenePos(), QTransform()); if (rotatingItem) { isRotating = true; rotationCenter = rotatingItem->boundingRect().center(); rotatingItem->setTransformOriginPoint(rotatingItem->boundingRect().center()); startAngle = std::atan2(event->scenePos().y() - rotationCenter.y(), event->scenePos().x() - rotationCenter.x()); updateSelectedFigureInfo(); } } else if (currentMode == ScalingMode) { scalingItem = itemAt(event->scenePos(), QTransform()); if (scalingItem) { scalingItem->setTransformOriginPoint(scalingItem->boundingRect().center()); startPointScaling = event->scenePos(); currentScale = scalingItem->scale(); updateSelectedFigureInfo(); } }
}
void PaintScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
// tempFigure->setEndPoint(event->scenePos());
// this->update(QRectF(0, 0, this->width(), this->height()));
textif (currentMode == DrawingMode && tempFigure) { // Режим рисования tempFigure->setEndPoint(event->scenePos()); // this->update(QRectF(0, 0, this->width(), this->height())); this->update(); updateSelectedFigureInfo(); } else if (currentMode == MovingMode && selectedItem) { selectedItem->setPos(event->scenePos() - offset); // Перемещаем элемент this->update(); updateSelectedFigureInfo(); } else if (currentMode == RotatingMode && isRotating && rotatingItem) { qreal currentAngle = std::atan2(event->scenePos().y() - rotationCenter.y(), event->scenePos().x() - rotationCenter.x()); qreal angleDif = (currentAngle - startAngle) * 180 / M_PI; rotatingItem->setTransformOriginPoint(rotatingItem->boundingRect().center()); rotatingItem->setRotation(rotatingItem->rotation() + angleDif); startAngle = currentAngle; this->update(); } else if (currentMode == ScalingMode && scalingItem) { QPointF currentPoint = event->scenePos(); qreal scaleCoefficient = 1.0 + (currentPoint.y() - startPointScaling.y()) / 100.0; if (scaleCoefficient > 0.1) scalingItem->setScale(currentScale * scaleCoefficient); this->update(); updateSelectedFigureInfo(); }
}
void PaintScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (currentMode == MovingMode && selectedItem)
{
selectedItem->setSelected(false);
selectedItem = nullptr;
}
else if (currentMode == DrawingMode)
{
tempFigure = nullptr;
}
else if (currentMode == RotatingMode)
{
isRotating = false;
rotatingItem = nullptr;
}
else if (currentMode == ScalingMode && scalingItem)
{
scalingItem = nullptr;
}
//QGraphicsScene::mouseReleaseEvent(event);
updateSelectedFigureInfo();
}
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QPainter>
#include "paintscene.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
scene = new PaintScene(this);
textconnect(scene, &PaintScene::figureInfoChanged, this, &MainWindow::onFigureInfoChanged); ui->graphicsView->setScene(scene); ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); timer = new QTimer(); connect(timer, &QTimer::timeout, this, &MainWindow::slotTimer); timer->start(100);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::slotTimer()
{
timer->stop();
scene->setSceneRect(0, 0, ui->graphicsView->width() - 20, ui->graphicsView->height() - 20);
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
timer->start(100);
QMainWindow::resizeEvent(event);
}
// void MainWindow::showPerimeterValue(QGraphicsSceneMouseEvent *event)
// {
// //ui->perimeterWindow->setText(perimeterValueText);
// }
void MainWindow::on_figureType_activated(int index)
{
if (index == 0)
scene->setFigureType(PaintScene::TriangleType);
else if (index == 1)
scene->setFigureType(PaintScene::EllipseType);
else if (index == 2)
scene->setFigureType(PaintScene::RhombusType);
else if (index == 3)
scene->setFigureType(PaintScene::RectangleType);
else if (index == 4)
scene->setFigureType(PaintScene::HexagonType);
else if (index == 5)
scene->setFigureType(PaintScene::SquareType);
else if (index == 6)
scene->setFigureType(PaintScene::Star5Type);
else if (index == 7)
scene->setFigureType(PaintScene::Star6Type);
else if (index == 8)
scene->setFigureType(PaintScene::Star8Type);
else if (index == 9)
scene->setFigureType(PaintScene::CircleType);
}
void MainWindow::on_modeType_activated(int index)
{
if (index == 0)
{
scene->setMode(PaintScene::DrawingMode);
}
else if (index == 1)
{
scene->setMode(PaintScene::MovingMode);
}
else if (index == 2)
{
scene->setMode(PaintScene::RotatingMode);
}
else if (index == 3)
{
scene->setMode(PaintScene::ScalingMode);
}
}
void MainWindow::onFigureInfoChanged(const QString &info)
{
ui->infoLabel->setText(info);
}
#include "figure.h"
#include <QPainter>
Figure::Figure(QPointF point, QObject *parent) :
QObject(parent), QGraphicsItem()
{
this->setStartPoint(mapFromScene(point)); // координаты из graphiсscene к виджету graphicsview
this->setEndPoint(mapFromScene(point));
connect(this, &Figure::pointChanged, this, &Figure::updateArea);
}
Figure::~Figure()
{
}
QPointF Figure::startPoint() const
{
return tempStartPoint;
}
QPointF Figure::endPoint() const
{
return tempEndPoint;
}
void Figure::setStartPoint(const QPointF point)
{
tempStartPoint = point;
emit pointChanged();
}
void Figure::setEndPoint(const QPointF point)
{
tempEndPoint = point;
emit pointChanged();
}
QRectF Figure::boundingRect() const
{
// return QRectF((endPoint().x() > startPoint().x() ? startPoint().x() : endPoint().x()) - 5,
// (endPoint().y() > startPoint().y() ? startPoint().y() : endPoint().y()) - 5,
// qAbs(endPoint().x() - startPoint().x()) + 10,
// qAbs(endPoint().y() - startPoint().y()) + 10);
return QRectF(startPoint(), endPoint()).normalized();
}
void Figure::updateArea()
{
this->update((endPoint().x() > startPoint().x() ? startPoint().x() : endPoint().x()) - 5,
(endPoint().y() > startPoint().y() ? startPoint().y() : endPoint().y()) - 5,
qAbs(endPoint().x() - startPoint().x()) + 10,
qAbs(endPoint().y() - startPoint().y()) + 10);
}
#include "rect.h"
#include <QPainter>
Rect::Rect(QPointF point, QObject *parent) :
Figure(point, parent)
{
Q_UNUSED(point)
}
Rect::~Rect()
{
}
void Rect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setPen(QPen(Qt::black, 2));
painter->drawRect((endPoint().x() > startPoint().x() ? startPoint().x() : endPoint().x()),
(endPoint().y() > startPoint().y() ? startPoint().y() : endPoint().y()),
qAbs(endPoint().x() - startPoint().x()),
qAbs(endPoint().y() - startPoint().y()));
textQ_UNUSED(option); Q_UNUSED(widget);
}
double Rect::calculatePerimeter() const
{
double w = qAbs(endPoint().x() - startPoint().x());
double h = qAbs(endPoint().y() - startPoint().y());
return 2 * (w + h);
}
double Rect::calculateArea() const
{
double w = qAbs(endPoint().x() - startPoint().x());
double h = qAbs(endPoint().y() - startPoint().y());
return w * h;
}
QPointF Rect::calculateCenter() const
{
// Центр – это просто midpoint между startPoint() и endPoint()
double cx = (startPoint().x() + endPoint().x()) / 2.0;
double cy = (startPoint().y() + endPoint().y()) / 2.0;
return QPointF(cx, cy);
}
все равно постоянно выводит pupupu
Главная причина того, что метод calculateFigureInfo()
всегда возвращает "pupupu" (т. е. не находит выделенной фигуры) – в том, что у вас в конце действий (например, при отпускании мыши в режиме перемещения) элемент снимается с выделения:
cppif (currentMode == MovingMode && selectedItem) { selectedItem->setSelected(false); // <-- снимается выделение selectedItem = nullptr; }
и после этого вы вызываете updateSelectedFigureInfo()
, а внутри него – this->selectedItems()
. Но в списке выделенных элементов уже ничего нет, следовательно, items.isEmpty()
оказывается true
, и метод возвращает "pupupu"
.
Чтобы исправить это поведение и сохранять выделение выбранной фигуры (тем самым получая корректный периметр, площадь и т. д.), удалите или закомментируйте строку selectedItem->setSelected(false);
в нужных местах (обычно в mouseReleaseEvent
) – тогда после перемещения фигура останется выделенной, и selectedItems().first()
будет возвращать вашу фигуру.
Пример, где убрано снятие выделения при отпускании мыши:
cppvoid PaintScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (currentMode == MovingMode && selectedItem) { // НЕ снимаем выделение! // selectedItem->setSelected(false); // <--- убрать/закомментировать selectedItem = nullptr; } else if (currentMode == DrawingMode) { tempFigure = nullptr; } else if (currentMode == RotatingMode) { isRotating = false; rotatingItem = nullptr; } else if (currentMode == ScalingMode && scalingItem) { scalingItem = nullptr; } updateSelectedFigureInfo(); // теперь здесь будет реальная фигура QGraphicsScene::mouseReleaseEvent(event); }
После этого, при отпускании мыши, ваша фигура останется выделенной, а вызов updateSelectedFigureInfo()
действительно найдёт её в selectedItems()
, сделает dynamic_cast<Figure*>
и вернёт корректные значения вместо "pupupu"
.
Убедитесь, что в режиме рисования вы делаете:
cppclearSelection(); tempFigure->setSelected(true);
Тогда новая фигура точно становится выделенной.
В режиме перемещения (MovingMode) тоже хорошо бы перед выделением нового объекта очистить старое выделение, если хотите иметь только одну выделенную фигуру:
cppclearSelection(); selectedItem->setSelected(true);
Иначе может быть несколько выделенных фигур (если это не нужно – уберите).
Проверьте, что все ваши классы-наследники (Rect, Triangle, Ellipse и т. д.) действительно наследуют Figure
и имеют виртуальный деструктор, чтобы dynamic_cast
работал правильно. Судя по коду, у вас это уже сделано.
Убедитесь, что в mouseMoveEvent
и mousePressEvent
в конце (или в начале) вызывается QGraphicsScene::mousePressEvent(event);
/QGraphicsScene::mouseMoveEvent(event);
/QGraphicsScene::mouseReleaseEvent(event);
, чтобы не ломать внутреннюю логику QGraphicsScene (если хотите воспользоваться частично встроенными механизмами выделения). Но в вашем случае, вы вручную делаете setSelected(true)
, так что может и не требоваться, главное – не снимайте выделение, если вам нужно сохранить выбор.
В большинстве случаев достаточно не снимать выделение в mouseReleaseEvent
(или делать это только когда пользователь явно кликнул по пустому месту) – и тогда this->selectedItems()
всегда будет содержать текущую выбранную фигуру, а метод calculateFigureInfo()
будет возвращать корректные значения.
программа не заходит в метод calculateFigureInfo как исправить
Если программа не заходит в метод calculateFigureInfo()
, значит, не вызывается метод updateSelectedFigureInfo()
. Нужно проверить, почему updateSelectedFigureInfo()
не вызывается в нужные моменты.
updateSelectedFigureInfo()
Чтобы убедиться, что метод вообще вызывается, добавьте в updateSelectedFigureInfo()
отладочный вывод:
cppvoid PaintScene::updateSelectedFigureInfo() { qDebug() << "updateSelectedFigureInfo() called"; // Проверяем вызов метода QString info = calculateFigureInfo(); emit figureInfoChanged(info); }
Запустите программу и проверьте в qDebug()
, появляется ли "updateSelectedFigureInfo() called"
.
Если нет, значит, updateSelectedFigureInfo()
вообще не вызывается → идём дальше.
updateSelectedFigureInfo()
Вы вызываете updateSelectedFigureInfo()
в нескольких местах:
В mousePressEvent()
, когда создаётся новая фигура или выбирается существующая:
cppif (currentMode == DrawingMode) { this->addItem(tempFigure); clearSelection(); tempFigure->setSelected(true); updateSelectedFigureInfo(); // Должен вызываться здесь }
Проверка: Добавьте qDebug()
перед вызовом updateSelectedFigureInfo()
:
cppqDebug() << "Mouse pressed: updateSelectedFigureInfo() should be called"; updateSelectedFigureInfo();
Если "Mouse pressed: updateSelectedFigureInfo() should be called"
не появляется в qDebug()
, значит, проблема ещё раньше – например, mousePressEvent()
вообще не вызывается.
В mouseMoveEvent()
, если фигура перемещается:
cppelse if (currentMode == MovingMode && selectedItem) { selectedItem->setPos(event->scenePos() - offset); this->update(); updateSelectedFigureInfo(); // Должен вызываться здесь }
Проверка:
cppqDebug() << "Mouse moving: updateSelectedFigureInfo() should be called";
В mouseReleaseEvent()
, после завершения действий:
cppupdateSelectedFigureInfo(); // Должен вызываться здесь
Проверка:
cppqDebug() << "Mouse released: updateSelectedFigureInfo() should be called";
Если qDebug()
не показывает эти сообщения, значит, метод updateSelectedFigureInfo()
не вызывается → ищем, почему.
mousePressEvent()
и mouseMoveEvent()
Если updateSelectedFigureInfo()
не вызывается, значит, программа не заходит в mousePressEvent()
и mouseMoveEvent()
.
Проверьте, вызываются ли они:
cppvoid PaintScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { qDebug() << "mousePressEvent triggered"; // Проверяем вызов метода ... updateSelectedFigureInfo(); }
cppvoid PaintScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { qDebug() << "mouseMoveEvent triggered"; // Проверяем вызов метода ... updateSelectedFigureInfo(); }
Если mousePressEvent()
и mouseMoveEvent()
не вызываются, значит:
PaintScene
не получает события.graphicsView
блокирует их, либо перехватывает другой объект.setScene(scene);
действительно вызывается в MainWindow
.figureInfoChanged
Вы вызываете:
cppemit figureInfoChanged(info);
Но вызывается ли слот в MainWindow
?
Проверяем:
cppvoid MainWindow::onFigureInfoChanged(const QString &info) { qDebug() << "onFigureInfoChanged() received info: " << info; ui->infoLabel->setText(info); }
Если "onFigureInfoChanged() received info: ..."
не выводится, значит:
Проблема в connect()
:
cppconnect(scene, &PaintScene::figureInfoChanged, this, &MainWindow::onFigureInfoChanged);
Проверьте, что connect()
вызывается в MainWindow
после scene = new PaintScene(this);
.
Попробуйте перезапустить connect()
вручную (например, после создания scene
).
qDebug()
в updateSelectedFigureInfo()
, calculateFigureInfo()
и mousePressEvent()
.mousePressEvent()
и mouseMoveEvent()
(qDebug()
внутри них).connect()
в MainWindow
.figureInfoChanged
до слота onFigureInfoChanged()
.Если mousePressEvent()
и mouseMoveEvent()
не вызываются – проблема в graphicsView
.
Если calculateFigureInfo()
не вызывается – проблема в selectedItems()
.
Если onFigureInfoChanged()
не вызывается – проблема в connect()
.
метод вызывается, но информация не меняется никак!!
что делать, помоги пожалуйста((((
Раз метод вызывается, но информация не меняется, скорее всего, проблема в получении выделенного элемента (selectedItems()
) или в передаче данных в UI. Давай разберёмся по шагам.**
Добавь отладку внутрь calculateFigureInfo()
:
cppQString PaintScene::calculateFigureInfo() { QList<QGraphicsItem *> items = this->selectedItems(); qDebug() << "calculateFigureInfo() called. Selected items count:" << items.size(); if (!items.isEmpty()) { QGraphicsItem *item = items.first(); Figure *figure = dynamic_cast<Figure *>(item); qDebug() << "Dynamic cast successful?" << (figure != nullptr); if (figure) { double perimeter = figure->calculatePerimeter(); double area = figure->calculateArea(); QPointF center = figure->calculateCenter(); qDebug() << "Perimeter:" << perimeter << "Area:" << area << "Center:" << center; QString infoString = QString("Perimeter: %1\n" "Area: %2\n" "Center of mass: (%3, %4)") .arg(perimeter, 0, 'f', 2) .arg(area, 0, 'f', 2) .arg(center.x(), 0, 'f', 2) .arg(center.y(), 0, 'f', 2); return infoString; } } qDebug() << "No valid figure selected!"; return "pupupu"; }
Теперь запусти программу и посмотри в qDebug()
, что происходит при выделении фигуры.
Вот возможные проблемы и их решения:
Лог | Проблема | Решение |
---|---|---|
calculateFigureInfo() called. Selected items count: 0 | selectedItems() пустой (нет выделенной фигуры) | Проверь setSelected(true) в mousePressEvent() |
calculateFigureInfo() called. Selected items count: 1 и Dynamic cast successful? false | dynamic_cast не сработал | Проверь, что все фигуры наследуются от Figure |
calculateFigureInfo() called. Selected items count: 1 , Dynamic cast successful? true , но Perimeter: 0 и Area: 0 | calculatePerimeter() и calculateArea() работают неправильно | Проверь код этих методов |
Если selectedItems()
пустой, значит фигура не выделяется.
Добавь qDebug()
в mousePressEvent()
:
cppvoid PaintScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (currentMode == DrawingMode) { switch (tempFigureType) { case RectangleType: tempFigure = new Rect(event->scenePos()); break; case TriangleType: tempFigure = new Triangle(event->scenePos()); break; case EllipseType: tempFigure = new Ellipse(event->scenePos()); break; } this->addItem(tempFigure); clearSelection(); tempFigure->setSelected(true); qDebug() << "New figure created and selected!"; updateSelectedFigureInfo(); } else if (currentMode == MovingMode) { selectedItem = itemAt(event->scenePos(), QTransform()); if (selectedItem) { clearSelection(); selectedItem->setSelected(true); qDebug() << "Existing figure selected!"; updateSelectedFigureInfo(); } } QGraphicsScene::mousePressEvent(event); }
Запусти программу и посмотри в qDebug()
, появляется ли New figure created and selected!
или Existing figure selected!
.
Если нет – проверь, нет ли кода, который снимает выделение (setSelected(false)
)!
MainWindow
Добавь qDebug()
в слот onFigureInfoChanged()
в MainWindow
:
cppvoid MainWindow::onFigureInfoChanged(const QString &info) { qDebug() << "onFigureInfoChanged received info: " << info; ui->infoLabel->setText(info); }
Если qDebug()
не показывает, что onFigureInfoChanged()
вызывается, значит:
connect()
в MainWindow
сломан:
cppconnect(scene, &PaintScene::figureInfoChanged, this, &MainWindow::onFigureInfoChanged);
Убедись, что connect()
вызывается после scene = new PaintScene(this);
!
emit figureInfoChanged(info);
не вызывается → Проверь, вызывается ли updateSelectedFigureInfo()
.
calculatePerimeter()
, calculateArea()
, calculateCenter()
Добавь qDebug()
в rect.cpp
:
cppdouble Rect::calculatePerimeter() const { double w = qAbs(endPoint().x() - startPoint().x()); double h = qAbs(endPoint().y() - startPoint().y()); qDebug() << "calculatePerimeter() called: w =" << w << ", h =" << h; return 2 * (w + h); }
cppdouble Rect::calculateArea() const { double w = qAbs(endPoint().x() - startPoint().x()); double h = qAbs(endPoint().y() - startPoint().y()); qDebug() << "calculateArea() called: w =" << w << ", h =" << h; return w * h; }
cppQPointF Rect::calculateCenter() const { double cx = (startPoint().x() + endPoint().x()) / 2.0; double cy = (startPoint().y() + endPoint().y()) / 2.0; qDebug() << "calculateCenter() called: center =" << cx << "," << cy; return QPointF(cx, cy); }
Если w = 0
и h = 0
, значит, startPoint()
и endPoint()
не обновляются!
Проверь, вызывается ли setEndPoint(event->scenePos());
в mouseMoveEvent()
.
Добавь qDebug()
в calculateFigureInfo()
.
Если selectedItems().isEmpty()
, то выделение не работает → Проверь setSelected(true)
.
Добавь qDebug()
в mousePressEvent()
.
Если "New figure created and selected!"
не появляется, значит, setSelected(true)
не сработал.
Добавь qDebug()
в onFigureInfoChanged()
.
Если слот не вызывается, проблема в connect()
.
Если вызывается, но infoLabel
не меняется – проблема в UI.
Добавь qDebug()
в calculatePerimeter()
и calculateArea()
.
Если они всегда 0
, то setEndPoint()
не работает → Проверь mouseMoveEvent()
.
После этого посмотри, где ломается логика и исправь соответствующий шаг.