Qt项目防掉坑指南
写这个指南的原因呢,主要是最近利用Qt + OpenCV大体完成了一个电气设备诊断的桌面软件。这其中掉坑,爬坑数次,有一些心得,打算写在这和大家分享。自己铭记的同时让后来人可以避开这些坑。我的环境是vs2017+OpenCV3+Qt4
本文主要分为两个部分:
- Qt界面实现的坑
- OpenCV的坑
Qt界面实现的坑
由于整个项目我做了两个月,战线拉的比较长,所以有些地方记得不太清,需要看着自己代码慢慢回忆或者是找个最近解决的坑先写,所以顺序上可能会有些乱,希望大家谅解。
1. QStackedWidget大小无法调整
这个其实是一个比较小的问题,但是也让我苦恼了很久。首先说问题的情况,就是一个大的界面(窗口)上实现可以切换的子页面(子窗口),这个时候我们就会用到QStackedWidget 控件。我们将需要切换的小窗口一个个压栈到QStackedWidget 的对象中,然后就可以实现相关界面的切换了。问题在于你在UI creator中画一个切换子窗口的区域,之后再想更改这个子区域的大小会变得比较困难。因为你无法拖动这个区域的边界。截图说明
这个红线里面的区域就是我刚才说的子区域。这个区域你在属性编辑器里面,可以看到根本无法更改其几何属性。
这个几何属性都是灰的。想要更改QStackedWidget子区域的大小,只能通过mininumSize的数值才能实现。mininumSize这个参数也在属性编辑器里面,你通过改这个参数可以得到你想要的大小。
2. Qt控件的提升
Qt的这个功能,让我感到了惊喜。首先说遇到这个问题的背景,我当时实现的是在Qlabel上显示图片,然后在这个显示的图片上画出一个闭合的四边形,将这个闭合的四边形内部的像素透视变换到一个固定大小的矩形图片上,并且将这个矩形图片显示现在窗口的另一个QLabel上。这个技术难度在于原本只显示图片的控件上再加上画笔功能,开始我不太明白应该怎么做。在谷歌上搜,也没搜对路。后来恍然大悟,我在谷歌上搜”Qt实现画板功能”,就能找到我想要的。
主要实现的技术方法就是Qt控件的提升,其实说白了就是类的继承。我将原来的QLabel类继承过来,然后写出我需要的类myLabel,在myLabel类中实现画笔功能。
我的具体实现参考的Qt界面使用自定义控件“提升为” 以及 Qt学习之路(31): 一个简易画板的实现(QWidget) 这两篇博客写的就很好了,如果实在还有问题可以去我的github上下载相关的Qt资料(我基本也就是用的那些,感觉好的话,记得给我点个star哦)。我被这个问题困扰了很久,我在这把代码贴出来,希望能对大家有帮助。
我在这贴部分的实现代码,给大家:
#include "myLabel.h"
myLabel::myLabel(QGroupBox *parent):QLabel(parent)
{
this->lineStartPoint = QPoint(0, 0);
this->lineEndPoint = QPoint(0, 0);
this->lineColor = QColor(Qt::green);
this->lineSize = 2;
//this->signal_line->set_linefeature(1, Qt::black);
signal_line = NULL; //这个地方没有初始化的话 就没办法显示出来
}
//绘制线条
void myLabel::paintEvent(QPaintEvent *e)
{
QLabel::paintEvent(e);//必须有,才能让背景图片显示出来
//QPainter
QPainter painter(this);
QPen pen;
pen.setColor(lineColor);
pen.setWidth(lineSize);
painter.setPen(pen);
//painter.drawLine(lineStartPoint, lineEndPoint);
for each (myLine* signal_line in lines)
{
signal_line->paint(painter);
lineStartPoint = signal_line->get_startpoint();
lineEndPoint = signal_line->get_endpoint();
//signal_line->set_startpoint(lineStartPoint);
//signal_line->set_endpoint(lineEndPoint);
}
if (signal_line)
{
signal_line->paint(painter);
}
}
//鼠标按下
void myLabel::mousePressEvent(QMouseEvent *e)
{
signal_line = new myLine;
//signal_line->set_linefeature(5, Qt::blue);
if (signal_line!=NULL)
{
isPressed = true;
//lines << signal_line;
lines.push_back(signal_line);
signal_line->set_startpoint(e->pos());
signal_line->set_endpoint(e->pos());
}
/*lineStartPoint = e->pos();
lineEndPoint = e->pos();
points.push_back(lineStartPoint);*/
//在图片上绘制
}
//鼠标移动
void myLabel::mouseMoveEvent(QMouseEvent *e)
{
if (signal_line && isPressed)
{
signal_line->set_endpoint(e->pos());
update();
}
/*lineEndPoint = e->pos();
update();*/
//在这加上一个else待会试试
}
//鼠标抬起
void myLabel::mouseReleaseEvent(QMouseEvent *e)
{
isPressed = false;
lineEndPoint = e->pos();
signal_line->set_endpoint(e->pos());
//isPressed = false;
update();
}
void myLabel::setLineColor(const QColor lineColor)
{
this->lineColor = lineColor;
}
void myLabel::setLineSize(const int lineSize)
{
this->lineSize = lineSize;
}
QPoint myLabel::getStartPoint()
{
lineStartPoint = signal_line->get_startpoint();
return lineStartPoint;
}
QPoint myLabel::getEndPoint()
{
lineEndPoint=signal_line->get_endpoint();
return lineEndPoint;
}
QVector<myLine*> myLabel::getLines() {
return lines;
}
void myLabel::clear()
{
lineStartPoint = QPoint(0, 0);
lineEndPoint = QPoint(0, 0);
while (!lines.empty())
{
lines.pop_back();
}
update();
}
最终的实现效果:
3. 信号与槽以及多层次界面的实现
首先讲信号与槽,我相信用Qt做过界面的同学,信号与槽基本是躲不开的。一般情况下,信号与槽的连接也不算是大的问题。但是如果你实现的是多层次界面,就会遭遇各种问题。我解释一下多层次界面,多层次界面就是在一个大的界面里面还有子界面,这个时候实现信号与槽的传输就需要注意了。我当时参考的【Qt】窗体间传递数据(跨控件跨类),三种情况与处理方法 。这篇博客将信号传递分为三种形式:子界面之间的传递、子界面向主界面的传递、主界面向子界面的传递。
将上面三个问题建模:
已知三个窗体,A为B C的父控件,B与C互为兄弟控件 那么参数传递分三种情况:
1.B向A(C向A)传递参数
2.B向C(C向B)传递参数
3.A向B(A向C)传递参数
情况1: 网上描述的“信号与槽”进行参数传递,基本上都是情况1 在类B中设置一个信号并发射,在A中设定槽,并connect接收 B的代码
情况2: 关于A窗体下的两个子窗体间传递数据,发送信号的操作在对应的子窗体中,连接信号与槽的操作应该在主窗体A中; 假设B向C发送数据
情况3: 关于情况3,从父窗体向子窗体传递参数,在初学Qt的时候,纠结了很久却找不到方法,在子窗体的时候,无法得到父窗体的指针,那么connect的时候就没办法指定对应的信号来源。这个问题,网上似乎根本没有找到对应的解决方法。 想了很久,才突然恍然大悟,一直想着使用Qt“信号与槽”的机制来进行窗体间的数据传输,反而被局限了思维。却忘了C++本身的方法,直接在子窗体中留一个共有成员函数,让父窗体进行调用,自然就把数据传输了过去,从父窗体向子窗体(A->B/A->C)压根不需要使用“信号与槽”的参数传递方法 。
4. QLabel读取图片显示发生畸变扭曲
这个问题是真的坑,当时我读取好了csv文件,然后将之转换为一张图片,在电脑上显示的没有问题,结果在软件界面显示出来就发生了严重的扭曲变形。我当时也就纳了闷。后来各种找资料发现了原因,具体是参照的这篇博客用Qlabel显示pixcelmap时出现的图像扭曲的处理 。这篇博客讲了很多,其实真正有用的就是“观察图片的分辨率,发现图片宽度不为4的倍数时就会出现该问题。于是,猜想问题就应该是图像数据没有按每行4字节的组织造成的。” 是的 如果你的图片长宽不是4的倍数,这个QLabel就会无法显示。
OpenCV的坑
OpenCV其实是一个很好的开源计算机视觉库,但是由于在更新迭代的过程中,总会遇到适配的问题以及部分功能被舍弃的问题,所以有的时候也挺坑的。这一部分我就讲讲,我在做项目的过程中遇到的一些问题。
1. Opencv3 读取CSV文件 会少掉一列数据
不知道具体是OpenCV实现的不好,还是我没用好,总之用OpenCV读取csv文件,总会出现数据少一列的问题。也算是姻缘巧合吧,我在我的第二篇csdn博客里面就写到了如何使用OpenCV读取csv文件, Opencv3.2 读取CSV文件 当时就有人在我这篇博客下面评论说读取之后少了一列的问题,我当时也没当回事,现在真正在项目中遇到这个问题的时候才开始思考如何应对这个问题。有人说用二维数组可以解决这个问题,但是我也没有解决,我是读取之后在Mat数据中又复制了边界的一列数据,其实这个不太对。(希望能有技术大牛能解决我这个疑惑)
2. error LNK 2005 已经在 obj 中定义 错误
这个准确来说不是OpenCV的问题,而是代码书写规范问题。当你在调试程序的时候忽然出现类似于这样的报错:
-
“`C++
-
error LNK2005: ×××× (★★★★) 已经在 ※※※.obj 中定义
“`这样的报错经常是由于在头文件中定义了变量,将变量定义放到cpp文件中就行。具体的原因解释是:当你第一个使用这个头的cpp文件生成obj的时候,定义的变量在另一个使用了这个cpp文件的时候也会生成一个obj,这个变量会被重复定义这两个obj被另外的cpp给include了,就会引发重复定义。
-
3. OpenCV各种报错汇总
[opencv错误列表](https://blog.youkuaiyun.com/xibeichengf/article/details/50810712)
4. OpenCV存在的BUG
我的项目环境是vs2017 + opencv + Qt插件。经常会遇到鼠标停留在Mat(这个字符上)就会发生卡死的情况。具体原因是vs的lntelisense预览过多的信息时有BUG。Mat类(你转去定义会发现)上面有192行注释。
解决方案:右键Mat转到定义,然后把Mat类和上面一大截注释之间加上一个空格(直接删除也可以),这样就不会出现卡死的情况了。
5. opencv数据类型的坑
不多说了就是这个坑函数
calcCovarMatrix(entropyfilt_imag, covar1, E1, CV_COVAR_NORMAL | CV_COVAR_ROWS); //这是个坑 求出来的E2是 CV_64 类型
calcCovarMatrix(entropyfilt_imag, covar2, E2, CV_COVAR_NORMAL | CV_COVAR_COLS);
这个函数坑就坑在,你用CV_32F的数据类型放进去求,结果出来给你的是CV_64。所以要特别注意。
附上OpenCV相关的数据类型:
opencv矩阵元素数据类型
OpenCV中数据类型和常用数据类型对应关系