QT_滚轮缩放_坐标轴

实现功能:滚轮缩放,鼠标左键按住移动,缩放时刻度线跟着一起移动,缩放时以鼠标的位置为中心

效果图:

QT_滚轮缩放_坐标轴

 

工程文件:

 

 QT_滚轮缩放_坐标轴

mywidget.h

 

 

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>

#include "ui_mywidget.h"
#include <QPaintEvent>
#include <QtGui>



QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE

class MyWidget : public QWidget
{
    Q_OBJECT

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

    QPointF ObjectPtoDisplayP(QPointF objPoint);
    QPointF DisplayPtoObjectP(QPointF disPoint);
    QPointF WidgetPtoObjectP(QPointF wigPoint);//把相对于widget的坐标,转化为以画布的坐标
    QPointF ObjectPtoWidgetP(QPointF objPoint);//把相对于以画布的坐标,转化为widget的坐标
    QPointF ValuePtoObjectP(QPointF valPoint);
    QPointF ObjectPtoValueP(QPointF objPoint);
    //放大,以放缩的中心scale_center为准点,按照放缩比例scale_value,对点pos_before的x、y坐标进行放大
    QPointF scaleIn(QPointF pos_before, QPointF scale_center, double scale_value);
    //缩小,以放缩的中心scale_center为准点,按照放缩比例scale_value,对点pos_before的x、y坐标进行缩小
    QPointF scaleOut(QPointF pos_before, QPointF scale_center, double scale_value);

protected:
    void paintEvent(QPaintEvent *event);
    void wheelEvent(QWheelEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);

private:
    Ui::MyWidget *ui;

    int margin_size;

//    QImage image;

    QPointF paint_org;              //widget坐标系,是整体显示区域的左上角起点
    QSize paint_size_first;         //画布的大小
    QSize paint_size_old;
    QSize paint_size_new;

    QPointF paint_center_old;       //display坐标系
    QPointF paint_center_new;       //display坐标系

    double offset_x;
    double offset_y;

    QPointF rect_center;           //以画布为坐标系
    QPointF rect_topl;             //矩形框的左上角,display坐标系
    QPointF rect_bottomr;          //矩形框的右下角,display坐标系

    double scale_value;             //缩放的比例

    QPointF mousepress_org;        //display坐标系,储存鼠标移动中的过程值或释放左键时的位置点坐标

    QPointF axis_x_old;            //display坐标系
    QPointF axis_y_old;            //display坐标系
    double axis_scale;

    double offsetv_x;
    double offsetv_y;
    double pixel_per_mm;

    QPointF mouse_current_pos;      //object坐标系


};
#endif // MYWIDGET_H

main.cpp:

#include "mywidget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MyWidget w;
    w.show();
    return a.exec();
}

mywidget.cpp:

#include "mywidget.h"
#include "ui_mywidget.h"


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

    margin_size =35;//空白边距



     paint_org = QPointF(margin_size + 20, margin_size - 20);  //确定绘画image的左上角
     paint_size_first = QSize(this->width() - margin_size * 2, this->height() - margin_size * 2);//QSize 类代表一个矩形区域的大小,宽度和高度,计算画布的大小
     paint_size_old = paint_size_first;
     paint_size_new = paint_size_first;

     paint_center_old = QPointF(paint_size_first.width() / 2, paint_size_first.height() / 2);//以画布左上角为原点,确定画布的中心点坐标,是在display的坐标
     paint_center_new = paint_center_old;

     offset_x = 0;
     offset_y = paint_size_new.height();

    //矩形框的中心以画布的中心为中心,以画布左上角为原点,确定画矩形框的中心点坐标
     rect_center = QPointF(paint_size_new.width() / 2, paint_size_new.height() / 2);
    //15指的是矩形框跟画布之间的间隙
     rect_topl = QPointF(rect_center.rx() - (paint_size_new.height() / 2 -15 ), rect_center.ry() + (paint_size_new.height() / 2 -15 ));
     rect_bottomr = QPointF(rect_center.rx() + (paint_size_new.height() / 2-15 ), rect_center.ry() - (paint_size_new.height() / 2-15 ));
    //缩放比例初始化
     scale_value = 1.05;
    //储存鼠标移动中的过程值或释放左键时的位置点坐标,这里初始化
     mousepress_org = QPointF(0,0);

     //把画布的中心赋值给x、y轴的原点
     axis_x_old = axis_y_old = QPointF(rect_center.rx(), rect_center.ry());
     axis_scale = 20.0;

     offsetv_x = rect_center.rx();
     offsetv_y = rect_center.ry();


     pixel_per_mm = (double)(rect_bottomr.rx() - rect_topl.rx()) / 600;

     mouse_current_pos = QPointF(rect_center.rx(), rect_center.ry());//鼠标当前位置显示点,初始化为显示矩形中心位置坐标

     //此属性保存是否为小部件启用鼠标跟踪
     //如果禁用了鼠标跟踪(默认设置),则当移动鼠标时,窗口小部件仅在至少按下一个鼠标按钮时接收鼠标移动事件
     //如果启用了鼠标跟踪,即使没有按下任何按钮,小部件也会接收鼠标移动事件
     this->setMouseTracking(true);

     this->resize(800,800);

}

