MFC:C/C++导入导出csv/excel文件

2 篇文章 0 订阅

本来以为把MFC里CFileDialog类的文件类型过滤器szFileters加个 *.xlsx 这样的类型,就可以导入导出xlsx了,但是完全是想当然啊(没试过你永远不知道结果是否是预期的!)

本文目的旨在用纯c/c++代码导入导出csv/excel等格式文件!

话不多说,首先创建一个类似这样的窗口,在文件菜单设置子菜单 导入导出

这里需要知道csv和excel文件的区别:csv文件是以逗号分隔的纯文本文件,而excel文件带有格式,这就决定了我们需要用不同方法导入或者导出。

所以在导入/导出csv文件时,直接用文件读写的方式就ok了。比如导入:

csv内容如下:

这里要记得,导入之前一定要把文件关了,不然无法导入,因为文件占用打不开!(浪费了我半天时间,以为程序写错了~~)

在最最开始的时候,我用了一个函数批量生成一些数据,放在了OnInitDialog()函数里,rowPitch是列间距,我设置的70

	//初始化列名
	mList.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);

	colCount = COL_END;	                //列数
	rowCount = 25;				//行数
	colName.push_back(_T("序号"));
	colName.push_back(_T("名字"));
	colName.push_back(_T("年龄"));
	colName.push_back(_T("住址"));
	colName.push_back(_T("工资"));

	for(int i = 0; i < colCount; i++)
		mList.InsertColumn(i,colName[i],LVCFMT_CENTER,rowPitch);

	//初始化列表信息
	CString rowName;//行名
	for(int row = 0; row < rowCount; row++){
		//添加一行
		rowName.Format(_T(" %d"),row+1);
		mList.InsertItem(row,rowName);//行名为序号,又因为从第二行开始计数
		//为添加的行插入数据
		mList.SetItemText(row,COL_NAME,_T("张三丰"));
		mList.SetItemText(row,COL_AGE,_T("99"));
		mList.SetItemText(row,COL_ADDRESS,_T("北京"));
		mList.SetItemText(row,COL_SALARY,_T("99000"));
	}

当然,如果是导入的话,这些数据都没有用,所以先清空一下,这里需要注意,要把列名也清空掉!  不然插入数据的时候,会出现两行列名!

实现代码如下:

void FileDlg::OnBnClickedClearList()
{
	//清空列表
	mList.DeleteAllItems();//清空list列表
	colName.clear();//清空原有列名容器
	while(mList.DeleteColumn(0));//清空列
	mList.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
}

接着是导入导出了,比如导入csv文件到程序里,实现如下,

//导入csv文件到程序中
void FileDlg::OnImport()
{
	CFileDialog fileOpenDlg(TRUE,_T("*.xlsx"),NULL,OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,
		_T("csv file(*.csv)|*.csv|Microsoft Excel 工作表(*.xlsx)|*.xlsx|All files (*.*)|*.*||"),NULL);
	fileOpenDlg.m_ofn.lpstrTitle = _T("Open File");
	if(fileOpenDlg.DoModal() == IDOK)//导入对话框打开
	{
		CString szLine;
		CStdioFile file;
		if(file.Open(fileOpenDlg.GetPathName(),CFile::modeRead))//打开选中的文件
		{
			//清空列表
			mList.DeleteAllItems();//清空list列表
			colName.clear();//清空原有列名容器
			while(mList.DeleteColumn(0));//清空列
			mList.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);

			//获取文件首行de列名,并插入到list列表
			file.ReadString(szLine);//把文件中列名那一行读走
			if(szLine.GetLength() > 2)
			{
				CStringArray strColName;
				SplitString(szLine,',',strColName);//获取文件中首行内容,去掉分隔符‘,’
				for(int startRow = 0;startRow < strColName.GetCount(); startRow++)
				{
					colName.push_back(strColName.GetAt(startRow));//保存首行到列名容器
					mList.InsertColumn(startRow,strColName.GetAt(startRow),LVCFMT_CENTER,rowPitch);//插入首行列名到list列表
				}
				colCount = colName.size();//记录总列数
			}

			int startRow = 0;
			while(file.ReadString(szLine))//读取一行文本到szLine,遇到回车换行符停止读取
			{
				if(szLine.GetLength() > 2)//每行读取大于两个字符(一个逗号一个回车,剩下是内容)
				{
					CStringArray strArr;//保存从文件读取到的每行的信息,首行除外
					SplitString(szLine, ',', strArr);//分割此行,把每个字符串添加到字符串数组
					mList.InsertItem(startRow,strArr.GetAt(0));//插入行名(即读到的每行的第一个字符串)
					for(int j = 1; j < strArr.GetCount(); j++)
					{
						mList.SetItemText(startRow,j,strArr.GetAt(j));//每行每列的具体信息
					}
					startRow++;
				}
				rowCount++;//记录总行数
			}
			file.Close();
		}
	}
}

