Qt开发一个小软件记录

1、简述

 本博客主要记载开发过程中的不熟悉的Qt小知识点。本软件是上海火克公司开发的一款应用软件,软件实现的功能主要有三个部分,1是项目管理,2是计算管道覆盖比,3是自动生成word报告。

2、内容记录

2.1 QStatusBar的使用

效果图:

代码:

	// Initial the statusBar
	ui.statusBar->setStyleSheet(QString("QStatusBar::item{border: 0px}"));
	QLabel* label0 = new QLabel("User: ");
	label0->setAlignment(Qt::AlignLeft);
	ui.statusBar->addPermanentWidget(label0,0);    // 第二个参数为所占比例

	m_qUserName = new QLabel;
	m_qUserName->setAlignment(Qt::AlignLeft);
	ui.statusBar->addPermanentWidget(m_qUserName,5);

	QLabel* label1 = new QLabel("mm");
	ui.statusBar->addPermanentWidget(label1,1);

 2.1.1 QAction

说明:由于在使用QMenu的时候,发现其不能如同按钮一样,百思不得其解。最后请教师兄,师兄教我直接对statusBar添加QAction,槽函数响应triggered()信号。而且经过测试,发现QMenu并不具有triggered()信号。

代码:

	QAction *pProjects = new QAction("项目",nullptr);
	QAction *pCustomers = new QAction("客户", nullptr);
	QAction *pTemplates = new QAction("模板", nullptr);
	QAction *pVersion = new QAction("版本", nullptr);
	QAction *pAbout = new QAction("关于", nullptr);
	ui.menuBar->addAction(pProjects);
	ui.menuBar->addAction(pCustomers);
	ui.menuBar->addAction(pTemplates);
	ui.menuBar->addAction(pVersion);
	ui.menuBar->addAction(pAbout);

	connect(pCustomers, SIGNAL(triggered()), this, SLOT(OnBtCustomClicked()));
	connect(pTemplates, SIGNAL(triggered()), this, SLOT(OnBtTemplateClicked()));

 2.2 设置弹窗的模态

功能:阻止父窗口

代码:


	// m_pAddName是widget类型的子窗口
	m_pAddName->setWindowFlags( this->windowFlags() | Qt::Dialog);
	m_pAddName->setWindowModality(Qt::ApplicationModal); // 设置为模态

2.3 获取计算机用户名

代码:

	QString userName = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
	userName = userName.section("/", -1, -1);

2.4 QDoubleSpinBox的响应事件

功能:实现QDoubleSpinBox的键盘修改响应事件和点击增加减少的响应事件

代码:

	ui.m_dsbWidth->setKeyboardTracking(false);	// 键入数据失去焦点后才响应,如不添加,则一变就响应
	connect(ui.m_dsbWidth, SIGNAL(valueChanged(double)), this, SLOT(onDSBWidthValueChanged(double)));
	ui.m_dsbHeight->setKeyboardTracking(false);

2.5 QPainter

移动坐标轴,缩放坐标系,使作图区域填满并位于frame的中心。

代码:

	QPainter painter(this);
	painter.setRenderHint(QPainter::Antialiasing);
	painter.translate(iCanvasWidth / 2 + dCanvasX, iCanvasHeight / 2 + dCanvasY);	// 将原点设为frame的中心

	double dEdgeWidth, dEdgeHeight;		// 矩形边框
	double scale;	// 坐标轴缩放因子
	dEdgeWidth = dShapeWidth + 40;
	dEdgeHeight = dShapeHeight + 40;
	double scaleWidth = (iCanvasWidth - 20) / dEdgeWidth;
	double scaleHeight = (iCanvasHeight - 20) / dEdgeHeight;
	scale = std::min(scaleWidth, scaleHeight);

	painter.scale(scale, scale);		// 缩放坐标系

2.6 QPainterPath

画槽形环

效果图:

代码:

	QPainterPath path;
	path.addRoundedRect(-dShapeWidth / 2, -dShapeHeight / 2, dShapeWidth,
		dShapeHeight, dShapeRadius, dShapeRadius);	// 外轮廓
	path.addRoundedRect(-dShapeWidth / 2 - 10, -dShapeHeight / 2 - 10, dShapeWidth + 20,
		dShapeHeight + 20, dShapeRadius + 10, dShapeRadius + 10);	// 内轮廓
	painter.fillPath(path, Qt::red);

2.7 圆心定位算法

该算法是一道数学难题,很难得到一个通用的圆心坐标公式。经过与导师讨论,想到另外一种解决问题的方法。

