C++ Qt 学习(八):Qt 绘图技术与图形视图

1. 常见 18 种 Qt 绘图技术

在这里插入图片描述

1.1 widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <memory>
#include <QTreeView>
#include "CPaintWidget.h"

using namespace std;

class Widget : public QWidget {
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    void treeView();

private slots:
    void treeViewExpand(const QModelIndex &index);

private:
    QTreeView* m_pLeftTree = nullptr;
    CPaintWidget* m_pPaintWidget = nullptr;
};

#endif // WIDGET_H

1.2 widget.cpp

#pragma execution_character_set("utf-8")

#include "widget.h"
#include <QStandardItemModel>
#include <QHBoxLayout>
#include "drawtype.h"

Widget::Widget(QWidget* parent) : QWidget(parent) {
    setWindowTitle(u8"draw all");

    QHBoxLayout* pHLay = new QHBoxLayout(this);
    // 创建左边树形视图
    m_pLeftTree = new QTreeView(this);
    m_pLeftTree->setEditTriggers(QAbstractItemView::NoEditTriggers);  //设置不可编辑
    m_pLeftTree->setFixedWidth(300);
    // 创建右边绘图窗口
    m_pPaintWidget = new CPaintWidget(this);
    // 添加进水平布局
    pHLay->addWidget(m_pLeftTree);
    pHLay->addWidget(m_pPaintWidget);

    treeView();
}

Widget::~Widget() {}

void Widget::treeView() {
    m_pLeftTree->setFrameShape(QFrame::NoFrame);

    QStandardItemModel* model = new QStandardItemModel(m_pLeftTree);
    model->setHorizontalHeaderLabels(QStringList() << "draw all");

    QStandardItem* pParentItem = NULL;
    QStandardItem* pChildItem = NULL;

    // 点
    pParentItem = new QStandardItem(QIcon(":/resources/point.png"), "draw ponit");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/point.png"), "point");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/multipoints.png"), "multipoints");
    pParentItem->appendRow(pChildItem);

    // 线
    pParentItem = new QStandardItem(QIcon(":/resources/line.png"), "draw line");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/line.png"), "line");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/arc.png"), "arc");
    pParentItem->appendRow(pChildItem);

    // 封闭的图形
    pParentItem = new QStandardItem(QIcon(":/resources/rect.png"), "draw rect");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/rect.png"), "rect");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/roundrect.png"), "roundrect");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/chord.png"), "chord");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/ellipse.png"), "ellipse");
    pParentItem->appendRow(pChildItem);

    // 任意路径绘制
    pParentItem = new QStandardItem(QIcon(":/resources/polygon.png"), "draw polygon");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/polygon.png"), "polygon");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/polyline.png"), "polyline");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/ConvexPloygon.png"), "ConvexPloygon");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/lines.png"), "lines");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/path.png"), "path");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/pie.png"), "pie");
    pParentItem->appendRow(pChildItem);

    // 图片绘制
    pParentItem = new QStandardItem(QIcon(":/resources/image.png"), "draw image");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/image.png"), "image");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/pixmap.png"), "pixmap");
    pParentItem->appendRow(pChildItem);

    // 文本绘制
    pParentItem = new QStandardItem(QIcon(":/resources/text.png"), "draw text");
    model->appendRow(pParentItem);

    // 擦除
    pParentItem = new QStandardItem(QIcon(":/resources/erase.png"), "draw erase");
    model->appendRow(pParentItem);

    // 路径填充
    pParentItem = new QStandardItem(QIcon(":/resources/fillpath.png"), "draw fillpath");
    model->appendRow(pParentItem);

    // 矩形填充
    pParentItem = new QStandardItem(QIcon(":/resources/fillrect.png"), "draw fillrect");
    model->appendRow(pParentItem);

    m_pLeftTree->setModel(model);  // 模型视图

    connect(m_pLeftTree, &QAbstractItemView::clicked, this, &Widget::treeViewExpand);
}

void Widget::treeViewExpand(const QModelIndex& index) {
    QString text = index.data().toString();

    if (text.compare("point") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::point);
        // 不更新就不会立即显示
        m_pPaintWidget->update();  
    } else if (text.compare("multipoints") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::multipoints);
        m_pPaintWidget->update();  
    } else if (text.compare("line") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::line);
        m_pPaintWidget->update();
    } else if (text.compare("arc") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::arc);
        m_pPaintWidget->update();
    } else if (text.compare("rect") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::rect);
        m_pPaintWidget->update();
    } else if (text.compare("roundrect") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::roundrect);
        m_pPaintWidget->update();
    } else if (text.compare("chord") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::chord);
        m_pPaintWidget->update();
    } else if (text.compare("ellipse") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::ellipse);
        m_pPaintWidget->update();
    } else if (text.compare("polygon") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::polygon);
        m_pPaintWidget->update();
    } else if (text.compare("polyline") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::polyline);
        m_pPaintWidget->update();
    } else if (text.compare("ConvexPloygon") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::ConvexPloygon);
        m_pPaintWidget->update();
    } else if (text.compare("lines") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::lines);
        m_pPaintWidget->update();  
    } else if (text.compare("path") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::path);
        m_pPaintWidget->update();
    } else if (text.compare("pie") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::pie);
        m_pPaintWidget->update();
    } else if (text.compare("image") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::image);
        m_pPaintWidget->update();
    } else if (text.compare("pixmap") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::pixmap);
        m_pPaintWidget->update();
    } else if (text.compare("draw text") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::draw_text);
        m_pPaintWidget->update();
    } else if (text.compare("draw erase") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::draw_erase);
        m_pPaintWidget->update();
    } else if (text.compare("draw fillpath") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::draw_fillpath);
        m_pPaintWidget->update();
    } else if (text.compare("draw fillrect") == 0) {
        m_pPaintWidget->setDrawType(DRAW_TYPE::draw_fillrect);
        m_pPaintWidget->update();
    }
}

1.3 CPaintWidget.h

#pragma once
#include <QWidget>
#include "drawtype.h"

class CPaintWidget : public QWidget {
	Q_OBJECT

public:
	CPaintWidget(QWidget* p = nullptr);
	~CPaintWidget() {}

	void setDrawType(DRAW_TYPE type);

private:
	void paintEvent(QPaintEvent* event) override;

private:
    void draw_point();
    void draw_multipoints();
    void draw_line();
    void draw_arc();
    void draw_rect();
    void draw_roundrect();
    void draw_chord();
    void draw_ellipse();
    void draw_polygon();
    void draw_polyline();
    void draw_ConvexPloygon();
    void draw_lines();
    void draw_path();
    void draw_pie();
    void draw_image();
    void draw_pixmap();
    void draw_text();
    void draw_erase();
    void draw_fillpath();
    void draw_fillrect();
    
private:
	DRAW_TYPE m_drawType;

	int W = 0;
	int H = 0;
};

1.4 CPaintWidget.cpp

#include "CPaintWidget.h"
#include <QPainter>
#include <QPainterPath>

CPaintWidget::CPaintWidget(QWidget* p) :QWidget(p) {
	this->setMinimumSize(800, 600);

	m_drawType = DRAW_TYPE::polygon;
}

void CPaintWidget::paintEvent(QPaintEvent* event) {
	W = this->width();
	H = this->height();

	switch (m_drawType) {
	case DRAW_TYPE::point:
		draw_point();
		break;

	case DRAW_TYPE::multipoints:
		draw_multipoints();
		break;

	case DRAW_TYPE::line:
		draw_line();
		break;

	case DRAW_TYPE::arc:
		draw_arc();
		break;

	case DRAW_TYPE::rect:
		draw_rect();
		break;

	case DRAW_TYPE::roundrect:
		draw_roundrect();
		break;

	case DRAW_TYPE::chord:
		draw_chord();
		break;

	case DRAW_TYPE::ellipse:
		draw_ellipse();
		break;

	case DRAW_TYPE::polygon:
		draw_polygon();
		break;

	case DRAW_TYPE::polyline:
		draw_polyline();
		break;

	case DRAW_TYPE::ConvexPloygon:
		draw_ConvexPloygon();
		break;

	case DRAW_TYPE::lines:
		draw_lines();
		break;

	case DRAW_TYPE::path:
		draw_path();
		break;

	case DRAW_TYPE::pie:
		draw_pie();
		break;

	case DRAW_TYPE::image:
		draw_image();
		break;

	case DRAW_TYPE::pixmap:
		draw_pixmap();
		break;

	case DRAW_TYPE::draw_text:
		draw_text();
		break;

	case DRAW_TYPE::draw_erase:
		draw_erase();
		break;

	case DRAW_TYPE::draw_fillpath:
		draw_fillpath();
		break;

	case DRAW_TYPE::draw_fillrect:
		draw_fillrect();
		break;

	default:
		break;
	}
}