对应的函数都可以查到。这里当然我这里有个分割字符串函数,是自定义的:

SplitString()函数:功能是把带有指定分隔符的字符串分割,并加入到字符串数组里


int FileDlg::SplitString(const CString str, char split, CStringArray &strArray)
{
	strArray.RemoveAll();
	CString strTemp = str;
	int iIndex = 0;
	while (1)
	{
		iIndex = strTemp.Find(split);//分隔符位置确定,下标从0开始计算
		if(iIndex >= 0)
		{
			strArray.Add(strTemp.Left(iIndex));//把分隔符左边iIndex个字符保存到strArray
			strTemp = strTemp.Right(strTemp.GetLength()-iIndex-1);//把分隔符右边剩下的字符保存到strTemp
		}
		else
		{
			break;
		}
	}
	strArray.Add(strTemp);

	return strArray.GetSize();
}

导出就简单了,因为导出不同格式文件方法不同,所以我把方法单独列出来,在导出模块里是这样分类的:比较文件后缀来调用不同的导出模式

        //获取的文件名后缀为csv,保存为csv格式
        CString ext = fileName.Right(3);
        if(!ext.CompareNoCase(_T("csv")))
        {
            ExportAsCSV(fileName);
        }
        
        //获取的文件名后缀为xlsx格式,保存为xlsx格式
        ext = fileName.Right(4);
        if(!ext.CompareNoCase(_T("xlsx")))
        {
            ExportAsXLSX(fileName);
        }

导出模块详情代码如下,

//导出文件模块
void FileDlg::OnExport()
{

	//初始化 保存文件对话框 模块
	TCHAR szFilters[]= _T("csv file(*.csv)|*.csv|Microsoft Excel 工作表(*.xlsx)|*.xlsx|All Files (*.*)|*.*||");//保存的格式种类
	CFileDialog fileDlg(
			FALSE,  
			_T("*.csv"),
			_T("csv file"),
			OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, 
			szFilters);
	CString fileName = _T("data.csv");//默认保存文件名
	fileDlg.GetOFN().lpstrFile = fileName.GetBuffer(MAX_PATH);//获取在文件对话框修改后的文件名
	
	//打开保存文件的对话框
	if(fileDlg.DoModal() == IDOK)
	{
		fileName = fileDlg.GetOFN().lpstrFile;//输入文件名,点击保存后获取的该文件名...
		
		//获取的文件名后缀为csv,保存为csv格式
		CString ext = fileName.Right(3);
		if(!ext.CompareNoCase(_T("csv")))
		{
			ExportAsCSV(fileName);
		}
		
		//获取的文件名后缀为xlsx格式,保存为xlsx格式
		ext = fileName.Right(4);
		if(!ext.CompareNoCase(_T("xlsx")))
		{
			ExportAsXLSX(fileName);
		}
		
	}
	else
	{
		CString str;
		str.Format(_T("打开导出模块失败"));
		MessageBox(str);
	}
	fileName.ReleaseBuffer();
}

这里的两个函数   ExportAsCSV(fileName);  ExportAsXLSX(fileName); 导出不同的格式,

首先是 ExportAsCSV(fileName),导出csv格式的文件,这里完全是文件读写,没什么好说的,实现如下,


