实现功能:滚轮缩放,鼠标左键按住移动,缩放时刻度线跟着一起移动,缩放时以鼠标的位置为中心
效果图:
工程文件:
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); }