void CPaintWidget::draw_point() {
	QPainter painter(this);
	QPen pen;
	pen.setWidth(10);
	pen.setColor(Qt::red);
	pen.setStyle(Qt::SolidLine);
	painter.setPen(pen);
	painter.drawPoint(QPoint(W / 2, H / 2));
}

void CPaintWidget::draw_multipoints() {
	QPainter painter(this);
	QPen pen;
	pen.setWidth(10);
	pen.setColor(Qt::blue);
	pen.setStyle(Qt::SolidLine);
	painter.setPen(pen);

	// 画很多点
	QPoint points[] = {
		QPoint(5 * W / 12,H / 4),
		QPoint(3 * W / 4, 5 * H / 12),
		QPoint(2 * W / 4, 5 * H / 12)};
	painter.drawPoints(points, 3);
}

void CPaintWidget::draw_line() {
	QPainter painter(this);

	//QLine Line(W / 4, H / 4, W / 2, H / 2);
	QLine Line(W / 4, H / 4, W / 2, H / 4);
	painter.drawLine(Line);
}

void CPaintWidget::draw_arc() {
	QPainter painter(this);
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	int startAngle = 90 * 16;
	int spanAngle = 90 * 16;       //旋转 90°
	painter.drawArc(rect, startAngle, spanAngle);
}

void CPaintWidget::draw_rect() {
	QPainter painter(this);

	// 画矩形
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.drawRect(rect);
}

void CPaintWidget::draw_roundrect() {
	QPainter painter(this);

	// 画圆角矩形
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.drawRoundedRect(rect, 20, 20);
}

void CPaintWidget::draw_chord() {
	QPainter painter(this);
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	int startAngle = 90 * 16;
	int spanAngle = 90 * 16;
	painter.drawChord(rect, startAngle, spanAngle);
}

void CPaintWidget::draw_ellipse() {
	QPainter painter(this);

	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.drawEllipse(rect);
}

void CPaintWidget::draw_polygon() {
	QPainter painter(this);
	painter.setRenderHint(QPainter::Antialiasing);

	QPen pen;  // 画笔描边
	pen.setWidth(10);
	pen.setColor(Qt::red);
	pen.setStyle(Qt::SolidLine);
	pen.setCapStyle(Qt::SquareCap);
	pen.setJoinStyle(Qt::MiterJoin);  // 画笔断点的样式
	painter.setPen(pen);

	QBrush brush;  // 画刷填充
	brush.setColor(Qt::yellow);
	brush.setStyle(Qt::SolidPattern);
	painter.setBrush(brush);

	// 画多边形,最后一个点会和第一个点闭合
	QPoint points[] = {
		QPoint(5 * W / 12,H / 4),
		QPoint(3 * W / 4,5 * H / 12),
		QPoint(5 * W / 12,3 * H / 4),
		QPoint(2 * W / 4,5 * H / 12)};
	painter.drawPolygon(points, 4);
}

void CPaintWidget::draw_polyline() {
	QPainter painter(this);

	// 画多点连接的线,最后一个点不会和第一个点连接
	QPoint points[] = {
		QPoint(5 * W / 12, H / 4),
		QPoint(3 * W / 4, 5 * H / 12),
		QPoint(5 * W / 12, 3 * H / 4),
		QPoint(2 * W / 4, 5 * H / 12)};
	painter.drawPolyline(points, 4);
}

void CPaintWidget::draw_ConvexPloygon() {
	QPainter painter(this);

	QPoint points[4] = {
		QPoint(5 * W / 12, H / 4),
		QPoint(3 * W / 4, 5 * H / 12),
		QPoint(5 * W / 12, 3 * H / 4),
		QPoint(W / 4, 5 * H / 12)};
	painter.drawConvexPolygon(points, 4);
}

void CPaintWidget::draw_lines() {
	QPainter painter(this);

	// 画一批直线
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QVector<QLine> Lines;
	Lines.append(QLine(rect.topLeft(), rect.bottomRight()));
	Lines.append(QLine(rect.topRight(), rect.bottomLeft()));
	Lines.append(QLine(rect.topLeft(), rect.bottomLeft()));
	Lines.append(QLine(rect.topRight(), rect.bottomRight()));
	painter.drawLines(Lines);
}

void CPaintWidget::draw_path() {
	QPainter painter(this);
	// 绘制由 QPainterPath 对象定义的路线
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QPainterPath path;
	path.addEllipse(rect);
	path.addRect(rect);
	painter.drawPath(path);
}

void CPaintWidget::draw_pie() {
	QPainter painter(this);
	// 绘制扇形
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	int startAngle = 40 * 16;  // 起始 40
	int spanAngle = 120 * 16;  // 旋转 120
	painter.drawPie(rect, startAngle, spanAngle);
}

void CPaintWidget::draw_image() {
	QPainter painter(this);

	// 在指定的矩形区域内绘制图片
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QImage image(":/resources/tupian.png");
	painter.drawImage(rect, image);
}

void CPaintWidget::draw_pixmap() {
	QPainter painter(this);
	// 绘制 Pixmap 图片
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QPixmap pixmap(":/resources/tupix.png");
	painter.drawPixmap(rect, pixmap);
}

void CPaintWidget::draw_text() {
	QPainter painter(this);

	// 绘制文本,只能绘制单行文字,字体的大小等属性由 QPainter::font() 决定
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QFont font;
	font.setPointSize(30);
	font.setBold(true);
	painter.setFont(font);
	painter.drawText(rect, "Hello,Qt");
}

void CPaintWidget::draw_erase() {
	QPainter painter(this);

	// 擦除某个矩形区域,等效于用背景色填充该区域
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.eraseRect(rect);
}

void CPaintWidget::draw_fillpath() {
	QPainter painter(this);

	// 填充某个 QPainterPath 定义的绘图路径,但是轮廓线不显示
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QPainterPath path;
	path.addEllipse(rect);
	path.addRect(rect);
	painter.fillPath(path, Qt::red);
}

void CPaintWidget::draw_fillrect() {
	QPainter painter(this);
	// 填充一个矩形,无边框线
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.fillRect(rect, Qt::green);
}

void CPaintWidget::setDrawType(DRAW_TYPE type) {
	m_drawType = type;
}

1.5 drawtype.h

#pragma once

enum class DRAW_TYPE {
    point,
    multipoints,
    line,
    arc,
    rect,
    roundrect,
    chord,
    ellipse,
    polygon,
    polyline,
    ConvexPloygon,
    lines,
    path,
    pie,
    image,
    pixmap,
    draw_text,
    draw_erase,
    draw_fillpath,
    draw_fillrect
};

2. Qt 鼠标手动绘图技术

2.1 mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QMenu>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    // 右键菜单需要重写的类
    void contextMenuEvent(QContextMenuEvent* event) override;

private:
    Ui::MainWindow *ui;

    QMenu* m_pMenu;
};
#endif // MAINWINDOW_H

2.2 mainwindow.cpp

