赞
踩
最近由于项目上的要求,需要实现一个细节图,就是一个矩形框中加载一个大图,右下或者左下方有一个小矩形框,可以加载全图,并显示大矩形框中图片在全图的位置,有点拗口,直接上结果:
QQ录屏
最开始由于笔者对QGraphics不熟悉,当时有两个思路,但第一时间选择用QLabel。过程中也遇到了坐标映射的问题,在领导的指点下解决了,不过由于最后需求中需要拖动细节图的红框而大图中视角随之移动,这个需求完成不了,最后选择了不采用QLabel,也是笔者水平有限,要是你们用QLabel实现了的话,就更棒了。由于最后没有实现需求,代码这里就不放出来了!
由于QLabel的失败,笔者就开始研究QGraphics,发现这个类用来加载图片简直太方便了。
首先,需要加载大图,我们这里需要在ui界面中拖入一个QGraphicsView控件,并点击右键提升为自定义控件类。这里要是不会提升自定义控件可以查阅相关博客或文章,并不难,其目的主要是用于处理鼠标滑轮和鼠标移动事件。同时新建一个类继承自QGraphicsView。
view只是一个用来显示图片的窗口(视图),最后加载图片是通过设置item加载到scene(场景中)。新建一个QGraphicsScene类,方便在主线程中使用。
- #ifndef MYSCENE_H
- #define MYSCENE_H
-
- #include <QObject>
- #include <QGraphicsScene>
- #include <QGraphicsItem>
- class MyScene : public QGraphicsScene
- {
- Q_OBJECT
- public:
- MyScene();
-
- };
-
- #endif // MYSCENE_H
- #include "myscene.h"
-
- MyScene::MyScene()
- {
-
-
- }
先在mainwindow中设置场景和视图,再通过addItem将图片加载到场景中。由于graphicsview是ui的控件,所有这里不需要再实例化。
MyScene myscene;
QGraphicsItem *map;//头文件声明
QImage image("work.png");//图片地址,自行修改
map=new QGraphicsPixmapItem(QPixmap::fromImage(image));
myscene.addItem(map);
ui.graphicsView->setScene(&myscene);//给视图设置场景
此时就可以将图片加载到场景中!
在自定义的QGraphicsView中重载鼠标滑轮事件。通过滑轮上下滑动即可放大缩小图片,其本质是缩小放大了view窗口相对于scene的大小,并没有实际地缩放scene中的图片。(代码段见6)
细节图的思路就是用一个QLabel来加载图片,显示图片整体,并在其中用红色矩形框表示view在图片的范围。在ui界面拖入一个QLabel,然后新建一个mylabel的类继承自QLabel,然后提升自定义控件。
QLabel控件中加载图片有两种思路,可以通过mainwindow中设置图片,也可以像笔者一样,通过信号槽,将图片发送到mylabel中,只需要在mainwindow中定义一个信号(void sendPixMap(QImage)),再在mylabel中定义一个槽函数(void getPixMap(QImage)),至于连接可以在mainwindow中连接,也可在ui界面正下方的Signals Slots Editor中连接,不过如果采用后者,需要在每个对象中右键“改变信号\槽”中添加对应的信号或槽。
笔者给定了label的尺寸,所有加载时缩放了图片的尺寸,使之能适应label的尺寸。
//缩放图片
qreal MyLabel::scaleImg(QImage &img){
if((img.width()>this->width())||(img.height()>this->height())){
double prop_w=(double)this->width()/(double)img.width();
double prop_h=(double)this->height()/(double)img.height();
if(prop_w>=prop_h){
return prop_h;
}else if(prop_w<prop_h){
return prop_w;
}
}else{
return 1;
}
}//得到图片
void MyLabel::getMap(QImage img1){
isPixMap=true;
img=img1;
scale_size=scaleImg(img);
img=img.scaled((double)img.width()*scale_size,(double)img.height()*scale_size);
}
重载绘图事件,使能在label中绘制图片。
//绘图事件
void MyLabel::paintEvent(QPaintEvent *event){
QPainter painter(this);
painter.drawPixmap(0,0,QPixmap::fromImage(img));
update();//刷新
}
重载mousemoveevent事件。
- //view放大缩小(鼠标滑轮事件)
- void MyView::wheelEvent(QWheelEvent * event)
- {
-
- if (event->modifiers() == Qt::CTRL)
- {//按住ctrl键 可以放大缩小
- if((event->delta() > 0)&&(scale_m >= 50))//最大放大到原始图像的50倍
- {
- return;
- }
- else if((event->delta() < 0)&&(scale_m <= 0.01))//图像缩小到自适应大小之后就不继续缩小
- {
- return;//重置图片大小和位置,使之自适应控件窗口大小
- }
- else
- {
- // 当前放缩倍数;
- qreal scaleFactor = this->matrix().m11();
- scale_m = scaleFactor;
- int wheelDeltaValue = event->delta();
- // 向上滚动,放大;
- if (wheelDeltaValue > 0)
- {
- this->scale(1.2, 1.2);
- scale_/=1.2;
- }
- else
- {// 向下滚动,缩小;
- this->scale(1.0 / 1.2, 1.0 / 1.2);
- scale_*=1.2;
- }
- update();
- }
- }
- }
- //鼠标移动事件
- void MyView::mouseMoveEvent(QMouseEvent *event){
- QPoint point(event->x(),event->y());
- emit sendMousePose(point);//实时显示鼠标在view中位置的信号
- if(!isPressed)return;
- QPointF lTInMap=mapToScene(leftTop);
- emit sendLTScale(scale_,lTInMap,view_width,view_height);//显示view中图片在整体图片的位置
- QGraphicsView::mouseMoveEvent(event);//必须加
- }
- //鼠标按下事件
- void MyView::mousePressEvent(QMouseEvent *event){
- isPressed=true;
- QGraphicsView::mousePressEvent(event);
- }
- //鼠标释放事件
- void MyView::mouseReleaseEvent(QMouseEvent *event){
- isPressed=false;
- }
由于在鼠标移动或按下时,会拦截该事件,所有我们在重载最后都需要在加上该事件,使其可以传到scene中 。同时滑动时放大,是通过view在scene中的左上角坐标,以及自身的长宽和缩放比来确定在scene中的范围,所有在鼠标滑轮事件和鼠标移动事件中,可以定义一个信号(void sendLTScale(qreal,QPointF,int,int)),实时传送此时view的各个参数,label中接受到后做如下处理:
void MyLabel::getLTScale(qreal scale_,QPointF lTop,int view_width,int view_height){
QRectF rect2(lTop.x(),lTop.y(),view_width*scale_*scale_size*0.01,view_height*scale_*scale_size*0.01);//view映射出的矩形框
pointLT=lTop;//label中红色矩形框左上角坐标
rect_width=view_width*scale_*scale_size*0.01;
rect_height=view_height*scale_*scale_size*0.01;
rect=rect2;
}
上面这个是拖动或滑动滑轮,都会发送当前view在图片位置的信号,label中连接槽函数得到后,存到一个全局变量rect的矩形框中,然后label中的位绘图事件中再加入绘制矩形。
painter.drawRect(rect);
上面实现了拖动view中图片,移动label中rect,此外我们还要拖动label中rect,随之view中视角改变。当然我们还需要把rect限制在图片的范围内。
- //鼠标按下
- void MyLabel::mousePressEvent(QMouseEvent *event){
- if(event->button()==Qt::LeftButton){
- //判断鼠标按下时光标是否在矩形框内
- if((event->x()>=pointLT.x())&&(event->y()>=pointLT.y())&&(event->x()<=pointRB.x())&&(event->y()<=pointRB.y())){
- isPressed=true;
- startPoint=event->pos();//发送鼠标按下的坐标
- QPointF lTop(pointLT.x()/scale_size,pointLT.y()/scale_size);
- emit sendPressPoint(lTop);
- }
- }
- }
- //鼠标移动
- void MyLabel::mouseMoveEvent(QMouseEvent *event){
-
- if(isPressed == false||rect.width()==img.width())
- return;
- endPoint=event->pos();//获得鼠标移动时每个点,x和y坐标减去前一个点的坐标就是移动的距离
- qreal temp_x=endPoint.x()-startPoint.x();
- qreal temp_y=endPoint.y()-startPoint.y();
- //限制矩形框在图片内
- if(pointLT.x()+temp_x<=0){
- pointLT.setX(0);
- }
- else {
- if(pointLT.x()+rect_width +temp_x >=img.width()){
- pointLT.setX(img.width() - rect_width);
- }
- else {
- pointLT.setX(pointLT.x() + temp_x);
- }
- }
-
- if(pointLT.y() + temp_y<=0){
- pointLT.setY(0);
- }else {
- if(pointLT.y() +rect_height+ temp_y >=img.height()){
- pointLT.setY(img.height() - rect_height);
- }
- else {
- pointLT.setY(pointLT.y() + temp_y);
- }
- }
-
- QRectF rect2(pointLT.x(),pointLT.y(),rect_width,rect_height);
- rect=rect2;
- startPoint=endPoint;
- QPointF lTop(pointLT.x()/scale_size,pointLT.y()/scale_size);
- emit sendMovePoint(lTop);
-
- }
- //鼠标释放
- void MyLabel::mouseReleaseEvent(QMouseEvent *event){
- if(event->button()==Qt::LeftButton){
- endPoint=event->pos();
- isPressed=false;
- }
- }
上面拖动label中rect,鼠标实时发送光标位置,后一个减前一个坐标,并把当前坐标又赋值给前一个坐标,即可获得移动的距离。
但是,我们不能直接移动view(即不能使用view.move(x,y),这是在窗口中拖动view窗口),因为通过maptoscene这个函数发现,我们鼠标拖拽里面的图片,实际view映射到item上的坐标根本没有改变。笔者这里选择的是移动scene中的item。
void MainWindow::getMovePoint(QPointF temp){
map->moveBy(-((double)temp.x()-(double)start_.x()),-((double)temp.y()-(double)start_.y()));
emit sendMoveSize(((double)temp.x()-(double)start_.x()),((double)temp.y()-(double)start_.y()));
start_=temp;
}
通过信号槽将鼠标的坐标发送到主线程中,因为我们是在mainwindow这个里面加入item,也可在这里移动item。
由于笔者上面移动item,虽然达到了拖动label中rect,view中视角随之移动,但是也引发了深层次问题,即我们拖动了label中rect,再讲鼠标放在view中拖拽图片或者放大缩小图片,就会发现,此时图片发现了便宜,坐标已经映射不到原来的坐标了。
最后,由于笔者水平有限,没有更好的办法,就选择在拖动label中rect,一旦在view中拖拽图片或放大缩小图片,我们就将偏移量还原回来,此时也达到了一定目的。希望笔者后续水平提升可以想出更好的方法。
思路就是在view中鼠标滑轮和按下事件中加一个判断,将item移动的偏移量,存在view的两个全局变量里,移动触发这两个事件,判断是否变量为0,不为0,就发送信号,主线程中接受到触发槽函数将item移动这两个偏移量,然后再在判断中将偏移量置零。
- //view放大缩小
- void MyView::wheelEvent(QWheelEvent * event)
- {
- initView();//里面没啥东西,就是将view的width和height赋值给全局变量
-
- if (event->modifiers() == Qt::CTRL)
- {//按住ctrl键 可以放大缩小
- if(offset_h!=0||offset_w!=0){
- emit sendOffset(offset_w,offset_h);//发送偏移量
- offset_h=0;
- offset_w=0;//偏移量置零
- QPointF lTInMap=mapToScene(leftTop);
- emit sendLTScale(scale_,lTInMap,view_width,view_height);
- }
- if((event->delta() > 0)&&(scale_m >= 50))//最大放大到原始图像的50倍
- {
- return;
- }
- else if((event->delta() < 0)&&(scale_m <= 0.01))//图像缩小到自适应大小之后就不继续缩小
- {
- return;//重置图片大小和位置,使之自适应控件窗口大小
- }
- else
- {
- // 当前放缩倍数;
- qreal scaleFactor = this->matrix().m11();
- scale_m = scaleFactor;
- int wheelDeltaValue = event->delta();
- // 向上滚动,放大;
- if (wheelDeltaValue > 0)
- {
- this->scale(1.2, 1.2);
- scale_num*=1.2;
- scale_/=1.2;
- }
- else
- {// 向下滚动,缩小;
- this->scale(1.0 / 1.2, 1.0 / 1.2);
- scale_num/=1.2;
- scale_*=1.2;
- }
- update();
- rightButtom.setX(this->width());
- rightButtom.setY(this->height());
- QPointF lTInMap=mapToScene(leftTop);
- emit sendScale_m(scale_num);
- emit sendLTScale(scale_,lTInMap,view_width,view_height);
- }
- }
- }
- //鼠标移动事件
- void MyView::mouseMoveEvent(QMouseEvent *event){
- QPoint point(event->x(),event->y());
- emit sendMousePose(point);
- if(!isPressed)return;
- QPointF lTInMap=mapToScene(leftTop);
- emit sendLTScale(scale_,lTInMap,view_width,view_height);
- QGraphicsView::mouseMoveEvent(event);
- }
- //鼠标按下事件
- void MyView::mousePressEvent(QMouseEvent *event){
- isPressed=true;
- if(offset_h!=0||offset_w!=0){
- emit sendOffset(offset_w,offset_h);
- offset_h=0;
- offset_w=0;
- QPointF lTInMap=mapToScene(leftTop);
- emit sendLTScale(scale_,lTInMap,view_width,view_height);
- }
- QGraphicsView::mousePressEvent(event);
- }
- //鼠标释放事件
- void MyView::mouseReleaseEvent(QMouseEvent *event){
- isPressed=false;
- }
mainwindow接收到信号后,将item移动对应的偏移量。
void MainWindow::getOffset(double offset_w,double offset_h){
map->moveBy(offset_w,offset_h);
}
接下来,笔者的工作还需要在item上画图,标点或画线,后续还会涉及到坐标转换的问题,额,几何学的差,哎~~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。