//导出文件为.csv格式
BOOL FileDlg::ExportAsCSV(CString& fileName){
	FILE* pFile = fopen(fileName.GetBuffer(0),_T("w"));
	if(!pFile){
		CString str;
		str.Format(_T("file open failed,error code:%d"),WSAGetLastError());
	}
	//导出列名
	CString allColName;//保存每一列的列名信息
	for(int i = 0;i < COL_END;i++){
		allColName += colName[i];
		allColName += _T(",");
	}
	allColName += _T("\n");
	fwrite(allColName.GetBuffer(0), 1, allColName.GetLength(), pFile);

	//导出内容
	POSITION pos;
	pos = g_pMainDlg->mList.GetFirstSelectedItemPosition();
	CString totalInfo = _T("");
	if (pos == NULL)
	{			// 导出所有
		int rowCount = mList.GetItemCount();
		for(int i = 0; i < rowCount; i++) { // 处理行	
			totalInfo = _T("");	
			for(int j = 0;j < COL_END; j++){ // 处理列
				//每一列的数据保存起来
				totalInfo += mList.GetItemText(i,j);				
				if(j != COL_END){
					totalInfo += _T(",");
				}
			}end inner for
			totalInfo += _T("\n");
			fwrite(totalInfo.GetBuffer(0),1,totalInfo.GetLength(),pFile);		
		}
	}//end if pos == Null
	fclose(pFile);
	return TRUE;
}

而导出excel格式的文件,需要用到一些excel接口,这里网上有很多方法,我看的头大,有用com的,有用ole的,开始就有点蒙。但是后来想明白了,不管是什么方法,流程始终是:

打开Excel应用(CApplication类) -> 打开一个工作簿(CWorkbook类) -> 打开一个工作表(CWorksheet类) ->  找到单元格(CRange类)

可能叫法不同吧,但就是这么个流程。解释一下这几个概念和对应的类:

CApplication类 :好理解,就是excel应用,但是打开这个应用,得先加载excel的头文件,这些头文件里定义了这些类,怎么打开呢,可以看这几个文章:

https://blog.csdn.net/chanshibing/article/details/86304169

https://blog.csdn.net/thanklife/article/details/87447804

步骤如下:

1 首先创建MFC,打开类向导,添加类(类型库中的MFC类),选择文件位置(EXCEL.exe),添加类需要的类(参考下面代码的头文件),会生成对应的头文件(CWorkbook.h、CHyperlinks.h…)

2 注视掉每个头文件的如下部分  

//#import "C:\\Program Files\\Microsoft Office\\Office15\\EXCEL.EXE" no_namespace
CWorkbook类:相当于单个的工作区是你创建、删除、操作表的地方,但首先得先创建一个容纳工作区的容器,就是CWorkbooks类

CWorksheet类:相当于一个工作表,是你操作单元格的地方,但首先你得先创建一个容纳工作表的容纳,就是CWorksheets类

单个的一个sheet表

 

工作簿容器里有一个工作簿,一个工作簿里有一个工作表容器,一个工作表容器里有一个工作表,基于此理解,再看下面的代码就很容易了:

books = app.get_Workbooks();                //获取book容器    
book = books.Add(vtMissing);                //打开一个book    
sheet = book.get_ActiveSheet();             //打开一个sheet表

CRange类:相当于单元格坐标类,类似于CPoint,是你读写单元格的地方,但首先你得获得一个Excel独有的坐标 如A3(A列的第3行)

这需要用到函数get_Range(_variant_t)和put_Value2(_variant_t),使用方式是

1 先获取单元格坐标

CRange range  = sheet.get_Range(_variant_t("A1"),_variant_t("A1"));

2 往里面赋值

range.put_Value2(_variant_t("输入的值"));

这里有个疑惑,就是看函数名字是Range,应该是一个区域,但是吧,这样用的话,就只是对一个单元格读写。我在测试的时候,1000条数据大概就要

性能有点不理想。应该有选中一片区域,然后直接用二维数组往里面写的方法,但是网上查不到put_Value2()的用法,今晚折腾也没有尝试。这个算以后需要弄明白的一个问题吧。

注释是其他导出的方法,我没有删除。代码如下:

//导出文件为.xlsx格式
BOOL FileDlg::ExportAsXLSX(CString& fileName)
{
	CoInitialize(NULL);//初始化COM,最后还有CoUninitialize
	//COleVariant vtMissing((long)DISP_E_PARAMNOTFOUND, VT_ERROR);

	CApplication app;//excel应用
	CWorkbooks books;//全部工作簿
	CWorkbook book;  //工作簿容器
	CWorksheets sheets; //工作表容器
	CWorksheet sheet;//单个工作表
	CRange range;	 //可编辑区域
	CFont0 font;	 //字体
	LPDISPATCH lpDisp = NULL;

	//启动excel
	if(!app.CreateDispatch(_T("EXCEL.application")))
	{
		MessageBox(_T("未安装Excel!"));
		return false;
	}

	//显示版本
	CString strExcelVersion = app.get_Version();
	int iStart = 0;
	strExcelVersion = strExcelVersion.Tokenize(_T("."),iStart);
	if (_T("11") == strExcelVersion)
	{
		MessageBox(_T("当前版本是Excel 2003!"));
	}
	else if (_T("12") == strExcelVersion)
	{
		MessageBox(_T("当前版本是Excel 2007!"));
	}
	else
	{
		//MessageBox(_T("当前版本是其他版本!"));
	}
	
	//设置表
	app.put_Visible(TRUE);						//操作时可见
	app.put_UserControl(FALSE);			
	app.put_DisplayFullScreen(FALSE);	
	app.put_DisplayAlerts(FALSE);

	books = app.get_Workbooks();				//获取book容器
	book = books.Add(vtMissing);				//打开一个book
	sheet = book.get_ActiveSheet();				//打开一个sheet表

	//设置导出后第一行的值,即列名
	CString strHead[] = {"A","B","C","D","E","F","G","H","I","G","K","L","M","N","O",
		"P","Q","R","X","T","U","V","W","X","Y","Z"};
	for(int i = 0; i < COL_END;i++)
	{
		CString cell;
		cell.Format("%s%d",strHead[i].GetBuffer(0),1);
		range  = sheet.get_Range(_variant_t(cell),_variant_t(cell));//首行单元格坐标如(A1,A1)(B1 B1)
		range.put_Value2(_variant_t(colName[i]));
		
	}
	
	//读取表数据到内存
	vector<vector<CString> > vecAllData(rowCount,vector<CString>(colCount));//变长二维数组保存全部单元格数据
	for(int row = 0; row < rowCount;row++)
	{
		for(int col = 0; col < colCount;col++)
		{
			vecAllData[row][col] = mList.GetItemText(row,col);
		}
	}
	
	//插入数据到文件
	clock_t startTime,finishTime;
	startTime = clock();
	try
	{
		for(int row = 0; row < rowCount;row++)
		{
			for(int col = 0; col < colCount; col++)
			{
				CString cell;
				cell.Format("%s%d",strHead[col].GetBuffer(0),row+2);
				range  = sheet.get_Range(_variant_t(cell),_variant_t(cell));//首行单元格坐标如(A1,A1)(B1 B1)
				range.put_Value2(_variant_t(vecAllData[row][col]));
			}
		}
		finishTime = clock();
		CString time;
		time.Format("已成功导出到Excel文件,用时 %g 秒!",(double)(finishTime-startTime)/CLOCKS_PER_SEC);
		MessageBox(time);
	}
	catch(...)
	{
		MessageBox(_T("插入数据到xlsx文件失败!!"));
	}

	//保存文件
	book.SaveAs(COleVariant(fileName),vtMissing,
		vtMissing,vtMissing,vtMissing,vtMissing,(long)0,
		vtMissing,vtMissing,vtMissing,vtMissing,vtMissing);
	
	sheet.ReleaseDispatch();
	sheets.ReleaseDispatch();
	book.ReleaseDispatch();
	books.ReleaseDispatch();
	app.Quit();
	app.ReleaseDispatch();
	return TRUE;
}

 