/*
使用方式:左键点击,移动鼠标开始绘制,双击左键结束绘制,或者右键点击结束绘制
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMenu>
#include <QAction>

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);

	m_pMenu = new QMenu(this);

	QAction* pAc1 = new QAction(QString::fromLocal8Bit("结束绘制"), this);
	pAc1->setShortcut(QKeySequence("Ctrl+E"));
	QAction* pAc2 = new QAction(QString::fromLocal8Bit("清除"), this);
	pAc2->setShortcut(QKeySequence("Ctrl+D"));

	// 这是个假动作,为了让菜单消失,且不影响绘制路径
	QAction* pAc3 = new QAction(QString::fromLocal8Bit("退出菜单"), this);

	m_pMenu->addAction(pAc1);
	m_pMenu->addAction(pAc2);
	m_pMenu->addAction(pAc3);

	m_pMenu->setStyleSheet("QMenu{font:18px;}");

	connect(pAc1, &QAction::triggered, [=] {
		ui->graphicsPainter->endDraw();
	});

	connect(pAc2, &QAction::triggered, [=] {
		ui->graphicsPainter->clearPath();
	});
}

MainWindow::~MainWindow() {
    delete ui;
}

// 右键菜单
void MainWindow::contextMenuEvent(QContextMenuEvent* event) {
	m_pMenu->move(cursor().pos());  // 移动右键菜单到鼠标当前位置
	m_pMenu->show();
}

2.3 MyPainterWidget.h

#ifndef GRAPHICSPAINTER_H
#define GRAPHICSPAINTER_H

#include <QWidget>

class MyPainterWidget : public QWidget {
    Q_OBJECT

public:
    explicit MyPainterWidget(QWidget *parent = nullptr);

    void endDraw();
    void clearPath();

protected:
    void paintEvent(QPaintEvent *) override;
    void mousePressEvent(QMouseEvent *e) override;            // 按下
    void mouseMoveEvent(QMouseEvent *e) override;             // 移动
    void mouseReleaseEvent(QMouseEvent *e) override;          // 松开
    void mouseDoubleClickEvent(QMouseEvent *event) override;  // 双击

    bool m_bStartDraw = false;  // 是否已经开始左键点击,同时标识是否开始进行绘制
    bool bMove = false;         // 是否处于绘制时的鼠标移动状态
    
    QVector<QPointF> pointList;
    QPointF movePoint;
};

#endif // GRAPHICSPAINTER_H

2.4 MyPainterWidget.cpp

#include "MyPainterWidget.h"
#include <QPainter>
#include <QMouseEvent>

MyPainterWidget::MyPainterWidget(QWidget *parent) : QWidget(parent) {
    setMouseTracking(true);

    pointList.clear();
}

void MyPainterWidget::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setPen(QColor(255,0,0));

    QVector<QLineF> lines;

    for(int i = 0; i < pointList.size()-1; i++) {
        QLineF line(QPointF(pointList[i].x(), pointList[i].y()), 
            QPointF(pointList[i+1].x(), pointList[i+1].y()));

        lines.push_back(line);
    }

    if (m_bStartDraw) {
        int size = pointList.size();

        if (bMove && size > 0) {
            QLineF line(QPointF(pointList[pointList.size() - 1].x(), pointList[pointList.size() - 1].y()),
                movePoint);

            lines.push_back(line);
        }
    }

    painter.drawLines(lines);
}

// 按下
void MyPainterWidget::mousePressEvent(QMouseEvent *e) {
    if (e->button() == Qt::LeftButton) {
        if(!m_bStartDraw) {
            pointList.clear();
            m_bStartDraw = true;
        }
    }   
}

// 移动
void MyPainterWidget::mouseMoveEvent(QMouseEvent *e) {
    if(m_bStartDraw) {
        movePoint = e->pos();
        
        this->update();

        // 先刷新再设为true, 防止第一点和(0,0)连在一块
        bMove = true;
    }
}

// 松开
void MyPainterWidget::mouseReleaseEvent(QMouseEvent *e) {
    if (e->button() == Qt::LeftButton) {
        if (m_bStartDraw) {
            // 鼠标松开后的点需要添加到路径中
            pointList.push_back(QPointF(e->x(), e->y()));
            bMove = false;
            this->update();
        }
    }
}

// 双击结束绘制
void MyPainterWidget::mouseDoubleClickEvent(QMouseEvent *event) {
    endDraw();
}

void MyPainterWidget::endDraw() {
    m_bStartDraw = false;

    // 需要把第一个点连起来
    pointList.push_back(pointList[0]);
    this->update();
}

void MyPainterWidget::clearPath() {
    pointList.clear();
    this->update();
}

3. Qt 绘制带三角形箭头的窗口

在这里插入图片描述

3.1 widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

public slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

3.2 widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "triangledialog.h"
#include <QLabel>
#include "userinfowidget.h"
#include <QPropertyAnimation>

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
    ui->setupUi(this);
}

Widget::~Widget() {
    delete ui;
}

void Widget::on_pushButton_clicked() {
    int triangle_start_x = 60;

    TriangleDialog *pDialog = new TriangleDialog(343, 320, this);
    pDialog->setTriangleInfo(triangle_start_x, 20, 12);

    // 设置三角窗口的弹出位置, 有Qt::Popup属性
    QPoint p1 = ui->pushButton->mapToGlobal(QPoint(0, 0));  //按钮左上角相对于桌面的绝对位置
    QRect rect1 = ui->pushButton->rect();

    int x = p1.x() + rect1.width() / 2 - triangle_start_x - 20 / 2;
    int y = p1.y() + rect1.height() + 1 - 15;
    pDialog->move(x, y);

    pDialog->show();
}

3.3 userinfowidget.h

#ifndef USERINFOWIDGET_H
#define USERINFOWIDGET_H

#include <QWidget>

namespace Ui {
    class UserInfoWidget;
}

class UserInfoWidget : public QWidget {
    Q_OBJECT

public:
    explicit UserInfoWidget(QWidget *parent = nullptr);
    ~UserInfoWidget();

private:
    Ui::UserInfoWidget *ui;
};

#endif // USERINFOWIDGET_H

3.4 userinfowidget.cpp

#include "userinfowidget.h"
#include "ui_userinfowidget.h"

UserInfoWidget::UserInfoWidget(QWidget *parent) : QWidget(parent), ui(new Ui::UserInfoWidget) {
    ui->setupUi(this);

    setFixedSize(this->width(), this->height());

    setAttribute(Qt::WA_StyledBackground);
    QString qss = "QLabel{font-family:Microsoft YaHei; font-size:18px; color:#ffffff; background-color:#363636;}";

    ui->label_UserImage->setText("");
    QPixmap pmp1(":/resources/user_image.png");
    pmp1.scaled(ui->label_UserImage->size(), Qt::KeepAspectRatio);
    ui->label_UserImage->setScaledContents(true);
    ui->label_UserImage->setPixmap(pmp1);

    ui->label_UserName->setText(u8"没有做完的梦最痛");
    ui->label_UserName->setStyleSheet(qss);

    ui->label_VipInfo->setText("");
    QPixmap pmp2(":/resources/vipinfo.png");
    pmp2.scaled(ui->label_VipInfo->size(), Qt::KeepAspectRatio);
    ui->label_VipInfo->setScaledContents(true);
    ui->label_VipInfo->setPixmap(pmp2);
}

UserInfoWidget::~UserInfoWidget() {
    delete ui;
}

3.5 triangledialog.h

/*
    带三角形箭头的对话框
*/
#ifndef TRIANGLEDIALOG_H
#define TRIANGLEDIALOG_H

#include <QDialog>

class TriangleDialog : public QDialog {
    Q_OBJECT
public:
    explicit TriangleDialog(int fixedW, int fixedH, QWidget *parent = nullptr);

    // 设置小三角行的起始位置、宽、高
    void setTriangleInfo(int startX, int width, int height);

protected:
    void paintEvent(QPaintEvent *e);

private:
    // 小三角形的起始位置
    int m_startX;

    // 小三角形的宽度
    int m_triangleWidth;

    // 小三角形的高度
    int m_triangleHeight;
};

#endif // TRIANGLEDIALOG_H

3.6 triangledialog.cpp

#include "triangledialog.h"
#include <QVBoxLayout>
#include <QPainter>
#include <QGraphicsDropShadowEffect>
#include <QPainterPath>
#include "userinfowidget.h"

#define SHADOW_WIDTH    15                 // 窗口阴影宽度
#define TRIANGLE_WIDTH  15                 // 小三角行的宽度
#define TRIANGLE_HEIGHT 10                 // 小三角行的高度
#define BORDER_RADIUS   5                  // 窗口圆角

TriangleDialog::TriangleDialog(int fixedW, int fixedH, QWidget *parent) : QDialog(parent)
    ,m_startX(50)
    ,m_triangleWidth(TRIANGLE_WIDTH)
    ,m_triangleHeight(TRIANGLE_HEIGHT) {
    setWindowFlags(Qt::FramelessWindowHint | Qt::Popup | Qt::NoDropShadowWindowHint);
    setAttribute(Qt::WA_TranslucentBackground);  // 设置透明窗口, 为窗口阴影和圆角做准备

    UserInfoWidget* pUserinfoWidget = new UserInfoWidget(this);

    QVBoxLayout* pVlay = new QVBoxLayout(this);
    pVlay->addWidget(pUserinfoWidget);

    // 设置布局的四周边距
    pVlay->setContentsMargins(SHADOW_WIDTH, SHADOW_WIDTH + TRIANGLE_HEIGHT, SHADOW_WIDTH, SHADOW_WIDTH);

    // 设置阴影边框
    auto shadowEffect = new QGraphicsDropShadowEffect(this);
    shadowEffect->setOffset(0, 0);
    shadowEffect->setColor(Qt::red);
    shadowEffect->setBlurRadius(SHADOW_WIDTH);
    this->setGraphicsEffect(shadowEffect);

    // 设置窗口固定大小
    setFixedSize(fixedW, fixedH);
}