解题思路:利用图像处理的方法遍历确定圆心

1.创建一个Mat类的灰度图,初始化为255。

2.给矩形槽区域外的像素赋值0。0像素代表不合理的位置,或者已存在圆。

3.由下向上遍历,并同时由中间向两边遍历。以(i,j)为中心的圆形区域,若没有0像素,则记录该位置。

由此方式可近似确定圆心坐标,其中精度由实际尺寸与像素比决定,即分辨率决定。

 贴出一张结果图:输出圆心坐标,bool类型的是否溢出:0-溢出 1-未溢出

源码:

calcularCenter.h

#pragma once
#include <opencv2/opencv.hpp>
#include <vector>
using namespace cv;
using namespace std;

struct CableData
{
	int iAmount;
	double dOutDiameter;
};

// 获取电缆圆心坐标
bool GetPoints(const vector<CableData>& vData, vector<Point2f>& vPoints, double dWidth, double dHeight, double dRadius);
// 添加一个圆,传出圆心坐标
bool AddCircle(Mat& image, Point2i &point, const int& radius);
// 将一个添加的圆涂黑
void PaintBlack(Mat& image, const Point2i& point, const int& diameter);
// 圆心在point的圆是否存在像素为0的点,true:没有像素为0的点,false:有像素为0的点
bool IsContain0(const Mat& image, const Point2i& point, const int& diameter);
// 计算2点之间距离的平方
int GetDistance(const Point2i& p1, const Point2i& p2);

calcularCenter.cpp

#include "calcularCenter.h"

bool GetPoints(const vector<CableData>& vData, vector<Point2f>& vPoints, double dWidth, double dHeight, double dRadius)
{
	double scale;
	if (dWidth > dHeight)
		scale = 1000 / dWidth;
	else
		scale = 1000 / dHeight;

	int nRow = ceil(dHeight * scale);
	int nCol = ceil(dWidth * scale);
	int nRadius = ceil(dRadius * scale);
	int nRadius_2 = ceil(dRadius * scale * dRadius * scale);
	Mat src(nRow,nCol,CV_8UC1,Scalar::all(255));

	if (!src.data)
	{
		cout << "error!" << endl;
		return false;
	}

	//将圆角矩形外面涂黑
	for(int i = 0; i < nRow; i++)
		for (int j = 0;  j < nCol; j++)
		{
			if (i < nRadius && j < nRadius && GetDistance(Point2i(j, i), Point2i(nRadius, nRadius)) > nRadius_2)
			{
				src.data[i*nCol + j] = 0;
			}
			else if (i > nRow - nRadius && j < nRadius && GetDistance(Point2i(j, i), Point2i(nRadius, nRow - nRadius - 1)) > nRadius_2)
			{
				src.data[i*nCol + j] = 0;
			}
			else if (i < nRadius && j > nCol - nRadius && GetDistance(Point2i(j, i), Point2i(nCol - nRadius - 1, nRadius)) > nRadius_2)
			{
				src.data[i*nCol + j] = 0;
			}
			else if (i > nRow - nRadius && j > nCol - nRadius && GetDistance(Point2i(j, i), Point2i(nCol - nRadius - 1, nRow - nRadius - 1)) > nRadius_2)
			{
				src.data[i*nCol + j] = 0;
			}
			else
			{

			}
		}

	bool flag = true;
	// 逐个添加圆
	int size = vData.size();
	int k;
	for (k = size - 1; k >= 0; k--)
	{
		int radius = floor(vData[k].dOutDiameter * scale / 2);
		for (int m = 0; m < vData[k].iAmount; m++ )
		{

			Point2i point;
			if (AddCircle(src, point, radius))
			{
				Point2f _point;
				_point.x = point.x / scale - dWidth / 2;
				_point.y = point.y / scale - dHeight / 2;
				vPoints.push_back(_point);
			}
			else
				flag = false;
		}
	}
	imshow("src",src);
	imwrite("1.bmp", src);
	waitKey(0);
	return flag;
}

/****************************************************************************
 *	函数:AddCircle
 *  功能:添加一个圆,成功返回true; 不成功,则表示溢出,返回false.传出圆心坐标
 *  参数:image,已确定圆的图像,point,传出的圆心坐标, radius,添加圆的半径
 *  返回值:bool
 ****************************************************************************/
