目录
一、前言
二、主要功能和技术点
1.主要功能
2.主要技术点
三、准备工作
1.SQLite数据库操作工具
2.SqLiteCpp第三方库
3.安装office导入Excel需要的接口
3.1具体步骤
四、主界面
1.自定义窗口背景
1.1消息映射
1.2选择背景图片
1.3绘制背景
1.4静态控件透明化
2.实现统计功能
2.1消息映射
2.2数据统计
3.实现保存、详情功能
3.1消息映射
3.2保存数据到Sqlite数据库
3.3显示详情窗口
4.实现导出Excel功能
4.1消息映射
4.2导出数据到excel中
五、源码
一、前言
之前的文章一直用的是Windows的API以及ATL的框架来绘制控件的,那本次使用MFC框架来做一下小工具。
二、主要功能和技术点
1.主要功能
1)支持对数据的存储、修改以及查询。
2)可以显示本周的数据详情。
3)支持导出本周的数据以及所有的数据为EXCEL文件。
4)支持自定义窗口背景图片。
2.主要技术点
Sqlite数据库的增删改查、GDI/GDI+绘制、EXCEL接口、控件自绘、正则表达式。
三、准备工作
1.SQLite数据库操作工具
详情见《基于Windows系统用C++做一个点名工具》文章。
2.SqLiteCpp第三方库
详情见《基于Windows系统用C++做一个点名工具》文章。
3.安装office导入Excel需要的接口
3.1具体步骤
1)右击【项目】--》 点击【添加】--》点击【新建项】
2)在弹出来的窗口中选择【MFC】-->选择【TypeLib中的MFC类】,点击添加。
3)在弹出来的窗口中的【实现接口的位置】中选择【文件】,再点击【...】在你的EXCEL.EXE所在的路径点击打开。
4)按【>】添加以下七个基础接口:
_Application、_Workbook、_Worksheet、Worksheets、Workbooks、Range、Font
点击确定,可以在头文件中看见已经已经添加完成:
5)分别打开这个七个头文件,将导入的代码注释掉。
同时把CFont类重新改一个名,因为std命名空间中也有相同的类,会冲突。
头文件名也改一下
6)在我们的程序中添加这七个头文件,编译。
7)如果编译报错,那么我们根据报错提示改一下头文件中的代码
①CRange中的VARIANT DialogBox() 改为 VARIANT _DialogBox()
②CWorksheets 和 CWorkbooks中的InvokeHelper方法如果缺少参数则用NULL填充
③出现未定义的标识符XmlMap,使用void代替。
注意:本文用到了单元格填充颜色,所以还需要按以上的方法添加Interior接口,头文件是<Cnterior.h>。
四、主界面
界面由两个窗口组成:输入窗口和数据展示窗口。
1)输入窗口由静态文本、编辑框、菜单栏以及按钮组成
2)数据展示窗口由静态文本、列表视图以及按钮组成
无论是Windows底层api还是基于mfc的框架,都是通过处理窗口消息来实现窗口以及控件的绘制。mfc框架只是在Windows底层api的基础上对其进行了封装而已,所以通过mfc封装的宏来映射窗口消息即可。因为mfc封装了大量的消息映射函数,所以大部分都不需要自己手动映射,我们只需要在控件的属性中点击【黄色小闪电的标志】,然后在下方选择要映射函数即可。
1.自定义窗口背景
1.1消息映射
映射窗口的WM_PAINT消息,进行背景绘制。
BEGIN_MESSAGE_MAP(CAccountDlg, CDialogEx)
ON_WM_PAINT()
END_MESSAGE_MAP()
1.2选择背景图片
//设置筛选器
TCHAR szFilter[] = _T("BMP图片(*.bmp)|*.bmp*|JPG图片(*.jpg)|*.jpg*|PNG图片(*.png)|*.png*|所有文件(*.)|*.*|");
CFileDialog fileDlg(TRUE, _T("doc"), _T(""), OFN_OVERWRITEPROMPT, szFilter, this);
// 构造打开文件对话框
if (IDOK == fileDlg.DoModal())
{//获取选择文件的路径m_bkPicPth = fileDlg.GetPathName();SaveToPathDB(DB_FULLPATH, m_bkPicPth);this->InvalidateRect(0, 1);this->UpdateWindow();
}
1.3绘制背景
使用GDI+用图片来绘制背景,初始状态下默认浅蓝色背景。
PAINTSTRUCT ps;CDC* pDC = BeginPaint(&ps);Gdiplus::Graphics gh(pDC->GetSafeHdc());if (m_bkPicPth.GetLength() > 0){Gdiplus::Image* image = Gdiplus::Image::FromFile(m_bkPicPth.GetString());gh.DrawImage(image, Gdiplus::RectF(ps.rcPaint.left, ps.rcPaint.top,ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top));}else{Gdiplus::SolidBrush brush(Gdiplus::Color(120, 200, 250));gh.FillRectangle(&brush, Gdiplus::RectF(ps.rcPaint.left, ps.rcPaint.top,ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top));}
1.4静态控件透明化
我们如果自绘了背景,那么静态文本的留白会让界面不那么美观,所以我们需要对静态文本控件的背景透明化。那么就需要映射处理WM_CTLCOLORSTATIC消息。但是在mfc中把所有控件的绘制背景的消息都封装成了ON_WM_CTLCOLOR宏,所以我们映射该宏,在消息处理函数中对其进行判断处理即可。
BEGIN_MESSAGE_MAP(CAccountDlg, CDialogEx)ON_WM_CTLCOLOR()
END_MESSAGE_MAP()HBRUSH CAccountDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{if (nCtlColor == CTLCOLOR_STATIC){UINT id = ::GetDlgCtrlID(pWnd->GetSafeHwnd());if(id == IDC_TODAYEDIT)return (HBRUSH)GetStockObject(WHITE_BRUSH);else{SetTextColor(pDC->GetSafeHdc(), RGB(255, 255, 255));SetBkColor(pDC->GetSafeHdc(), RGB(255, 255, 255));SetBkMode(pDC->GetSafeHdc(), TRANSPARENT);return (HBRUSH)GetStockObject(NULL_BRUSH);}}return (HBRUSH)GetStockObject(WHITE_BRUSH);
}
2.实现统计功能
2.1消息映射
响应编辑框的内容改变,映射EN_CHANGE或者EN_UPDATE消息。
BEGIN_MESSAGE_MAP(CAccountDlg, CDialogEx)ON_EN_UPDATE(IDC_BREAKFASTEDIT, &CAccountDlg::OnEnUpdateBreakfastedit)
END_MESSAGE_MAP()
2.2数据统计
//判断字符串是否为合法数字字符串
static bool IsNumCharacterStr(CString str)
{// 定义一个正则表达式,仅匹配正整数或正浮点数std::regex pattern("^\\d+(\\.)?\\d{0,2}$");std::string numStr = WCharToString(str.GetString());return std::regex_match(numStr, pattern);
}TCHAR text[100] = {};
SendDlgItemMessage(itemID, WM_GETTEXT, 100, (LPARAM)text);
if (IsNumCharacterStr(text))
{num = _wtof_l(text, NULL);
}
else
{SendDlgItemMessage(itemID, WM_SETTEXT, NULL, (LPARAM)L"0");num = 0;
}m_totalNum = m_breakfastNum + m_lunchNum + m_dinnerNum +m_fruitNum + m_elecProductNum + m_dailyProductNum +m_relaxationNum + m_medicalNum + m_petNum + m_educationNum +m_trafficNum + m_rentNum + m_otherNum;CString totalStr;
totalStr.Format(L"%.2lf", m_totalNum);
SendDlgItemMessage(IDC_TODAYEDIT, WM_SETTEXT, NULL, (LPARAM)totalStr.GetString());
if (m_totalNum >= 0.01)
{CWnd* cwnd = GetDlgItem(IDC_SAVEBUTTON);cwnd->EnableWindow();
}
3.实现保存、详情功能
3.1消息映射
响应按钮按下,映射BN_CLICKED消息。
BEGIN_MESSAGE_MAP(CAccountDlg, CDialogEx) ON_BN_CLICKED(IDC_SAVEBUTTON, &CAccountDlg::OnBnClickedSavebutton)ON_BN_CLICKED(IDC_DETAILBUTTON, &CAccountDlg::OnBnClickedDetailbutton)
END_MESSAGE_MAP()
3.2保存数据到Sqlite数据库
//保存数据到本地数据库
static BOOL SaveToDB(CString path, ACCOUNTDATA data)
{try{std::string fullPath = UnicodeToUtf8(path.GetString());Database db(fullPath, OPEN_READWRITE);std::string sql = R"(INSERT OR REPLACE INTO ACCOUNT (week, breakfast,lunch,dinner,fruit,elecProduct,dailyProduct,relaxation,medical,pet,education,traffic,rent,other,total,date)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?))";Statement query2(db, sql);int i = 1;//绑定数据query2.bind(i++, data.week);query2.bind(i++, data.breakfast);query2.bind(i++, data.lunch);query2.bind(i++, data.dinner);query2.bind(i++, data.fruit);query2.bind(i++, data.elecProduct);query2.bind(i++, data.dailyProduct);query2.bind(i++, data.relaxation);query2.bind(i++, data.medical);query2.bind(i++, data.pet);query2.bind(i++, data.education);query2.bind(i++, data.traffic);query2.bind(i++, data.rent);query2.bind(i++, data.other);query2.bind(i++, data.total);query2.bind(i, UnicodeToUtf8(data.date.GetString()));//执行query2.exec();}catch (const std::exception& e){MessageBoxA(NULL, e.what(), "Save to db failed", MB_OK | MB_ICONERROR);return FALSE;}return TRUE;
}if (SaveToDB(DB_FULLPATH, data))
{SelectInDB(DB_FULLPATH, m_startDate, m_endDate, m_accountVec);int size = m_accountVec.size() - 1;GetTypeTotal(m_accountVec.at(size), m_accountVec);this->MessageBox(_T("保存成功"), _T("Success"), MB_OK | MB_ICONINFORMATION);CWnd* cwnd = GetDlgItem(IDC_SAVEBUTTON);cwnd->EnableWindow(0);
}
3.3显示详情窗口
this->ShowWindow(SW_HIDE);CDetailDlg detailDlg;float dpi = GetDpiForOwnWindow(this->GetSafeHwnd());detailDlg.Init(m_accountVec, m_curDate, m_bkPicPth, dpi);detailDlg.DoModal();this->ShowWindow(SW_SHOW);
4.实现导出Excel功能
4.1消息映射
同样是响应按钮按下的消息,映射BN_CLICKED消息。
BEGIN_MESSAGE_MAP(CDetailDlg, CDialogEx)ON_BN_CLICKED(IDC_EXPORTALLBUTTON, &CDetailDlg::OnBnClickedExportallbutton)
END_MESSAGE_MAP()
4.2导出数据到excel中
CApplication app;
CWorkbooks books;
CWorkbook book;
CWorksheets sheets;
CWorksheet sheet;
CRange range;COleVariant covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
books = app.get_Workbooks();
book = books.Add(covOptional);
sheets = book.get_Worksheets();
sheet = sheets.get_Item(COleVariant((short)1));
range.AttachDispatch(sheet.get_Cells());//得到全部单元格
int rowNum = m_mylist.GetItemCount(); //所有数据行(不包括列头)
CHeaderCtrl* pHead = m_mylist.GetHeaderCtrl();
int colNum = pHead->GetItemCount(); //所有列头//写入列头
TCHAR colName[MAX_PATH] = { 0 };
for (int col = 0; col < colNum; col++)
{range.put_Item(_variant_t((long)(1)), variant_t((long)(col + 1)),_variant_t(columnhead[col]));
}//写入数据
for (int i = 0; i < rowNum; i++)
{CString text;for (int j = 0; j < colNum; j++){//从excel的第二行开始写入列表数据。range.put_Item(_variant_t((long)(i + 2)), variant_t((long)(j + 1)),_variant_t(text.GetString()));}
}
设置指定范围的单元格背景颜色
range.AttachDispatch(sheet.get_Range(_variant_t(rowStr), _variant_t(colStr)));Cnterior it;it.AttachDispatch(range.get_Interior());it.put_Color(_variant_t(RGB(140, 255, 170)));
注意:无论是从列表上获取数据还是从容器中获取数据保存到excel中,excel的表格行列索引都是从1开始的。
五、源码
见压缩包。