void TriangleDialog::setTriangleInfo(int startX, int width, int height) {
    m_startX = startX;
    m_triangleWidth = width;
    m_triangleHeight = height;
}

void TriangleDialog::paintEvent(QPaintEvent *e) {
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(Qt::NoPen);
    painter.setBrush(QColor(55, 55, 55));

    // 小三角区域,这里是等腰三角形
    QPolygon trianglePolygon;
    trianglePolygon << QPoint(m_startX, m_triangleHeight + SHADOW_WIDTH);
    trianglePolygon << QPoint(m_startX + m_triangleWidth / 2, SHADOW_WIDTH);
    trianglePolygon << QPoint(m_startX + m_triangleWidth, m_triangleHeight + SHADOW_WIDTH);

    QPainterPath drawPath;
    drawPath.addRoundedRect(QRect(SHADOW_WIDTH, SHADOW_WIDTH + m_triangleHeight,
                            this->width() - SHADOW_WIDTH * 2, this->height() - SHADOW_WIDTH * 2 - m_triangleHeight),
                            BORDER_RADIUS, BORDER_RADIUS);
    // 矩形路径加上三角形
    drawPath.addPolygon(trianglePolygon);

    // 绘制路径
    painter.drawPath(drawPath);
}

4. Qt 场景视图技术

4.1 简介

  • 使用 QPainter 可以绘制一些常用的图形,如果需要对绘制的图形进行编辑,例如拉伸,拖拽,组合等,会比较复杂,用代码是可以实现的,但是会比较复杂,例如处理鼠标的形状,判定拉伸拖拽的点,然后重绘等等,如果拉伸的时候和其它图形有冲突,还需要做判定处理
    • 使用 QPainter 的绘制技术不太适合对图形进行选择、编辑、拖放、修改等功能
  • Qt 提供了 Graphics View 图形架构,是一种基于图形项的模型/视图模式,使用 Graphics View 架构可以绘制复杂的有几万个基本图形元件的图形,并且每个图形元件是可选择、可拖放和修改的,类似于矢量绘图软件的绘图功能

4.2 Graphics View 基本组成

  • Graphics View 架构主要由 3 部分组成,即视图、场景和图形项,其构成的绘图系统结构如下图所示
    • 视图 QGraphicsScene:提供绘图的视图组件,用于显示场景中的内容
      • 视图比场景大时,显示场景的全部内容(默认在中间部分显示)
      • 视图比场景小时,只能显示场景的部分内容,但是会自动提供卷滚条在整个场景内移动
    • 场景 QGraphics View:提供绘图场景
      • 场景是不可见的,是管理图形项的容器,可以向场景添加图形项,获取场景中的某个图形项等
    • 图形项 QGraphicsItem:一些基本的图形元件
      • 支持鼠标事件响应、键盘输入与按键事件
      • 支持拖放、组合操作

在这里插入图片描述

4.3 Graphics View 坐标系统

4.3.1 视图坐标
  • 视图坐标与设备坐标相同,是物理坐标,缺省以左上角为原点 (0, 0)
    • 所有的鼠标事件、拖放事件的坐标首先是由视图坐标定义的
    • 用户需要将视图坐标映射为场景坐标,以便和图形项交互
4.3.2 场景坐标
  • 场景的坐标一般以场景的中心为原点
  • 场景是所有图形项的基础坐标,场景坐标描述了每个顶层图形项的位置,创建场景时可定义场景坐标范围
    • scene 是左上角坐标为 (-400,-300),宽度为 800,高度为 600 的矩形区域,单位是像素
    scene = new QGraphicsScene(-400, -300, 800, 600);
    
4.3.3 图形项坐标
  • 图形项使用自己的局部坐标,通常以其中心为原点 (0, 0),也是各种坐标变换的中心

    • 图形项的鼠标事件的坐标是用局部坐标表示的,创建自定义图形项,绘制图形项时只需考虑其局部坐标
  • 一个图形项的位置是其中心点在父坐标系统中的坐标

    • 对于没有父图形项的图形项,其父对象就是场景,图形项的位置就是在场景中的坐标
  • 如果一个图形项还是其他图形项的父项,父项进行坐标变换时,子项也做同样的坐标变换

4.3.4 坐标映射
  • 在场景中操作图形项时,进行场景到图形项、图形项到图形项,或视图到场景之间的坐标变换是比较有用的
    • 例如在 QGraphicsView 的视口上单击鼠标时,通过函数 QGraphicsView::mapToScene() 可以将视图坐标映射为场景坐标,然后用 QGraphicsScene::itemAt() 函数可以获取场景中鼠标光标处的图形项

4.4 基本图元绘制

在这里插入图片描述

4.4.1 主界面
  • widget.h
#pragma once

#include <QWidget>
#include "ui_widget.h"
#include <QGraphicsScene>

class Widget : public QWidget {
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    void initGraphicsView();

private slots:
    void on_mouseMovePoint(QPoint point);
    void on_mouseClicked(QPoint point);

private:
    Ui::Widget *ui;

    QGraphicsScene *scene;
};
  • widget.cpp
#include "widget.h"
#include <QGraphicsRectItem>

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
    ui->setupUi(this);

    connect(ui->graphicView, &MyGraphicsView::mouseMovePoint, this, &Widget::on_mouseMovePoint);
    connect(ui->graphicView, &MyGraphicsView::mouseClicked, this, &Widget::on_mouseClicked);

    initGraphicsView();
}

Widget::~Widget() {
    delete ui;
}

// 视图初始化
void Widget::initGraphicsView() {
    QRectF rect(-200, -100, 400, 200);
    scene = new QGraphicsScene(rect); 

    // 给视图设置场景
    ui->graphicView->setScene(scene);

    // 画一个矩形框,大小等于 scene
    QGraphicsRectItem *item = new QGraphicsRectItem(rect); 
    // 可选,可以有焦点,但是不能移动
    item->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);
    QPen pen;
    pen.setWidth(20);
    item->setPen(pen);
    scene->addItem(item);

    // 一个位于 scene 中心的椭圆
    QGraphicsEllipseItem *item2 = new QGraphicsEllipseItem(-100, -50, 200, 100);
    item2->setPos(-200, 0);
    item2->setBrush(QBrush(Qt::blue));
    item2->setFlags(QGraphicsItem::ItemIsMovable
                   | QGraphicsItem::ItemIsSelectable
                   | QGraphicsItem::ItemIsFocusable);
    scene->addItem(item2);

    // 一个圆,中心位于 scene 的边缘
    QGraphicsEllipseItem *item3 = new QGraphicsEllipseItem(-50, -50, 100, 100);
    item3->setPos(rect.right(), rect.bottom());
    item3->setBrush(QBrush(Qt::red));
    item3->setFlags(QGraphicsItem::ItemIsMovable
                   | QGraphicsItem::ItemIsSelectable
                   | QGraphicsItem::ItemIsFocusable);
    scene->addItem(item3);

    scene->clearSelection();
}

// 鼠标移动事件,point是 GraphicsView 的坐标,物理坐标
void Widget::on_mouseMovePoint(QPoint point) {
    ui->label_viewpos->setText(QString::asprintf("%d, %d", point.x(), point.y()));
    QPointF pointScene = ui->graphicView->mapToScene(point);        // 视图转换到场景坐标
    ui->label_scenepos->setText(QString::asprintf("%.0f,%.0f", pointScene.x(), pointScene.y()));
}

// 鼠标单击事件
void Widget::on_mouseClicked(QPoint point) {
    QPointF pointScene = ui->graphicView->mapToScene(point);        // 视图转换到场景坐标
    QGraphicsItem *item = NULL;
    item = scene->itemAt(pointScene,ui->graphicView->transform());  // 获取光标下的绘图项
    if (item != NULL) {  // 有图形项
        QPointF pointItem = item->mapFromScene(pointScene);         // 场景坐标转换为图形项的局部坐标
        ui->label_itempos->setText(QString::asprintf("%.0f, %.0f", pointItem.x(), pointItem.y()));
    }
}
4.4.2 场景视图界面
  • MyGraphicsView.h