MyWidget::~MyWidget()
{
    delete ui;
}
void MyWidget::paintEvent(QPaintEvent *event)
{
    //当窗口刷新时,按照当前窗口的大小,刷新paint_size_new画布的大小
    paint_size_new = QSize(this->width() - margin_size * 2, this->height() - margin_size * 2);

    paint_org = QPointF(margin_size + 20, margin_size - 20);  //确定绘画image的左上角
    QImage image = QImage(QSize(this->width() - margin_size * 2, this->height() - margin_size * 2), QImage::Format_RGB32);//创建画布对象
    QColor backColor = qRgb(255, 255, 255);//设置为白色
    image.fill(backColor);//填充画布为白色,没有这一步则画布默认为黑色

    //因为窗口大小改变导致画布大小发生改变
    //当画布的大小发生改变时
    if (paint_size_new != paint_size_old)
    {
        offset_x = 0;
        offset_y = paint_size_new.height();

        paint_center_new = QPointF(paint_size_new.width() / 2, paint_size_new.height() / 2);
        //根据变化的画布大小,改变矩形框的位置
        rect_center = paint_center_new - paint_center_old + rect_center;//根据画布的中心,移动矩形的中心,保持两个中心重合
        rect_topl = paint_center_new - paint_center_old + rect_topl;
        rect_bottomr = paint_center_new - paint_center_old + rect_bottomr;

        paint_size_old = paint_size_new;
        paint_center_old = paint_center_new;
    }

    offsetv_x = rect_center.rx();
    offsetv_y = rect_center.ry();

    pixel_per_mm = (double)(rect_bottomr.rx() - rect_topl.rx()) / 600;
    //用于绘画画布
    QPainter painterimage(this);
    //用于绘画画布上的内容
    //painter在画布上作画时,需要得到object的坐标才行,所以当以display为坐标进行输入时,需要转化为object才行
    QPainter painter(&image);
    QPen pen(Qt::black);
    painter.setPen(pen);
//    painter.setRenderHint(QPainter::Antialiasing, true);

    QRectF rec(DisplayPtoObjectP(rect_topl), DisplayPtoObjectP(rect_bottomr));//矩形框的左上角和右下角的坐标
    painter.drawRect(rec);//画矩形框
    painter.drawPoint(DisplayPtoObjectP(rect_center));//画出中心点

    painterimage.drawImage(paint_org, image);

    //在鼠标的当前点位置,绘画当前点的坐标数值,精度为1个小数点
    painter.drawText(mouse_current_pos, "(" + QString::number(ObjectPtoValueP(mouse_current_pos).rx(), 'f', 1) + "mm, " + QString::number(ObjectPtoValueP(mouse_current_pos).ry(), 'f', 1) + "mm)");

    //x 轴的主直线,画在画布的最下方,跟画布重合
    painter.drawLine(DisplayPtoObjectP(QPointF(0, rect_center.ry())), DisplayPtoObjectP(QPointF(image.width(), rect_center.ry())));
    //Y 轴的主直线,画在画布的最左方,跟画布边缘重合,如果完全重合就看不到直线,所以竖线向左移动一点
    painter.drawLine(DisplayPtoObjectP(QPointF(rect_center.rx(), 0)), DisplayPtoObjectP(QPointF(rect_center.rx(), image.height())));

    //此四行是在坐标轴的端点处各画一条长刻度线,不建议添加
//    painter.drawLine(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(0, 0))), ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(0, -10))));
//    painter.drawLine(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(0, 0))), ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(-10, 0))));
//    painter.drawLine(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(image.width(), 0))), ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(image.width(), -10))));
//    painter.drawLine(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(0, image.height()))), ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(-10, image.height()))));

    //此四行,是始终在坐标轴的端点,显示端点的坐标值,不建议添加
//    painter.drawText(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(0, -20))), QString::number((double)(0 - offsetv_x) / pixel_per_mm, 'f', 1));
//    painter.drawText(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(-30, 0))), QString::number((double)(0 - offsetv_y) / pixel_per_mm, 'f', 1));
//    painter.drawText(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(image.width(), -20))), QString::number((double)(image.width() - offsetv_x) / pixel_per_mm, 'f', 1));
//    painter.drawText(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(-30, image.height()))), QString::number((double)(image.height() - offsetv_y) / pixel_per_mm, 'f', 1));

    //绘画坐标轴标题
    painter.drawText(DisplayPtoObjectP(QPointF(image.width()-45, rect_center.ry()-20)), QString(tr("X[mm]")));
//    painter.rotate(-90);
    painter.drawText(DisplayPtoObjectP(QPointF(rect_center.rx()-50, image.height()-20)), QString(tr("Y[mm]")));
//    painter.rotate(90);

    //此处两个判断语句,主要保证当主窗口大小变化时,x、y轴上的0刻度位置始终保持在各自轴的中心位置
    //x轴
    if (rect_center.rx() > 0 && rect_center.rx() < image.width())
     {
         painter.drawLine(DisplayPtoObjectP(QPointF(rect_center.rx(), rect_center.ry())),DisplayPtoObjectP(QPointF(rect_center.rx(), rect_center.ry()-10)));
         painter.drawText(DisplayPtoObjectP(QPointF(rect_center.rx(), rect_center.ry()-20)), QString::number((double)(rect_center.rx() - offsetv_x) / pixel_per_mm, 'f', 1));

     }
    //y轴
//     if (rect_center.ry() > 0 && rect_center.ry() < image.height())
//     {
//         painter.drawLine(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(0, rect_center.ry()))), ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(-10, rect_center.ry()))));
//         painter.drawText(ObjectPtoWidgetP(DisplayPtoObjectP(QPointF(-30, rect_center.ry()))), QString::number((double)(rect_center.ry() - offsetv_y) / pixel_per_mm, 'f', 1));
//     }

    //保证axis_scale在(17.5至30)范围内
     if (axis_scale >= 35.0)
     {
         axis_scale = axis_scale / 2;
     }
     else if (axis_scale <= 15.0)
     {
         axis_scale = axis_scale * 2;
     }

    //x轴负半轴刻度线和坐标值
     axis_x_old = QPointF(rect_center.rx(),rect_center.ry());
     int i = 0;
     while (axis_x_old.rx() > axis_scale)
     {
         axis_x_old.rx() = axis_x_old.rx() - axis_scale;
         i++;
         if (axis_x_old.rx() > 0 && axis_x_old.rx() < image.width())
         {
             //每5次,画一条长刻度线,并显示刻度值
             if (i % 5 == 0)
             {
                 //长刻度线
                 painter.drawLine(DisplayPtoObjectP(QPointF(axis_x_old.rx(), axis_x_old.ry())), DisplayPtoObjectP(QPointF(axis_x_old.rx(), axis_x_old.ry()-10)));
                 painter.drawText(DisplayPtoObjectP(QPointF(axis_x_old.rx(), axis_x_old.ry()-20)), QString::number((double)(axis_x_old.rx() - offsetv_x) / pixel_per_mm, 'f', 1));
             }
             else
             {
                 //短刻度线
                 painter.drawLine(DisplayPtoObjectP(QPointF(axis_x_old.rx(), rect_center.ry())), DisplayPtoObjectP(QPointF(axis_x_old.rx(), rect_center.ry()-5)));
             }
         }
     }

    //x正半轴刻度线和坐标值
     axis_x_old = QPointF(rect_center.rx(), rect_center.ry());
     i = 0;
     while ((image.width() - axis_x_old.rx()) > axis_scale)
     {
         axis_x_old.rx() = axis_x_old.rx() + axis_scale;
         i++;
         if (axis_x_old.rx() > 0 && axis_x_old.rx() < image.width())
         {
             if (i % 5 == 0)
             {
                 painter.drawLine(DisplayPtoObjectP(QPointF(axis_x_old.rx(), rect_center.ry())), DisplayPtoObjectP(QPointF(axis_x_old.rx(), rect_center.ry()-10)));
                 painter.drawText(DisplayPtoObjectP(QPointF(axis_x_old.rx(), rect_center.ry()-20)), QString::number((double)(axis_x_old.rx() - offsetv_x) / pixel_per_mm, 'f', 1));
             }
             else
             {
                 painter.drawLine(DisplayPtoObjectP(QPointF(axis_x_old.rx(), rect_center.ry())), DisplayPtoObjectP(QPointF(axis_x_old.rx(), rect_center.ry()-5)));
             }
         }
     }

    //y负半轴刻度线和坐标值
     axis_y_old = QPointF(rect_center.rx(), rect_center.ry());
     i = 0;
     while (axis_y_old.ry() > axis_scale)
     {
         axis_y_old.ry() = axis_y_old.ry() - axis_scale;
         i++;
         if (axis_y_old.ry() > 0 && axis_y_old.ry() < image.height())
         {
             if (i % 5 == 0)
             {
                 painter.drawLine(DisplayPtoObjectP(QPointF(rect_center.rx(), axis_y_old.ry())), DisplayPtoObjectP(QPointF(rect_center.rx()-10, axis_y_old.ry())));
                 painter.drawText(DisplayPtoObjectP(QPointF(rect_center.rx()-60, axis_y_old.ry())), QString::number((double)(axis_y_old.ry() - offsetv_y) / pixel_per_mm, 'f', 1));
             }
             else
             {
                 painter.drawLine(DisplayPtoObjectP(QPointF(rect_center.rx(), axis_y_old.ry())), DisplayPtoObjectP(QPointF(rect_center.rx()-5, axis_y_old.ry())));
             }
         }
     }

     //y正半轴刻度线和坐标值
     axis_y_old = QPointF(rect_center.rx(), rect_center.ry());
     i = 0;
     while ((image.height() - axis_y_old.ry()) > axis_scale)
     {
         axis_y_old.ry() = axis_y_old.ry() + axis_scale;
         i++;
         if (axis_y_old.ry() > 0 && axis_y_old.ry() < image.height())
         {
             if (i % 5 == 0)
             {
                 painter.drawLine(DisplayPtoObjectP(QPointF(rect_center.rx(), axis_y_old.ry())), DisplayPtoObjectP(QPointF(rect_center.rx()-10, axis_y_old.ry())));
                 painter.drawText(DisplayPtoObjectP(QPointF(rect_center.rx()-60, axis_y_old.ry())), QString::number((double)(axis_y_old.ry() - offsetv_y) / pixel_per_mm, 'f', 1));
             }
             else
             {
                 painter.drawLine(DisplayPtoObjectP(QPointF(rect_center.rx(), axis_y_old.ry())), DisplayPtoObjectP(QPointF(rect_center.rx()-5, axis_y_old.ry())));
             }
         }
     }

//     painter.drawLine(DisplayPtoObjectP(QPointF(rect_center.rx()+0, rect_center.ry()+0)), DisplayPtoObjectP(QPointF((100 - offsetv_y) / pixel_per_mm, (100 - offsetv_y) / pixel_per_mm)));
     painterimage.drawImage(paint_org, image);


 }