bool AddCircle(Mat & image, Point2i& point, const int & radius)
{
	int nRow = image.rows;
	int nCol = image.cols;
	for (int i = nRow - 1; i > 0; i--)
	{
		for (int j = nCol / 2 - 1; j > 0; j--)
		{
			if (IsContain0(image, Point2i(j, i), radius))
			{
				PaintBlack(image, Point2i(j, i), radius);
				point = Point2i(j, i);
				return true;
			}
		}
		for (int j = nCol / 2; j < nCol; j++)
		{
			if (IsContain0(image, Point2i(j, i), radius))
			{
				PaintBlack(image, Point2i(j, i), radius);
				point = Point2i(j, i);
				return true;
			}
		}
	}
	return false;
}

/****************************************************************************
 *	函数:PaintBlack
 *  功能:将添加的圆涂黑
 *  参数:image,被修改的图片;point,圆心坐标;radius,半径大小
 *  返回值:void
 ****************************************************************************/
void PaintBlack(Mat & image, const Point2i & point, const int & radius)
{
	int nRow = image.rows;
	int nCol = image.cols;
	int iR_2 = radius * radius;
	for (int i = point.y + radius; i >= point.y - radius; i--)
		for (int j = point.x + radius; j >= point.x - radius; j--)
		{
			if (GetDistance(Point2i(j, i), point) <= iR_2)
			{
				image.data[i*nCol + j] = 0;
			}
		}
}

/****************************************************************************
 *	函数:IsContain0
 *  功能:判断圆心在该点的圆是否相交
 *  参数:image,已确定的圆的图像; point,圆心; radius,半径大小
 *  返回值:bool
 ****************************************************************************/
bool IsContain0(const Mat & image, const Point2i & point, const int & radius)
{
	int nRow = image.rows;
	int nCol = image.cols;
	int iR_2 = radius * radius;
	if (point.x - radius < 0 || point.x + radius > nCol - 1
		|| point.y - radius < 0 || point.y + radius > nRow - 1)
		return false;

	for (int i = point.y + radius; i >= point.y - radius; i--)
		for (int j = point.x + radius; j >= point.x - radius; j--)
		{
			if (GetDistance(Point2i(j, i), point) <= iR_2)
			{
				if (image.data[i*nCol + j] == 0)
					return false;
			}
		}
			
	return true;
}

/****************************************************************************
 *	函数:GetDistance
 *  功能:计算2点距离
 *  参数:p1,p2像素坐标
 *  返回值:double
 ****************************************************************************/