#pragma once

#include <QObject>
#include <QGraphicsView>

class MyGraphicsView : public QGraphicsView {
    Q_OBJECT

protected:
    void mouseMoveEvent(QMouseEvent *event);
    void mousePressEvent(QMouseEvent *event);

public:
    MyGraphicsView(QWidget *parent = 0);

signals:
    void mouseMovePoint(QPoint point);
    void mouseClicked(QPoint point);
};
  • MyGraphicsView.cpp
#include "MyGraphicsView.h"
#include <QMouseEvent>
#include <QPoint>

MyGraphicsView::MyGraphicsView(QWidget* parent) :QGraphicsView(parent) {
    // 设置视图的鼠标指针形状为十字光标
    this->setCursor(Qt::CrossCursor);
    // 启用鼠标追踪功能,即使没有按下鼠标按钮,也可以接收到鼠标移动事件
    this->setMouseTracking(true);
    // 设置视图的拖动模式为拉框拖动模式
    // 通过点击并按住鼠标左键来创建一个矩形框选择多个图形项
    this->setDragMode(QGraphicsView::RubberBandDrag);
}

// 鼠标移动事件
void MyGraphicsView::mouseMoveEvent(QMouseEvent *event) {
    QPoint point = event->pos();   // QGraphicsView 视图坐标
    emit mouseMovePoint(point);    // 释放信号
    QGraphicsView::mouseMoveEvent(event);
}

// 鼠标左键按下事件
void MyGraphicsView::mousePressEvent(QMouseEvent *event) {
    if (event->button() == Qt::LeftButton) {
        QPoint point = event->pos();  // 返回当前鼠标光标的位置(视图坐标系下)
        emit mouseClicked(point);     // 发射鼠标点击信号
    }

    QGraphicsView::mousePressEvent(event);  // 响应自身的鼠标移动事件
}

4.5 自定义图元实现拖拽、拉伸、旋转

在这里插入图片描述

4.5.1 主界面
  • ch88_CustomRectItem.h
#pragma once

#include <QtWidgets/QWidget>
#include "ui_ch88_CustomRectItem.h"

class ch88_CustomRectItem : public QWidget {
    Q_OBJECT

public:
    ch88_CustomRectItem(QWidget *parent = nullptr);
    ~ch88_CustomRectItem();

private:
    Ui::ch88_CustomRectItemClass ui;
};
  • ch88_CustomRectItem.cpp
#include "ch88_CustomRectItem.h"
#include "MyRectItem.h"

ch88_CustomRectItem::ch88_CustomRectItem(QWidget *parent) : QWidget(parent) {
    ui.setupUi(this);

    QRect rect = ui.graphicsView->rect();

    // 创建场景
    QGraphicsScene* pScene = new QGraphicsScene(rect);

    // 视图里设置场景
    ui.graphicsView->setScene(pScene);

    // 创建自定义图元
    MyRectItem* pRectItem = new MyRectItem();

    // 场景添加图元
    pScene->addItem(pRectItem);
}

ch88_CustomRectItem::~ch88_CustomRectItem() {}
4.5.2 自定义矩形图元
  • MyRectItem.h
/*
    自定义矩形图元
*/

#pragma once
#include <QGraphicsItem>

enum STATE_FLAG {
    DEFAULT_FLAG = 0,
    MOV_LEFT_LINE,         // 矩形的左边界区域
    MOV_TOP_LINE,          // 矩形的上边界区域
    MOV_RIGHT_LINE,        // 矩形的右边界区域
    MOV_BOTTOM_LINE,       // 矩形的下边界区域
    MOV_RIGHTBOTTOM_RECT,  // 矩形的右下角
    MOV_RECT,              // 移动状态
    ROTATE                 // 旋转状态
};

class MyRectItem : public QObject, public QGraphicsItem {
    Q_OBJECT

public:
    MyRectItem(QGraphicsItem* parent = nullptr);
    ~MyRectItem();

    // boundingRect() 是纯虚函数,必须由派生类重新实现
    QRectF boundingRect() const override;
    
    void setRectSize(QRectF mrect, bool bResetRotateCenter = true);

    void paint(QPainter* painter,
        const QStyleOptionGraphicsItem* option,
        QWidget* widget) override;

    void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;

    void SetRotate(qreal RotateAngle, QPointF ptCenter = QPointF(-999, -999));
    // 获取旋转后的点
    QPointF getRotatePoint(QPointF ptCenter, QPointF ptIn, qreal angle);
    // 获取多个旋转后的点
    QList<QPointF> getRotatePoints(QPointF ptCenter, QList<QPointF> ptIns, qreal angle);
    // 将矩形旋转之后返回多边形
    QPolygonF getRotatePolygonFromRect(QPointF ptCenter, QRectF rectIn, qreal angle);
    QRectF getCrtPosRectToSceen();

    // 获取旋转时候矩形正上方的旋转标记矩形
    QPointF getSmallRotateRectCenter(QPointF ptA, QPointF ptB);
    QRectF  getSmallRotateRect(QPointF ptA, QPointF ptB);

private:
    QRectF  m_oldRect;
    QPolygonF m_oldRectPolygon;
    QRectF  m_RotateAreaRect;
    bool    m_bResize;
    QPolygonF m_insicedPolygon;
    QRectF  m_insicedRectf;
    QPolygonF m_leftPolygon;
    QRectF  m_leftRectf;
    QPolygonF m_topPolygon;
    QRectF  m_topRectf;
    QPolygonF m_rightPolygon;
    QRectF  m_rightRectf;
    QPolygonF m_bottomPolygon;
    QRectF  m_bottomRectf;
    QPointF m_startPos;
    STATE_FLAG m_StateFlag;
    QPointF* m_pPointFofSmallRotateRect;
    QRectF m_SmallRotateRect;        // 矩形顶部用来表示旋转的标记的矩形
    QPolygonF m_SmallRotatePolygon;  // 矩形顶部用来表示旋转的标记的矩形旋转后形成的多边形
    bool    m_bRotate;
    qreal   m_RotateAngle;
    QPointF m_RotateCenter;
};
  • MyRectItem.cpp
#include "MyRectItem.h"
#include <QtMath>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsScene>

MyRectItem::MyRectItem(QGraphicsItem* parent) :
    m_bResize(false),
    m_oldRect(0, 0, 200, 200),
    m_bRotate(false),
    m_RotateAngle(0),
    m_StateFlag(DEFAULT_FLAG) {

    setRectSize(m_oldRect);
    
    // 设置光标形状,手的形状
    setCursor(Qt::ArrowCursor);

    // 设置图元是可移动的
    setFlags(QGraphicsItem::ItemIsMovable
        | QGraphicsItem::ItemIsSelectable
        | QGraphicsItem::ItemIsFocusable);

    m_pPointFofSmallRotateRect = new QPointF[4];
    SetRotate(0);
}

MyRectItem::~MyRectItem() {
    delete[] m_pPointFofSmallRotateRect;
    m_pPointFofSmallRotateRect = nullptr;
}

QRectF MyRectItem::boundingRect() const {
    QRectF boundingRectF = m_oldRectPolygon.boundingRect();
    return QRectF(boundingRectF.x() - 40, boundingRectF.y() - 40, 
        boundingRectF.width() + 80, boundingRectF.height() + 80);
}