//导入.xlsl文件
BOOL FileDlg::ImportAsXLSX(CString& fileNameOfFullPath)
{
	CoInitialize(NULL);//初始化COM,最后还有CoUninitialize
	COleVariant vOpt((long)DISP_E_PARAMNOTFOUND, VT_ERROR);

	CApplication app;//excel应用
	CWorkbooks books;//全部工作簿
	CWorkbook book;  //工作簿容器
	CWorksheets sheets; //工作表容器
	CWorksheet sheet;//单个工作表
	CRange range;	 //可编辑区域
	//CFont0 font;	 //字体
	LPDISPATCH lpDisp = NULL;

	//启动excel
	if(!app.CreateDispatch(_T("EXCEL.application")))
	{
		MessageBox(_T("未安装Excel!"));
		return false;
	}

	//显示版本
	CString strExcelVersion = app.get_Version();
	int iStart = 0;
	strExcelVersion = strExcelVersion.Tokenize(_T("."),iStart);
	if (_T("11") == strExcelVersion)
	{
		MessageBox(_T("当前版本是Excel 2003!"));
	}
	else if (_T("12") == strExcelVersion)
	{
		MessageBox(_T("当前版本是Excel 2007!"));
	}
	else
	{
		MessageBox(_T("当前版本是其他版本!"));
	}
	
	//设置表
	app.put_Visible(false);						//操作时可见
	app.put_UserControl(false);			
	app.put_DisplayFullScreen(false);	
	app.put_DisplayAlerts(false);

	//打开指定的xlsx文件
	try
	{
		books = app.get_Workbooks();				//获取book容器
		book = books.Open(fileNameOfFullPath, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt);
	}
	catch(...)
	{
		AfxMessageBox("打开xlsx文件失败!可能因为文件正在使用中?");
		return false;
	}

	//清空列表
	OnBnClickedClearList();

	//设置导入后第一行的索引
	CString strHead[] = {"A","B","C","D","E","F","G","H","I","G","K","L","M","N","O",
		"P","Q","R","X","T","U","V","W","X","Y","Z"};
	//打开当前活动的sheet,准备读取数据
	sheet = book.get_ActiveSheet();
	//插入列名
	for(int i = 0; i < COL_END-1;i++)
	{
		CString cell;
		cell.Format("%s%d",strHead[i].GetBuffer(0),1);//得到'A2'这样的坐标
		range  = sheet.get_Range(_variant_t(cell),_variant_t(cell));//首行单元格坐标如(A1,A1)(B1 B1)
		CString firstColName = range.get_Value2();
		colName.push_back(firstColName);//保存每列的名字	
		mList.InsertColumn(i,colName[i],LVCFMT_CENTER,rowPitch);//插入首行列名到list列表
		colCount++;
	}
	//从excel表拷贝数据到程序
	try{
		CString cell, data;
		for(int row = 0;;row++)
		{
			// 1 从A2单元格往下遍历,有值就表示还有数据,继续循环,否则退出循环
			cell.Format("A%d",row+2);
			range = sheet.get_Range(_variant_t(cell),_variant_t(cell));
			if((data = range.get_Value2()).IsEmpty())
			{
				break;
			}
			// 2 插入行名(即读到的每行的第一个字符串)
			mList.InsertItem(row,data);
			for(int col = 0; col < colName.size(); col++)
			{
				cell.Format("%s%d",strHead[col].GetBuffer(0), row+2);//如遍历到A3行,A3 B3 C3 D3...的数据都要拷贝
				range = sheet.get_Range(_variant_t(cell),_variant_t(cell));
				data =range.get_Value2();
				if(!data.IsEmpty())
					mList.SetItemText(row,col,data);//每行每列的具体信息
				else
					mList.SetItemText(row,col,"");  //读到的列为空时
			}		
			rowCount++;
		}
	}
	catch(...)
	{
		MessageBox(_T("插入数据到xlsx文件失败!!"));
		return false;
	}

	//关闭文件
	books.Close();
	
	sheet.ReleaseDispatch();
	sheets.ReleaseDispatch();
	book.ReleaseDispatch();
	books.ReleaseDispatch();
	app.Quit();
	app.ReleaseDispatch();
	return true;
}

 

上面是csv格式   下面是xlsx格式

如果你看完了,那一定是真爱,点个赞留个言吧~欢迎指出问题

链接如下,使用大于等于vs2010的版本可以正常打开

链接:https://pan.baidu.com/s/1qqrfpDcFxrkK5mR0_xIKFA 
提取码:leyz 
 

  • 10
    点赞
  • 36
    收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 6

打赏作者

__Christopher

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值