1. 程式人生 > 實用技巧 >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);
 }