基于MFC框架用C++做一个记账本

目录

一、前言

二、主要功能和技术点

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_WorksheetWorksheetsWorkbooksRangeFont

点击确定,可以在头文件中看见已经已经添加完成:

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开始的。

五、源码

见压缩包。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值