void MyWidget::wheelEvent(QWheelEvent *event)
 {
    //在滚轮滚动时,把鼠标的当前点,转化为画布的坐标
      QPointF mousepos = WidgetPtoObjectP(event->pos());
      if (event->delta() > 0)
      {
          //以鼠标的当前点为中心,按照缩放比例scale_value,进行放大
          //对矩形框进行放大
         rect_center = scaleIn(rect_center, ObjectPtoDisplayP(mousepos), scale_value);
         rect_topl = scaleIn(rect_topl, ObjectPtoDisplayP(mousepos), scale_value);
         rect_bottomr = scaleIn(rect_bottomr, ObjectPtoDisplayP(mousepos), scale_value);
         //对坐标轴的刻度进行同步缩放
         axis_scale = axis_scale * scale_value;
      }
      else
      {
          //缩小
         rect_center = scaleOut(rect_center, ObjectPtoDisplayP(mousepos), scale_value);
         rect_topl = scaleOut(rect_topl, ObjectPtoDisplayP(mousepos), scale_value);
         rect_bottomr = scaleOut(rect_bottomr, ObjectPtoDisplayP(mousepos), scale_value);
         //对坐标轴的刻度进行同步缩放
         axis_scale = axis_scale / scale_value;
       }
        // 没有这个刷新,每次进行了滚轮,绘画事件不会立即生效
        repaint();
 }