void MyRectItem::setRectSize(QRectF mrect, bool bResetRotateCenter) {
    m_oldRect = mrect;

    if (bResetRotateCenter) {
        m_RotateCenter.setX(m_oldRect.x() + m_oldRect.width() / 2);
        m_RotateCenter.setY(m_oldRect.y() + m_oldRect.height() / 2);
    }

    m_oldRectPolygon = getRotatePolygonFromRect(m_RotateCenter, m_oldRect, m_RotateAngle);

    m_insicedRectf = QRectF(m_oldRect.x() + 8, m_oldRect.y() + 8, m_oldRect.width() - 16, m_oldRect.height() - 16);
    m_insicedPolygon = getRotatePolygonFromRect(m_RotateCenter, m_insicedRectf, m_RotateAngle);

    m_leftRectf = QRectF(m_oldRect.x(), m_oldRect.y(), 8, m_oldRect.height() - 8);
    m_leftPolygon = getRotatePolygonFromRect(m_RotateCenter, m_leftRectf, m_RotateAngle);

    m_topRectf = QRectF(m_oldRect.x() + 8, m_oldRect.y(), m_oldRect.width() - 8, 8);
    m_topPolygon = getRotatePolygonFromRect(m_RotateCenter, m_topRectf, m_RotateAngle);

    m_rightRectf = QRectF(m_oldRect.right() - 8, m_oldRect.y() + 8, 8, m_oldRect.height() - 16);
    m_rightPolygon = getRotatePolygonFromRect(m_RotateCenter, m_rightRectf, m_RotateAngle);

    m_bottomRectf = QRectF(m_oldRect.x(), m_oldRect.bottom() - 8, m_oldRect.width() - 8, 8);
    m_bottomPolygon = getRotatePolygonFromRect(m_RotateCenter, m_bottomRectf, m_RotateAngle);

    m_SmallRotateRect = getSmallRotateRect(mrect.topLeft(), mrect.topRight());//矩形正上方的旋转标记矩形
    m_SmallRotatePolygon = getRotatePolygonFromRect(m_RotateCenter, m_SmallRotateRect, m_RotateAngle);
}

void MyRectItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
    // 反锯齿
    painter->setRenderHint(QPainter::Antialiasing, true);

    QPen mPen = QPen(Qt::black);
    mPen.setWidth(5);
    painter->setPen(mPen);

    // 绘制旋转后的矩形
    painter->drawPolygon(m_oldRectPolygon);

    // 绘制旋转圆形
    mPen.setWidth(2);
    mPen.setColor(Qt::green);
    painter->setPen(mPen);
    QPointF pf = getSmallRotateRectCenter(m_oldRectPolygon[0], m_oldRectPolygon[1]);
    QRectF rect = QRectF(pf.x() - 10, pf.y() - 10, 20, 20);

    // 绘制圆形
    painter->drawEllipse(rect);

    // 绘制点
    painter->drawPoint(pf);

    if (option->state & QStyle::State_Selected) {
        // 绘制选择后的虚线框
        const qreal penWidth = 1;

        // 边框区域颜色
        QColor color = QColor(Qt::white);

        painter->setPen(QPen(color, penWidth, Qt::DashLine));
        painter->setBrush(Qt::NoBrush);
        painter->drawPolygon(m_oldRectPolygon);
    }
}

void MyRectItem::mousePressEvent(QGraphicsSceneMouseEvent* event) {
    if (event->button() == Qt::LeftButton) {
        m_startPos = event->pos();
        if (m_SmallRotatePolygon.containsPoint(m_startPos, Qt::WindingFill)) {
            // 鼠标在旋转圆形内内
            setCursor(Qt::PointingHandCursor);
            m_StateFlag = ROTATE;
        } else if (m_insicedPolygon.containsPoint(m_startPos, Qt::WindingFill)) {
            // 在矩形内框区域时
            setCursor(Qt::ClosedHandCursor);  // 改变光标形状,手的形状
            m_StateFlag = MOV_RECT;
        } else if (m_leftPolygon.containsPoint(m_startPos, Qt::WindingFill)) {
            // 矩形的左边界区域
            setCursor(Qt::SizeHorCursor);
            m_StateFlag = MOV_LEFT_LINE;
        } else if (m_rightPolygon.containsPoint(m_startPos, Qt::WindingFill)) {
            // 矩形的右边界区域
            setCursor(Qt::SizeHorCursor);
            m_StateFlag = MOV_RIGHT_LINE;
        } else if (m_topPolygon.containsPoint(m_startPos, Qt::WindingFill)) {
            // 矩形的上边界区域
            setCursor(Qt::SizeVerCursor);
            m_StateFlag = MOV_TOP_LINE;
        } else if (m_bottomPolygon.containsPoint(m_startPos, Qt::WindingFill)) {
            // 矩形的下边界区域
            setCursor(Qt::SizeVerCursor);
            m_StateFlag = MOV_BOTTOM_LINE;
        } else {
            m_StateFlag = DEFAULT_FLAG;
        }
    } else {
        QGraphicsItem::mousePressEvent(event);
    }
}

void MyRectItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
    if (m_StateFlag == ROTATE) {
        // atan2 计算出来的是弧度值,需要转化为角度值
        int nRotateAngle = atan2((event->pos().x() - m_RotateCenter.x()), 
            (event->pos().y() - m_RotateCenter.y())) * 180 / M_PI;

        SetRotate(180 - nRotateAngle);
        setRectSize(m_oldRect);
    } else if (m_StateFlag == MOV_RECT) {
        QPointF point = (event->pos() - m_startPos);
        moveBy(point.x(), point.y());
        setRectSize(m_oldRect);
        scene()->update();
    } else if (m_StateFlag == MOV_LEFT_LINE) {
        QPointF pf = QPointF((m_oldRectPolygon.at(1).x() + m_oldRectPolygon.at(2).x()) / 2, ((m_oldRectPolygon.at(1).y() + m_oldRectPolygon.at(2).y()) / 2));

        // 计算到右侧边中点的距离
        qreal dis = sqrt((event->pos().x() - pf.x()) * (event->pos().x() - pf.x()) + (event->pos().y() - pf.y()) * (event->pos().y() - pf.y()));
        qreal dis2LT = sqrt((event->pos().x() - m_oldRectPolygon.at(0).x()) * (event->pos().x() - m_oldRectPolygon.at(0).x()) + (event->pos().y() - m_oldRectPolygon.at(0).y()) * (event->pos().y() - m_oldRectPolygon.at(0).y()));
        qreal dis2RT = sqrt((event->pos().x() - m_oldRectPolygon.at(1).x()) * (event->pos().x() - m_oldRectPolygon.at(1).x()) + (event->pos().y() - m_oldRectPolygon.at(1).y()) * (event->pos().y() - m_oldRectPolygon.at(1).y()));
        if (dis < 16 || dis2LT > dis2RT) {
            return;
        } else {
            QRectF newRect(m_oldRect);
            newRect.setLeft(m_oldRect.right() - dis);
            newRect.setRight(m_oldRect.right());
            setRectSize(newRect, false);
            m_RotateCenter = QPointF((m_oldRectPolygon.at(0).x() + m_oldRectPolygon.at(2).x()) / 2, (m_oldRectPolygon.at(0).y() + m_oldRectPolygon.at(2).y()) / 2);
            m_oldRect.moveCenter(m_RotateCenter);
            setRectSize(m_oldRect);

            // 必须要用scene()->update(),不能用update();否则会出现重影
            scene()->update();
        }
    } else if (m_StateFlag == MOV_TOP_LINE) {
        // 底边中点
        QPointF pf = QPointF((m_oldRectPolygon.at(2).x() + m_oldRectPolygon.at(3).x()) / 2, ((m_oldRectPolygon.at(2).y() + m_oldRectPolygon.at(3).y()) / 2));

        //计算到底边中点的距离
        qreal dis = sqrt((event->pos().x() - pf.x()) * (event->pos().x() - pf.x()) + (event->pos().y() - pf.y()) * (event->pos().y() - pf.y()));
        qreal dis2LT = sqrt((event->pos().x() - m_oldRectPolygon.at(0).x()) * (event->pos().x() - m_oldRectPolygon.at(0).x()) + (event->pos().y() - m_oldRectPolygon.at(0).y()) * (event->pos().y() - m_oldRectPolygon.at(0).y()));
        qreal dis2LB = sqrt((event->pos().x() - m_oldRectPolygon.at(3).x()) * (event->pos().x() - m_oldRectPolygon.at(3).x()) + (event->pos().y() - m_oldRectPolygon.at(3).y()) * (event->pos().y() - m_oldRectPolygon.at(3).y()));

        if (dis < 16 || dis2LT > dis2LB) {
            return;
        } else {
            QRectF newRect(m_oldRect);
            newRect.setTop(m_oldRect.bottom() - dis);
            newRect.setBottom(m_oldRect.bottom());
            setRectSize(newRect, false);
            m_RotateCenter = QPointF((m_oldRectPolygon.at(0).x() + m_oldRectPolygon.at(2).x()) / 2, (m_oldRectPolygon.at(0).y() + m_oldRectPolygon.at(2).y()) / 2);
            m_oldRect.moveCenter(m_RotateCenter);
            setRectSize(m_oldRect);
            scene()->update();  // 必须要用scene()->update(),不能用 update(),否则会出现重影
        }
    } else if (m_StateFlag == MOV_RIGHT_LINE) {
        QPointF pf = QPointF((m_oldRectPolygon.at(0).x() + m_oldRectPolygon.at(3).x()) / 2, ((m_oldRectPolygon.at(0).y() + m_oldRectPolygon.at(3).y()) / 2));

        // 计算到左侧边中点的距离
        qreal dis = sqrt((event->pos().x() - pf.x()) * (event->pos().x() - pf.x()) + (event->pos().y() - pf.y()) * (event->pos().y() - pf.y()));
        qreal dis2LT = sqrt((event->pos().x() - m_oldRectPolygon.at(0).x()) * (event->pos().x() - m_oldRectPolygon.at(0).x()) + (event->pos().y() - m_oldRectPolygon.at(0).y()) * (event->pos().y() - m_oldRectPolygon.at(0).y()));
        qreal dis2RT = sqrt((event->pos().x() - m_oldRectPolygon.at(1).x()) * (event->pos().x() - m_oldRectPolygon.at(1).x()) + (event->pos().y() - m_oldRectPolygon.at(1).y()) * (event->pos().y() - m_oldRectPolygon.at(1).y()));
        if (dis < 16 || dis2LT < dis2RT) {
            return;
        } else {
            QRectF newRect(m_oldRect);
            newRect.setLeft(m_oldRect.left());
            newRect.setRight(m_oldRect.left() + dis);
            setRectSize(newRect, false);
            m_RotateCenter = QPointF((m_oldRectPolygon.at(0).x() + m_oldRectPolygon.at(2).x()) / 2, (m_oldRectPolygon.at(0).y() + m_oldRectPolygon.at(2).y()) / 2);
            m_oldRect.moveCenter(m_RotateCenter);
            setRectSize(m_oldRect);

            // 必须要用 scene()->update(),不能用 update(),否则会出现重影
            scene()->update(); 
        }
    } else if (m_StateFlag == MOV_BOTTOM_LINE) {
        // 顶边中点
        QPointF pf = QPointF((m_oldRectPolygon.at(0).x() + m_oldRectPolygon.at(1).x()) / 2, ((m_oldRectPolygon.at(0).y() + m_oldRectPolygon.at(1).y()) / 2));

        // 计算到底边中点的距离
        qreal dis = sqrt((event->pos().x() - pf.x()) * (event->pos().x() - pf.x()) + (event->pos().y() - pf.y()) * (event->pos().y() - pf.y()));
        qreal dis2LT = sqrt((event->pos().x() - m_oldRectPolygon.at(0).x()) * (event->pos().x() - m_oldRectPolygon.at(0).x()) + (event->pos().y() - m_oldRectPolygon.at(0).y()) * (event->pos().y() - m_oldRectPolygon.at(0).y()));
        qreal dis2LB = sqrt((event->pos().x() - m_oldRectPolygon.at(3).x()) * (event->pos().x() - m_oldRectPolygon.at(3).x()) + (event->pos().y() - m_oldRectPolygon.at(3).y()) * (event->pos().y() - m_oldRectPolygon.at(3).y()));
        if (dis < 16 || dis2LT < dis2LB) {
            return;
        } else {
            QRectF newRect(m_oldRect);
            newRect.setTop(m_oldRect.top());
            newRect.setBottom(m_oldRect.top() + dis);
            setRectSize(newRect, false);
            m_RotateCenter = QPointF((m_oldRectPolygon.at(0).x() + m_oldRectPolygon.at(2).x()) / 2, (m_oldRectPolygon.at(0).y() + m_oldRectPolygon.at(2).y()) / 2);
            m_oldRect.moveCenter(m_RotateCenter);
            setRectSize(m_oldRect);

            // 必须要用 scene()->update(),不能用 update(),否则会出现重影
            scene()->update();
        }
    }
}

void MyRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
    setCursor(Qt::ArrowCursor);
    if (m_StateFlag == MOV_RECT) {
        m_StateFlag = DEFAULT_FLAG;
    } else {
        QGraphicsItem::mouseReleaseEvent(event);
    }
}

void MyRectItem::SetRotate(qreal RotateAngle, QPointF ptCenter) {
    m_bRotate = true;
    if (ptCenter.x() == -999 && ptCenter.y() == -999) {
        m_RotateCenter = QPointF(m_oldRect.x() + m_oldRect.width() / 2, m_oldRect.y() + m_oldRect.height() / 2);
    } else {
        m_RotateCenter = ptCenter;
    }

    m_RotateAngle = RotateAngle;
    this->update();
}

QPointF MyRectItem::getRotatePoint(QPointF ptCenter, QPointF ptIn, qreal angle) {
    double dx = ptCenter.x();
    double dy = ptCenter.y();
    double x = ptIn.x();
    double y = ptIn.y();
    double xx, yy;
    xx = (x - dx) * cos(angle * M_PI / 180) - (y - dy) * sin(angle * M_PI / 180) + dx;
    yy = (x - dx) * sin(angle * M_PI / 180) + (y - dy) * cos(angle * M_PI / 180) + dy;

    return QPointF(xx, yy);
}

QList<QPointF> MyRectItem::getRotatePoints(QPointF ptCenter, QList<QPointF> ptIns, qreal angle) {
    QList<QPointF> lstPt;
    for (int i = 0; i < ptIns.count(); i++) {
        lstPt.append(getRotatePoint(ptCenter, ptIns.at(i), angle));
    }
    return lstPt;
}

QPolygonF MyRectItem::getRotatePolygonFromRect(QPointF ptCenter, QRectF rectIn, qreal angle) {
    QVector<QPointF> vpt;
    QPointF pf = getRotatePoint(ptCenter, rectIn.topLeft(), angle);
    vpt.append(pf);
    pf = getRotatePoint(ptCenter, rectIn.topRight(), angle);
    vpt.append(pf);
    pf = getRotatePoint(ptCenter, rectIn.bottomRight(), angle);
    vpt.append(pf);
    pf = getRotatePoint(ptCenter, rectIn.bottomLeft(), angle);
    vpt.append(pf);
    pf = getRotatePoint(ptCenter, rectIn.topLeft(), angle);
    vpt.append(pf);
    return QPolygonF(vpt);
}

QRectF MyRectItem::getCrtPosRectToSceen() {
    QRectF retRect = QRectF(m_oldRect.x() + pos().x(), m_oldRect.y() + pos().y(), m_oldRect.width(), m_oldRect.height());
    return retRect;
}

QRectF MyRectItem::getSmallRotateRect(QPointF ptA, QPointF ptB) {
    QPointF pt = getSmallRotateRectCenter(ptA, ptB);
    return QRectF(pt.x() - 10, pt.y() - 10, 20, 20);
}