int GetDistance(const Point2i & p1, const Point2i & p2)
{
	return  (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
}

 main.cpp

#include "calcularCenter.h"

int main()
{
	vector<CableData> vCableData;
	vector<Point2f> vPoints;
	vector<Point2f> vPoints1;
	bool flag;

	CableData data;
	data.iAmount = 3;
	data.dOutDiameter = 5;
	vCableData.push_back(data);
	data.iAmount = 3;
	data.dOutDiameter = 20;
	vCableData.push_back(data);


	flag = GetPoints(vCableData, vPoints, 100, 100, 50);
	for (int i = 0; i < vPoints.size(); i++)
	{
		cout << "(" << vPoints[i].x << ", " << vPoints[i].y << ")" << endl;
	}
	cout << int(flag)<< endl;
	system("pause");
	return 0;
}

反思:

由于功能需要,在已插入小圆后,需要求用标准直径的小圆填充大圆的小圆个数。最初使用的方法是,直接在getPoints函数后面添加对溢出的判断,若未溢出,则对标准直接的圆addCircle(),nFiller数字为记录的数量。代码如下:

	// 逐个添加圆
	for (int k = size - 1; k >= 0; k--)
	{
		int tmp = GetSleeveDiameter(vData[k].dOutDiameter);
		int radius = floor(tmp * scale / 2);
		for (int m = 0; m < vData[k].iAmount; m++)
		{
			Point2i point;
			if (AddCircle(src, point, radius))
			{
				QPointF _point;
				_point.rx() = point.x / scale - dWidth / 2;
				_point.ry() = point.y / scale - dHeight / 2;
				vPoints.push_back(_point);
				vDiameter.push_back(vData[k].dOutDiameter);
			}
			else
				flag = false;
		}
	}
	// add filler sleeves
	//if (flag)
	//{
	//	Point2i point;
	//	int radius = floor(27 * scale / 2);
	//	while (AddCircle(src,point, radius))
	//	{
	//		nFiller[0]++;
	//	}
	//	radius = floor(19 * scale / 2);
	//	while (AddCircle(src, point, radius))
	//	{
	//		nFiller[1]++;
	//	}
	//	//radius = floor(12 * scale / 2);
	//	//while (AddCircle(src, point, radius))
	//	//{
	//	//	nFiller[2]++;
	//	//}
	//}

其结果导致每一次刷新耗费大量事件计算十分卡顿。

总结

 由于paintEvent()是一个不断重复执行的函数,如果每一次主要数据没有变化的情况下paintEvent()事件响应,都得执行该圆心的求解,十分耗时,界面卡顿。因此,要考虑在数据没有变化的情况下,直接利用上一次计算的结果,而不用重复计算。同理,对于求填充填充圆的个数时,这个数据只有在特定情况下才需要(本项目是在响应按钮save和report时),不必要对每次数据变化计算该值。最后解决的办法时将GetPoints函数中的Mat类型作为一个全局变量,在按钮save和report的槽函数添加计算该值的代码。如下:

	// add filler sleeves
	int a[2] = { 0 };
	if (m_isFill)
	{
		Point2i point;
		int radius = floor(27 * scale / 2);
		while (AddCircle(src,point, radius))
		{
			a[0]++;
		}
		radius = floor(19 * scale / 2);
		while (AddCircle(src, point, radius))
		{
			a[1]++;
		}
	}
	m_penetration.iFillerSleeve27Amount = a[0];
	m_penetration.iFillerSleeve19Amount = a[1];

将 PaintBlack()替换为opencv自带的函数 circle(image, Point2i(j, i), radius, Scalar(0), -1);

2.8 排序输出

在项目中,得到一个vector<struct>数据,struct包含多种数据,要按照A方式输出,当A相同时,按照B输出。为了实现该功能,我采用Map<int, Map<String, int>>的数据结构,将数据改造成该种类型,利用map对key的排序,然后遍历map输出数据。

部分代码:

QMap<int, QMap<QString, int>> mapInsertSleeves;
	int sizeOfPene = m_qvPenetrations.size();

	for (int i = 0; i < sizeOfPene; i++)
	{
		if (!mapInsertSleeves.contains(m_qvPenetrations[i].iFillerSleeve19Length))
		{
			mapInsertSleeves[m_qvPenetrations[i].iFillerSleeve19Length] = QMap<QString, int>();
		}
		QMap<QString, int> &_map = mapInsertSleeves[m_qvPenetrations[i].iFillerSleeve19Length];
		int sizeOfCable = m_qvPenetrations[i].qvCables.size();
		QString str;
		for (int j = 0; j < sizeOfCable; j++)
		{
			int d = PenetrationDetails::GetSleeveDiameter(m_qvPenetrations[i].qvCables[j].dOutDiameter);
			if (d == 12)
				str = "D12/6";
			else if (d == 15)
				str = "D15/8";
			else if (d == 17)
				str = "D17/10";
			else if (d == 19)
				str = "D19/12";
			else if (d == 21)
				str = "D21/14";
			else if (d == 23)
				str = "D23/16";
			else if (d == 27)
				str = "D27/19";
			else if (d == 31)
				str = "D31/23";
			else if (d == 35)
				str = "D35/27";
			else if (d == 41)
				str = "D41/31";
			else if (d == 46)
				str = "D46/36";
			else if (d == 52)
				str = "D52/42";
			else if (d == 58)
				str = "D58/48";
			else if (d == 64)
				str = "D64/54";
			else
				str = "D70/60";
			if (_map.contains(str))
			{
				_map[str] += m_qvPenetrations[i].qvCables[j].iAmount;
			}
			else
			{
				_map[str] = m_qvPenetrations[i].qvCables[j].iAmount;
			}
		}
	}

回顾:

昨天学习数据结构与算法一书时,无意中在某博客看到:

priority_queue<pair<int, int>, vector<pair<int, int>>, greater(pair<int, int>)>的结构,在first相等时,可以按second排序,可以应用到此处。

2.9 QFileDialog::getSaveFileName

保存文件到桌面,并设置默认的文件名。代码:

 

	QString defaultSaveName = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "./" + m_penetration.qstrPenetration + ".doc";
	QString outFileName = QFileDialog::getSaveFileName(this, QStringLiteral("请输入要保存的名字:"), defaultSaveName, "Microsoft Word 97-2003(*.doc);;Microsoft Word 2007-2013(*.docx)");
	if (outFileName.isEmpty()) {
		QMessageBox::warning(this, tr("警告"), tr("输入的文件名为空!"), QMessageBox::Ok);
		return;
	}
	Report(outFileName);

2.10 Qt 输出word

使用封装好的源码QWordDemo.zip,源码出处已遗忘,以后若想起补上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值