void MyWidget::mouseMoveEvent(QMouseEvent *event)
 {
    //pos():返回鼠标光标相对于接收事件的小部件的位置
     mouse_current_pos = WidgetPtoObjectP(event->pos());//刷新鼠标的当前焦点
    //按住左键时移动
     if (event->buttons() & Qt::LeftButton)
     {
         QPointF mousepos_move = ObjectPtoDisplayP(WidgetPtoObjectP(event->pos()));

         //mousepos_move - mousepress_org是每次移动之间的差值,代表要把矩形框移动多远
         rect_center = mousepos_move - mousepress_org + rect_center;
         rect_topl = mousepos_move - mousepress_org + rect_topl;
         rect_bottomr = mousepos_move - mousepress_org + rect_bottomr;

         mousepress_org = mousepos_move;
     }

     repaint();
 }

void MyWidget::mousePressEvent(QMouseEvent *event)
 {
     if (event->button() == Qt::LeftButton)
     {
         mousepress_org = ObjectPtoDisplayP(WidgetPtoObjectP(event->pos()));
     }
 }

//把以左上角为原点的画布坐标系,转为以左下角为原点的坐标系
QPointF MyWidget::ObjectPtoDisplayP(QPointF objPoint)
 {
     return QPointF(objPoint.rx() - offset_x, -objPoint.ry() + offset_y);
 }