QPointF MyRectItem::getSmallRotateRectCenter(QPointF ptA, QPointF ptB) {
    // A,B 点的中点 C
    QPointF ptCenter = QPointF((ptA.x() + ptB.x()) / 2, (ptA.y() + ptB.y()) / 2);

    // 中垂线方程式为 y=x*k + b
    qreal x, y;//旋转图标矩形的中心
    if (abs(ptB.y() - ptA.y()) < 0.1) {
        if (ptA.x() < ptB.x()) {  // 矩形左上角在上方
        
            x = ptCenter.x();
            y = ptCenter.y() - 20;
        } else {
            // 矩形左上角在下方
            x = ptCenter.x();
            y = ptCenter.y() + 20;
        }
    } else if (ptB.y() > ptA.y()) {  // 顺时针旋转 0-180
        qreal k = (ptA.x() - ptB.x()) / (ptB.y() - ptA.y());  // 中垂线斜率
        qreal b = (ptA.y() + ptB.y()) / 2 - k * (ptA.x() + ptB.x()) / 2;
        // 求 AB 线中垂线上离 AB 中点 20 个像素的点 C 的坐标
        x = 20 * cos(atan(k)) + ptCenter.x();
        y = k * x + b;
    } else if (ptB.y() < ptA.y()) {  // 顺时针旋转 180-360
        qreal k = (ptA.x() - ptB.x()) / (ptB.y() - ptA.y());  // 中垂线斜率
        qreal b = (ptA.y() + ptB.y()) / 2 - k * (ptA.x() + ptB.x()) / 2;

        // 求 AB 线中垂线上离 AB 中点 20 个像素的点 C 的坐标
        x = -20 * cos(atan(k)) + ptCenter.x();
        y = k * x + b;
    }

    return QPointF(x, y);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/149146.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LOWORD, HIWORD, LOBYTE, HIBYTE的解释

文章目录 实验结论 实验 int 类型大小正常为4Byte 以小端序来看 0x12345678在内存中的存储为 0x78 0x56 0x34 0x120x78在低地址&#xff0c;0x12在高地址 程序输出 #include <stdio.h> #include <string.h> #include<windows.h>int main() {int a 0x12345…

d3dcompiler_43.dll丢失了怎么办,详细解答和d3dcompiler_43.dll修复方法

以下将为您提供几种处理d3dcompiler_43.dll文件丢失的解决措施&#xff0c;这些方法实用有效&#xff0c;可以帮助我们恢复计算机运行。 一.d3dcompiler_43.dll是什么 在我们开始探讨如何修复d3dcompiler_43.dll文件丢失的问题之前&#xff0c;首先需要了解这个文件的作用。该…

现场直击!触想智能亮相德国2023 SPS展会

当地时间11月14日上午9时 2023 年(德国)纽伦堡国际工业自动化及元器件展览会 SPS 展(以下简称&#xff1a;SPS展会)正式拉开帷幕&#xff0c;触想智能与来自全球各地的领先科技公司及前沿业者齐聚盛会&#xff0c;共赴一场科技与创新交汇的“饕餮盛宴”。 △ 2023 SPS展会开幕(…

可怕!.Net 8正式发布了,.Net野心确实不小!

随着三天.NET Conf 2023的会议结束了&#xff0c;.Net 8正式发布了。 .Net 8是官方号称有史以来性能最快的一个版本了。 .Net 8 增加了数以千计的性能、稳定性和安全性改进&#xff0c;以及平台和工具增强功能&#xff0c;有助于提高开发人员的工作效率和创新速度。 反正就是…

载誉前行 | 求臻医学MRD检测方案荣获金如意奖·卓越奖

2023年11月11日 由健康界、海南博鳌医学创新研究院 中国医药教育协会数字医疗专业委员会联合主办的 第三届“金如意奖”数字医疗优选解决方案 评选颁奖典礼 在2023中国医院管理年会上揭晓榜单并颁奖 求臻医学MRD检测解决方案 荣获第三届金如意奖最高奖项——卓越奖 这一…

ROS stm32 CAN通信

文章目录 运行环境&#xff1a;原理1.1 ros中的代码1)socketcan_bridge2)测试的ros-python包3)keil5中数据解析4)USB-CAN连接5)启动指令 运行环境&#xff1a; ubuntu18.04.melodic STM32&#xff1a;DJI Robomaster C板 ROS&#xff1a;18.04 硬件&#xff1a;USB-CAN&#x…

Vue修饰符(Vue事件修饰符、Vue按键修饰符)

目录 前言 Vue事件修饰符 列举较常用的事件修饰符 .stop .prevent .capture .once Vue按键修饰符 四个特殊键 获取某个键的按键修饰符 前言 本文介绍Vue修饰符&#xff0c;包括Vue事件修饰符以及按键修饰符 Vue事件修饰符 列举较常用的事件修饰符 .stop: …

Oneid方案

一、前文 用户画像的前提是标识出用户&#xff0c;存在以下场景&#xff1a;不同业务系统对同一个人的标识&#xff0c;匿名用户行为的行为归因&#xff1b;本文提供多种解决方案&#xff0c;提供大家思考。 二、方案矩阵 三、其他 相关连接&#xff1a; 如何通过图算法能力获…

ChatGPT助力高效办公——神奇的效率工具Airy

Airy是一款免费而又强大的高效办公软件&#xff0c;用户可以通过快捷键和丰富的内置插件&#xff0c;充分发挥GPT-3.5模型的强大功能&#xff0c;轻松实现搜索、翻译、文本生成与写作、文本概括与总结&#xff0c;同时还可以作为一款日程提醒工作&#xff0c;记录和提醒每天要做…

day17_多线程基础

今日内容 零、 复习昨日 一、作业 二、进程与线程 三、创建线程 四、线程的API 一、复习 IO流的分类 方向: 输入,输出类型: 字节(XxxStream),字符(XxxReader,XxxWriter)字节输入流类名: FileInputStream字节输出流类名: FileOutputStream字符输入流类名: FileReader字符输出流类…

为什么说MES管理系统是车间层与管理层的桥梁

随着制造业的快速发展&#xff0c;企业对于生产过程中的管理要求越来越高。为了满足这一需求&#xff0c;MES生产管理系统应运而生。MES管理系统作为车间层与管理层之间的桥梁&#xff0c;扮演着至关重要的角色。本文将探讨为什么说MES管理系统是车间层与管理层之间的桥梁。 一…

基础课4——客服中心管理者面临的挑战

客服管理者在当今的数字化时代也面临着许多挑战。以下是一些主要的挑战&#xff1a; 同行业竞争加剧&#xff1a;客服行业面临着来自同行业的竞争压力。为了获得竞争优势&#xff0c;企业需要不断提高自身的产品和服务质量&#xff0c;同时还需要不断降低成本、提高效率。然而…

彩虹桥架构演进之路-性能篇

一、前言 一年前的《彩虹桥架构演进之路》侧重探讨了稳定性和功能性两个方向。在过去一年中&#xff0c;尽管业务需求不断增长且流量激增了数倍&#xff0c;彩虹桥仍保持着零故障的一个状态&#xff0c;算是不错的阶段性成果。而这次的架构演进&#xff0c;主要分享一下近期针对…

【经验记录】Ubuntu系统安装xxxxx.tar.gz报错ImportError: No module named setuptools

最近在Anaconda环境下需要离线状态&#xff08;不能联网的情况&#xff09;下安装一个xxxxx.tar.gz格式的包&#xff0c;将对应格式的包解压后&#xff0c;按照如下命令进行安装 sudo python setup.py build # 编译 sudo python setup.py install # 安装总是报错如下信息&am…

竞赛 题目:基于大数据的用户画像分析系统 数据分析 开题

文章目录 1 前言2 用户画像分析概述2.1 用户画像构建的相关技术2.2 标签体系2.3 标签优先级 3 实站 - 百货商场用户画像描述与价值分析3.1 数据格式3.2 数据预处理3.3 会员年龄构成3.4 订单占比 消费画像3.5 季度偏好画像3.6 会员用户画像与特征3.6.1 构建会员用户业务特征标签…

数字化时代,VR虚拟展厅为企业带来全新商机

临近年关&#xff0c;各个行业都想在年关将至之时冲一波销量&#xff0c;各种婚博会、家博会、车展会多不胜数。但是线下展会终归是场地有限&#xff0c;因此为了扩大受众范围&#xff0c;同时节约一定宣传成本&#xff0c;实现全球范围的展示和推广&#xff0c;不少企业都会选…

【机器学习7】优化算法

1 有监督学习的损失函数 1.1 分类问题 对二分类问题&#xff0c; Y{1,−1}&#xff0c; 我们希望sign f(xi,θ)yi&#xff0c; 最自然的损失函数是0-1损失&#xff0c; 函数定义特点0-1损失函数非凸、非光滑&#xff0c;很难直接对该函数进行优化Hinge损失函数当fy≥1时&…

PG数据库实现merge into方法

语法格式1&#xff1a;有则更新&#xff0c;无则插入 insert into table_1(column_1,column_2, column_3) select column_1,column_2,column_3,from table_2on conflict (column_1)do update setcolumn_2 excluded.column_2,column_3 excluded.column_3如&#xff1a; inse…

墨西哥专线一次最多发几条柜?

墨西哥专线一次最多发几条柜这个问题涉及到海运业务中的一些复杂因素。墨西哥是一个重要的贸易国家&#xff0c;其与美国和加拿大之间的贸易往来非常频繁&#xff0c;因此海运业务也非常活跃。在墨西哥专线上&#xff0c;一次最多发几条柜通常取决于以下几个因素&#xff1a; 1…

使用X2Keyarch迁移CentOS至浪潮信息KeyarchOS体验

浪潮信息KeyarchOS简介 浪潮信息研发的云峦操作系统KeyarchOS(简称KOS), 是一款面向政企、金融等企业级用户的 Linux 服务器操作系统&#xff0c;其稳定性、安全性、兼容性和性能等核心能力均已得到充分验证。历经近10年自主研发历史&#xff0c;支持x86、ARM、Power主流架构处…