QPointF MyWidget::DisplayPtoObjectP(QPointF disPoint)
 {
     return QPointF(disPoint.rx() + offset_x, -disPoint.ry() + offset_y);
 }
//以放缩的中心scale_center为准点,按照放缩比例scale_value,对点pos_before的x、y坐标进行放大
QPointF MyWidget::scaleIn(QPointF pos_before, QPointF scale_center, double scale_value)
 {
     QPointF temp;
     temp.rx() = (double)(pos_before.rx() - scale_center.rx()) * scale_value + scale_center.rx();
     temp.ry() = (double)(pos_before.ry() - scale_center.ry()) * scale_value + scale_center.ry();
     return temp;
 }
//以放缩的中心scale_center为准点,按照放缩比例scale_value,对点pos_before的x、y坐标进行缩小
QPointF MyWidget::scaleOut(QPointF pos_before, QPointF scale_center, double scale_value)
 {
     QPointF temp;
     temp.rx() = (double)(pos_before.rx() - scale_center.rx()) / scale_value + scale_center.rx();
     temp.ry() = (double)(pos_before.ry() - scale_center.ry()) / scale_value + scale_center.ry();
     return temp;
 }

//把相对于widget的坐标,转化为以画布的坐标
QPointF MyWidget::WidgetPtoObjectP(QPointF wigPoint)
 {
     return QPointF(wigPoint - paint_org);
 }
//把相对于以画布的坐标,转化为widget的坐标
QPointF MyWidget::ObjectPtoWidgetP(QPointF objPoint)
 {
     return QPointF(objPoint + paint_org);
 }

QPointF MyWidget::ValuePtoObjectP(QPointF valPoint)
 {
     return DisplayPtoObjectP(QPointF(valPoint.rx() * pixel_per_mm + offsetv_x, valPoint.ry() * pixel_per_mm + offsetv_y));
 }

QPointF MyWidget::ObjectPtoValueP(QPointF objPoint)
 {
     return QPointF((double)(ObjectPtoDisplayP(objPoint).rx() - offsetv_x) / pixel_per_mm, (double)(ObjectPtoDisplayP(objPoint).ry() - offsetv_y) / pixel_per_mm);
 }

 

上一篇:verilong 串口通信程序及原理


下一篇:STM32的GPS数据解析程序设计说明——基于NMEA0183协议