目录

MFC-项目简易销售系统实践

MFC 项目:简易销售系统实践

预览

源链接:https://blog.iyatt.com/?p=19158

https://i-blog.csdnimg.cn/img_convert/e7a1b715e6bb639d8f178eba93966ff2.png

https://i-blog.csdnimg.cn/img_convert/3530a956146a0e736c193d5e9ab55b23.png

https://i-blog.csdnimg.cn/img_convert/b657312f6a3d221a2f75e111814c5ce9.png

https://i-blog.csdnimg.cn/img_convert/f33255c826958ce833ad66531ccce8f0.png

https://i-blog.csdnimg.cn/img_convert/c219293b8c36f8fd9065446052d0def4.png

https://i-blog.csdnimg.cn/img_convert/e7887929af66754864edd6916672eb92.png

https://i-blog.csdnimg.cn/img_convert/4cd9ab37acc89dbe4e9550a503a145f2.png

测试环境

项目

参考黑马程序员的案例教程进行实践的记录,部分内容自行修改过,比如原案例直接读写文件保存账号、密码、数据,这里改为使用 SQLite3 数据库。

注意 VS 默认头文件扩展名用的 .h,我是喜欢在 C++ 中使用扩展名 .hpp,只要不是模板创建的代码部分,是我自己添加的都使用 .hpp 扩展名。

源码:下载地址见文首源链接

新建项目

新建一个 MFC 应用,项目名 saleSystem应用程序类型单个文档项目样式MFC standard

https://i-blog.csdnimg.cn/img_convert/bc1e27c6cb02bbfb1e1b1718692ab93e.png

用户界面功能经典菜单选项 ,再点 完成 进行创建

https://i-blog.csdnimg.cn/img_convert/d2a894759685544cf837ef50b09c730c.png

不使用安全函数

在项目名称上右键属性

https://i-blog.csdnimg.cn/img_convert/880e9a890d49597a591a343a3b9547b4.png

展开 配置属性 -> C/C++ -> 代码生成 ,在 安全检查中禁用安全检查

https://i-blog.csdnimg.cn/img_convert/f424842960eafc7ff72a8047e1a3788e.png

添加窗口图标

user.ico 放到项目目录下的 res 目录里

https://i-blog.csdnimg.cn/img_convert/bb0c5adde0aed75b2c7c7af96cfe37ed.png

切换到 资源视图 选项卡,展开上面的资源分支,在 Icon 上右键 添加资源

https://i-blog.csdnimg.cn/img_convert/a8326d17021c688039b02403615cd87f.png

导入

https://i-blog.csdnimg.cn/img_convert/391a4d3d895092d22af48ffec99bb6c5.png

选择图标文件

https://i-blog.csdnimg.cn/img_convert/3a2715b33b1c862f4bbd06357bb1b039.png

ID 改为 IDI_ICON_WIN

https://i-blog.csdnimg.cn/img_convert/b613dc82239dfdfaca9d91dc6ef39f28.png

类视图 下, CMainFrame 类中的 OnCreate 方法里添加代码

https://i-blog.csdnimg.cn/img_convert/7032a2303815880afb2cfa897482aed8.png

	// 加载图标
	HICON winIcon = AfxGetApp()->LoadIconW(IDI_ICON_WIN);
	// 设置小图标
	SetIcon(winIcon, FALSE);

{F5} 调试运行,点 加载图标文件

https://i-blog.csdnimg.cn/img_convert/550be9953dbbfe022d3e8ca6b4f222de.png

运行效果

https://i-blog.csdnimg.cn/img_convert/6f479791141675efc35117f54641510e.png

设置窗口大小和居中显示

类视图 下, CMainFrame 类中的 OnCreate 方法里添加代码

	// 设置位置(0,0)和窗口大小(800×600)
	MoveWindow(0, 0, 800, 600);
	// 居中显示
	CenterWindow();

https://i-blog.csdnimg.cn/img_convert/069479c795caf2478c0fb085860b669c.png

设置窗口标题

资源视图 下,展开资源树,双击打开 String Table ,在底部添加一个 ID 为 ID_STRING_PROJECTNAME ,值为 销售系统

https://i-blog.csdnimg.cn/img_convert/574eaced5602ea6fbab0e993b6de6de8.png

类视图CsaleSystemDoc 类中 OnNewDocument 方法下添加代码

	CString projectName;
	projectName.LoadStringW(ID_STRING_PROJECTNAME); // 导入字符串资源
	SetTitle(projectName); // 设置窗口标题

https://i-blog.csdnimg.cn/img_convert/e40383291ec0ff0f2ed9efef3c400057.png

运行效果

https://i-blog.csdnimg.cn/img_convert/b78001154beee95a25e6a0fa83fbb5bb.png

设计 SQLite3 数据库读写实现

这里实现账号、密码、商品信息的读写,数据库采用 SQLite3,配置 SQLite3 环境参考:https://blog.iyatt.com/?p=19187

手动创建一个数据库文件

在项目目录下打开终端,执行命令打开数据库文件 saleSystem.sb (不存在会自动创建),打开后会处于命令交互模式

sqlite3 saleSystem.sqlite3

https://i-blog.csdnimg.cn/img_convert/a1af5729d62b5ff44d64bef4101021ee.png

新建一张表用于存储账号、密码,并写入初始账号、密码(账号:admin,密码:123456)

create table users
(
    id integer primary key autoincrement ,
    username text not null unique ,
    password text not null
);

insert into users (username, password) values ('admin', '123456');

https://i-blog.csdnimg.cn/img_convert/2e00df67a44e8541ca6ad4607c1edb8e.png

在创建一张表用于存储商品数据,并插入几条商品数据

create table products (
    id integer primary key autoincrement ,
    name text not null unique ,
    price real not null ,
    stock integer not null
);

insert into products (name, price, stock) values
    ('桌子', 199.9, 5),
    ('椅子', 49.8, 10);

https://i-blog.csdnimg.cn/img_convert/879d619ffe91a944ff6b6c37654a44f7.png

退出交互模式

.exit

https://i-blog.csdnimg.cn/img_convert/70e70455b534fd1abb6dd0bfd3d3a46b.png

读写实现

类视图 下,项目上右键 添加 ->

https://i-blog.csdnimg.cn/img_convert/8e08bfc1fe159e4b5b2595fc74ef87b5.png

创建一个类,类名 StoreManager

https://i-blog.csdnimg.cn/img_convert/b984e74783308390c2c9e29c4525a9ac.png

StoreManager.hpp

#pragma once
extern "C"
{
	#include "sqlite3.h"
}

#include <vector>

typedef struct
{
	int id;
	CString name;
	double price;
	int stock;
}productStruct;

typedef struct
{
    CString username;
	CString password;
}loginInfoStruct;

typedef std::vector<productStruct> productsVector;

class StoreManager
{
private:
	static sqlite3* db;
	static productStruct product;
	static loginInfoStruct loginInfo;

private:
	static int readLoginInfoCallback(void* data, int argc, char** argv, char** colName);

	/**
	 * @brief UTF-8 编码窄字符串转 GBK 编码 CString
	 * @param utf8Str 
	 * @return 
	 */
	static CString utf8ToGbk(const char* utf8Str);

	/**
	 * @brief GBK 编码 CString 转 UTF-8 窄字符串
	 * @param gbkStr 
	 * @return 
	 */
	static char* gbkToUtf8(const CString& gbkStr);

public:
	/**
	 * @brief 连接数据库
	 * @param databasePath 数据库文件路径
	 */
	static void connect(CString& databasePath);

	/**
	 * @brief 读取登录信息
	 * @param username 用户名
	 * @param password 密码
	 */
	static void readLogin(CString& username, CString& password);

	/**
	 * @brief 修改密码
	 * @param username 要修改密码的用户 
	 * @param password 新密码
	 */
	static bool writePassword(CString& username, CString& password);

	/**
	 * @brief 关闭数据库
	 */
	static void close();

	/**
	 * @brief 读取商品信息
	 * @param products 商品信息数组
	 */
	static void readProducts(productsVector& products);

	/**
	 * @brief 写入商品信息
	 * @param products 商品信息数据
	 */
	static void writeProducts(productsVector& products);

	/**
	 * @brief 修改商品信息
	 * @param products 
	 */
	static void modifyProducts(productsVector& products);
};

StoreManager.cpp

#include "pch.h"
#include "StoreManager.hpp"

#include <string>
#include <stdexcept>

sqlite3* StoreManager::db = nullptr;
loginInfoStruct StoreManager::loginInfo;
productStruct StoreManager::product;

void StoreManager::connect(CString& databasePath)
{
	if (StoreManager::db != nullptr)
	{
		return;
	}

	CW2A databasePathA(databasePath.GetString()); // 宽字符串转普通字符串
	if (sqlite3_open(databasePathA, &StoreManager::db) != SQLITE_OK)
	{
		std::string error = "打开数据库失败:" + std::string(sqlite3_errmsg(StoreManager::db));
		throw std::runtime_error(error);
	}
}

void StoreManager::close()
{
	if (StoreManager::db != nullptr)
	{
		sqlite3_free(StoreManager::db);
		StoreManager::db = nullptr;
	}
}

int StoreManager::readLoginInfoCallback(void* data, int argc, char** argv, char** colName)
{
	(void)data;
	(void)argc;
	(void)colName;
	StoreManager::loginInfo.username = argv[1];
	StoreManager::loginInfo.password = argv[2];
	return 0;
}

void StoreManager::readLogin(CString& username, CString& password)
{
	if (StoreManager::db == nullptr)
	{
		std::string error = "请连接数据库后再读取登录信息";
		throw std::runtime_error(error);
	}

	const char* sqlA = "select * from users";
	char* errorA = nullptr;
	if (sqlite3_exec(StoreManager::db, sqlA, StoreManager::readLoginInfoCallback, nullptr, &errorA) != SQLITE_OK)
	{
		std::string error = "读取登录信息失败:" + std::string(errorA);
		sqlite3_free(errorA);
		throw std::runtime_error(error);
	}

	username = StoreManager::loginInfo.username;
    password = StoreManager::loginInfo.password;
}

bool StoreManager::writePassword(CString& username, CString& password)
{
	if (StoreManager::db == nullptr)
	{
		std::string error = "请连接数据库后再读取登录信息";
		throw std::runtime_error(error);
	}

	CString sql;
	sql.Format(L"update users set password = '%s' where username = '%s'", password.GetString(), username.GetString());
	CW2A sqlA(sql);
	char* errorA = nullptr;
	if (sqlite3_exec(StoreManager::db, sqlA, nullptr, nullptr, &errorA) != SQLITE_OK)
	{
		CA2W errorW(errorA);
		AfxMessageBox(errorW);
		sqlite3_free(errorA);
		return false;
	}
	return true;
}

CString StoreManager::utf8ToGbk(const char* utf8Str)
{
	int wideCharLen = MultiByteToWideChar(CP_UTF8, 0, utf8Str, -1, nullptr, 0);
	if (wideCharLen <= 0)
	{
		return CString();
	}

	wchar_t* wideCharStr = new wchar_t[wideCharLen];
	MultiByteToWideChar(CP_UTF8, 0, utf8Str, -1, wideCharStr, wideCharLen);

	int gbkLen = WideCharToMultiByte(CP_ACP, 0, wideCharStr, -1, nullptr, 0, nullptr, nullptr);
	if (gbkLen <= 0)
	{
		delete[] wideCharStr;
		return CString();
	}

	char* gbkStr = new char[gbkLen];
	WideCharToMultiByte(CP_ACP, 0, wideCharStr, -1, gbkStr, gbkLen, nullptr, nullptr);

	CString result(gbkStr);

	delete[] wideCharStr;
	delete[] gbkStr;

	return result;
}

char* StoreManager::gbkToUtf8(const CString& gbkStr)
{
	// 获取宽字符字符串的长度
	int wideCharLen = gbkStr.GetLength();
	if (wideCharLen <= 0)
	{
		return nullptr; // 如果字符串为空,直接返回 nullptr
	}

	// 将 CString 转换为宽字符数组
	const WCHAR* gbkW = gbkStr.GetString();

	// 获取需要的 UTF-8 编码字符串的长度(包括结尾的 '\0')
	int utf8Len = WideCharToMultiByte(CP_UTF8, 0, gbkW, -1, nullptr, 0, nullptr, nullptr);
	if (utf8Len <= 0)
	{
		return nullptr; // 如果转换失败,返回 nullptr
	}

	// 分配内存用于存储 UTF-8 编码的字符串
	char* utf8Str = new char[utf8Len];

	// 执行转换
	if (WideCharToMultiByte(CP_UTF8, 0, gbkW, -1, utf8Str, utf8Len, nullptr, nullptr) == 0)
	{
		delete[] utf8Str; // 如果转换失败,释放已分配的内存
		return nullptr;
	}

	return utf8Str; // 返回转换后的 UTF-8 字符串
}


void StoreManager::readProducts(productsVector& products)
{
	if (StoreManager::db == nullptr)
	{
		std::string error = "请连接数据库后再读取商品信息";
		throw std::runtime_error(error);
	}

	products.clear();
	
	const char* sqlA = "select * from products";
	char** result = nullptr;
	char* errorA = nullptr;
	int rows, cols;
	if (sqlite3_get_table(StoreManager::db, sqlA, &result, &rows, &cols, &errorA) != SQLITE_OK)
	{
		CA2W errorW(errorA);
		AfxMessageBox(errorW);
		sqlite3_free(errorA);
		return;
	}

	productStruct product;
	for (int row = 1; row <= rows; ++row)
	{
		product.id = std::stoi(result[row * cols + 0]);
		product.name = StoreManager::utf8ToGbk(result[row * cols + 1]);
		product.price = std::stod(result[row * cols + 2]);
		product.stock = std::stoi(result[row * cols + 3]);
		products.push_back(product);
	}
}

void StoreManager::writeProducts(productsVector& products)
{
	if (StoreManager::db == nullptr)
	{
		std::string error = "请连接数据库后再写入商品信息";
		throw std::runtime_error(error);
	}

	CString sql;
	char* errorA = nullptr;
	for (productStruct product : products)
	{
		sql.Format(L"insert into products (name, price, stock) values ('%s', %f, %d)", product.name.GetString(), product.price, product.stock);
		char* sqlA = StoreManager::gbkToUtf8(sql);
		if (sqlite3_exec(StoreManager::db, sqlA, nullptr, nullptr, &errorA) != SQLITE_OK)
		{
			CA2W errorW(errorA);
			AfxMessageBox(errorW);
			sqlite3_free(errorA);
			delete[] sqlA;
			break;
		}
		else
		{
			delete[] sqlA;
		}
	}
}

void StoreManager::modifyProducts(productsVector& products)
{
	if (StoreManager::db == nullptr)
	{
		std::string error = "请连接数据库后再修改商品信息";
		throw std::runtime_error(error);
	}

	CString sql;
	char* errorA = nullptr;
	for (productStruct product : products)
	{
		sql.Format(L"update products set price = %f, stock = %d where name = '%s'", product.price, product.stock, product.name.GetString());
		char* sqlA = StoreManager::gbkToUtf8(sql);

		if (sqlite3_exec(StoreManager::db, sqlA, nullptr, nullptr, &errorA) != SQLITE_OK)
		{
			CA2W errorW(errorA);
			AfxMessageBox(errorW);
			sqlite3_free(errorA);
			delete[] sqlA;
			break;
		}
		else
		{
			delete[] sqlA;
		}
	}
}

保证程序退出时关闭数据库

类视图 下, CsaleSystemApp 类中,找一个位置写入。注意要引用 StoreManager.hpp 头文件

struct Release
{
	~Release()
	{
		StoreManager::close();
	}
} release;

https://i-blog.csdnimg.cn/img_convert/9b665f816f4a8b1cfb601f0f2c908271.png

可以把 C++ 中的 struct 看作是默认 public 的 class,这里析构函数需要 public,使用 struct 就不需要额外写一个 public: ,然后实例化一个对象,这样程序结束的时候就会自动调用这个析构函数,完成资源的释放。

登录对话框

界面设计

资源视图 下, Dialog 上右键 插入

https://i-blog.csdnimg.cn/img_convert/1a4b4cef3ac3b8ae3b0d953a95abee81.png

将对话框 ID 改为 DIALOG_LOGIN

https://i-blog.csdnimg.cn/img_convert/cafa0eef383cf88d11fab054e0a2828b.png

添加控件,并设置好描述文字,设置窗口标题

(使用 Static Text、Edit Control 和 Button)

https://i-blog.csdnimg.cn/img_convert/38368aeec48b9fec70d2ea144359d952.png

在对话框空白处右键 添加类

https://i-blog.csdnimg.cn/img_convert/d8ab56b96317ba4432b788583bd2e362.png

创建一个类 LoginDialog

https://i-blog.csdnimg.cn/img_convert/e92c71a7dbb51cdf2cdde01a862c8490.png

为用户名编辑框创建变量 usernameEditControl ,访问选 private

https://i-blog.csdnimg.cn/img_convert/29d7cbf4496749f8c49d6a596a3972e9.png

https://i-blog.csdnimg.cn/img_convert/f788fc862cd5a7e139135c0a8f0a6756.png

同样为密码编辑框创建变量 passwordEditControl

https://i-blog.csdnimg.cn/img_convert/9ffbc24e4b6002449a6490729a587287.png

功能实现

让登录对话框在文档之前创建

类视图 下, CsaleSystemApp 类中的 InitInstance 方法中

https://i-blog.csdnimg.cn/img_convert/951a37f36ce4b4fcae4fe08b58d47f27.png

找到**CWinApp::InitInstance();**的位置,在它之前以模态的方式运行登录对话框。注意要引用对话框的头文件 LoginDialog.hpp

然后判断返回值,后续实现中如果登录成功才会调用 OnOK ,这边得到的返回值就是 IDOK ,如果不是那就说明点击了取消登录或右上角的关闭按钮,以及其它操作,这时候就要直接返回,就不会创建后续文档页面。

	LoginDialog loginDialog;
	if (loginDialog.DoModal() != IDOK)
	{
		return TRUE;
	}

https://i-blog.csdnimg.cn/img_convert/ee1f9a634399977fb55c8b76b0655d94.png

这时候调试运行就会先显示登录对话框,但是关闭登录对话框后会显示文档页面,后续还要处理这个逻辑,只有登录验证才应该显示后续的文档。

https://i-blog.csdnimg.cn/img_convert/e11e3b43dfa9c09d53e35dfbd4162dd5.png

数据库连接

资源视图 下, StringTable 添加一个字段 DATABASE_PATH ,设置 SQLite3 数据库文件的路径,这里就在项目目录下,使用相对路径

https://i-blog.csdnimg.cn/img_convert/df5be2a488fbc1b697154c970c024110.png

类视图 下,在登录对话框类 LoginDialog 上右键 属性

https://i-blog.csdnimg.cn/img_convert/990973acc510f04a3a762651b2e94fb0.png

上面点击图标切换到 重写 页面,添加(重写) OnInitDialog

https://i-blog.csdnimg.cn/img_convert/5b1a594c0ea286f3268187be0a9818b5.png

然后回到 OnInitDialog 函数中,添加连接数据库的代码。注意前面要引用数据库读写实现的头文件 StoreManager.hpp

	CString databasePath;
	databasePath.LoadStringW(DATABASE_PATH);
	StoreManager::connect(databasePath);

https://i-blog.csdnimg.cn/img_convert/391ebbddec9e3bdbabef8631f75f5103.png

登录按钮回调实现

双击登录按钮会直接创建点击事件的回调

https://i-blog.csdnimg.cn/img_convert/27e27fb94798a7eda14e2260e7d67e46.png

写入代码

	// 读取用户输入的用户名和密码
	CString inputUsername, inputPassword;
	this->usernameEditControl.GetWindowTextW(inputUsername);
	this->passwordEditControl.GetWindowTextW(inputPassword);

	// 读取数据库中的用户名和密码
	CString username, password;
	StoreManager::readLogin(username, password);

	// 判断用户名和密码
	if (inputUsername == username)
	{
		if (inputPassword != password)
		{
			MessageBox(L"密码错误");
			this->usernameEditControl.SetWindowTextW(L"");
			this->passwordEditControl.SetWindowTextW(L"");
		}
		else
		{
			MessageBox(L"登录成功");
			CDialogEx::OnOK();
		}
	}
	else
	{
		MessageBox(L"用户名不存在");
		this->usernameEditControl.SetWindowTextW(L"");
		this->passwordEditControl.SetWindowTextW(L"");
	}

https://i-blog.csdnimg.cn/img_convert/44d44131ad423134c9f05386860ba1d9.png

调试运行

https://i-blog.csdnimg.cn/img_convert/3951c74f4ff76c4bdc89058e2aea7ebd.png

https://i-blog.csdnimg.cn/img_convert/1c60c8f352c618bafd88cd95509d8e38.png

取消按钮回调实现

双击取消按钮,创建点击事件回调

https://i-blog.csdnimg.cn/img_convert/5e58d5edab3bc830b15841da799a93fb.png

点击取消调用取消方法

	CDialogEx::OnCancel();

https://i-blog.csdnimg.cn/img_convert/27e5c73dbf1a73cc393872384ed327dc.png

修改回车键为登录

对话框默认状态下按回车会调用 CDialogEx::OnOK() ,这就会让登录验证成为摆设,相当于点击了登录对话框的 OK,那么接下来就会直接进入文档页面。

另外按照一般使用习惯回车键就是确认输入,这里也就是确认登录。

类视图 下, LoginDialog 类上右键 属性 ,重写对话框的 OnOK

https://i-blog.csdnimg.cn/img_convert/bb10688dc3c329e49c036e0c9da5d4d6.png

注释掉 CDialogEx::OnOK(); ,然后调用登录按钮点击事件的回调方法,这就按回车就等于是点击登录按钮。

	this->OnBnClickedButton1();

https://i-blog.csdnimg.cn/img_convert/ed390064e1bff7d1dca53632455f9fa7.png

静态拆分窗口

自定义视图类

类视图 中项目名上右键 类向导

https://i-blog.csdnimg.cn/img_convert/5bf7f05fa3e35a39476580a73207d315.png

下拉 MFC 类

https://i-blog.csdnimg.cn/img_convert/7155bcc7b3ccacaeff1c5089acb53931.png

类名 SelectView ,基类 CTreeView

https://i-blog.csdnimg.cn/img_convert/2a3a89fefc4692376b20fd23c22bfd5d.png

同样再添加一个类 DisplayView ,基类 CFormView

https://i-blog.csdnimg.cn/img_convert/9a7558054781bb080909a6b88aeb1020.png

创建完上面两个类,在类向导页面点确定,编译在下面可能看到一堆 SelectView 头文件和源文件的报错

https://i-blog.csdnimg.cn/img_convert/194c66a42eb029dfa0cdbc4e63b74e98.png

在头文件增加引用 #include “afxcview.h”

https://i-blog.csdnimg.cn/img_convert/38dc687be099a18c5e1ddc7c1a16b261.png

再编译就好了

https://i-blog.csdnimg.cn/img_convert/3fde5ba0e491f733b5327a3aa3f855c8.png

拆分窗口

类视图 中,点开 CMainFrame 类,声明一个对象

private:
	CSplitterWnd splitter;

https://i-blog.csdnimg.cn/img_convert/cf1edc8bfa7c1d3ddf9dc117dd16264d.png

CMainFrame 上右键 属性

https://i-blog.csdnimg.cn/img_convert/635fe74e882fbbd051998775cf0b9d8e.png

重写 OnCreateClient

https://i-blog.csdnimg.cn/img_convert/d99d32fb7b601f0309954efe17c03805.png

把原来的返回注释了,重新写

注意需要引用 SelectView.hppDisplayView.hpp 头文件

	// 拆分为 1 行 2 列
	this->splitter.CreateStatic(this, 1, 2);

	// 创建左侧视图
	this->splitter.CreateView(0, 0, RUNTIME_CLASS(SelectView), CSize(200, 500), pContext);

	// 创建右侧视图
	this->splitter.CreateView(0, 1, RUNTIME_CLASS(DisplayView), CSize(600, 500), pContext);

	return TRUE;

https://i-blog.csdnimg.cn/img_convert/09f546bdf0756c6db8cc637fd069aced.png

调试运行,登录后可以看到下图的布局

https://i-blog.csdnimg.cn/img_convert/bac903189af2ffa67a8749ecffed4c58.png

左侧树视图

添加功能节点

资源视图 下, Icon 右键 添加资源

https://i-blog.csdnimg.cn/img_convert/7c203496cab89e97a4ab0110d53d61e8.png

导入

https://i-blog.csdnimg.cn/img_convert/fff34f20908c084a19d36b7959d104ee.png

选择 re.ico 文件

https://i-blog.csdnimg.cn/img_convert/01fb73a26893c33db2bdf1af7fb06440.png

ID 重设为 IDI_ICON_RE

https://i-blog.csdnimg.cn/img_convert/790bba76835776ca2fb5030ea18579a6.png

类视图 下双击 SelectView 类,在类头文件中增加

private:
	CTreeCtrl* treeCtrl;
	CImageList imageList;

https://i-blog.csdnimg.cn/img_convert/55a1107854ab022fc128b640539bcb89.png

SelectView 类上右键属性

https://i-blog.csdnimg.cn/img_convert/6f7e8363cfe5bc05d160e9f4e0783229.png

重写 OnInitialUpdate

https://i-blog.csdnimg.cn/img_convert/05c6581160631c4511df004d48b6ddcb.png

写入

	// 加载图标
	HICON icon = AfxGetApp()->LoadIconW(IDI_ICON_RE);

	// 创建图片列表
	this->imageList.Create(30, 30, ILC_COLOR32, 1, 1);

	// 添加图标
	this->imageList.Add(icon);

	// 获取树控件
	this->treeCtrl = &GetTreeCtrl();

	// 树控件设置图片列表
	this->treeCtrl->SetImageList(&this->imageList, TVSIL_NORMAL);

	// 树控件设置节点
	this->treeCtrl->InsertItem(L"个人信息", 0, 0, TVI_ROOT, TVI_LAST);
	this->treeCtrl->InsertItem(L"销售管理", 0, 0, TVI_ROOT, TVI_LAST);
	this->treeCtrl->InsertItem(L"库存信息", 0, 0, TVI_ROOT, TVI_LAST);
	this->treeCtrl->InsertItem(L"库存添加", 0, 0, TVI_ROOT, TVI_LAST);
	this->treeCtrl->InsertItem(L"库存删除", 0, 0, TVI_ROOT, TVI_LAST);

https://i-blog.csdnimg.cn/img_convert/205497537728aefc4de9b1f5a487364f.png

运行登陆后

https://i-blog.csdnimg.cn/img_convert/106b462a399ddcca4b2d375a19a3af8f.png

功能节点消息处理

查看 SelectView 类属性,添加消息 TVN_SELCHANGED 的回调

https://i-blog.csdnimg.cn/img_convert/462eb42380f30eb3775fab0688a19db0.png

写入

	// 获取选中的项目
	HTREEITEM item = this->treeCtrl->GetSelectedItem();

	// 获取选中项文本内容
	CString selectedText = this->treeCtrl->GetItemText(item);

	if (selectedText == L"个人信息")
	{

	}
	else if (selectedText == L"销售管理")
	{

	}
	else if (selectedText == L"库存信息")
	{

	}
	else if (selectedText == L"库存添加")
	{

	}
	else if (selectedText == L"库存删除")
	{

	}

https://i-blog.csdnimg.cn/img_convert/87e2fcd98a61bba6430c57735d5e8909.png

个人信息页面

界面设计

资源视图 下, Dialog 上右键 插入

https://i-blog.csdnimg.cn/img_convert/7183a76f05bf324d703f20b37f49d2bc.png

ID 改为 DIALOG_USER边框 改为 None样式 改为 Child

https://i-blog.csdnimg.cn/img_convert/b285def292460dd4383e2a169e30f547.png

https://i-blog.csdnimg.cn/img_convert/1f6c1da60ed2bc59ff209608b97d154e.png

绘制页面,原来的确定和取消按钮保留

(Group Box、Static Text、Edit Control)

https://i-blog.csdnimg.cn/img_convert/35478ddb752df629bdc386cd3035bb1b.png

在对话框空白处右键 添加类

https://i-blog.csdnimg.cn/img_convert/a12e2eb5a1db49d9f9567c19035085c4.png

类名 UserDialog ,基类 CFormView

https://i-blog.csdnimg.cn/img_convert/e083470e25f044f4bdd753c530d0704b.png

下面分别为 4 个编辑框创建变量

身份

https://i-blog.csdnimg.cn/img_convert/282992fecd9dafe0719e7fb312914f75.png

变量名 positionEditControl

https://i-blog.csdnimg.cn/img_convert/4cb6ddf5e3ba9843fdd2443d0a6670f5.png

用户名编辑框变量 usernameEditControl

https://i-blog.csdnimg.cn/img_convert/2bbdadf480bb229088b6e27b1936eb72.png

新密码编辑框变量 newPasswordEditControl

https://i-blog.csdnimg.cn/img_convert/29c3e05b3a747f6907c45298b6a4775a.png

确定密码编辑框变量 confirmPasswordEditControl

https://i-blog.csdnimg.cn/img_convert/c97ffde221ccfac16b768cbc21f8990f.png

功能实现

初始化界面

类视图UserDialog 类上右键 属性

https://i-blog.csdnimg.cn/img_convert/6d28a75c592ad7318cc62383070ad70b.png

重写 OnInitialUpdate

https://i-blog.csdnimg.cn/img_convert/56dd7f3e09eb7eb287f0076687b49b1f.png

	this->positionEditControl.SetWindowTextW(L"销售员");

https://i-blog.csdnimg.cn/img_convert/d0550e954503087554688b938b021192.png

确定修改密码

双击确定按钮,编辑确定按钮的单机事件回调,注意要引用 StoreManager.hpp 头文件


	if (this->usernameEditControl.GetWindowTextLengthW() == 0)
	{
		MessageBox(L"输入用户名不能为空");
		return;
	}
	
	if (this->newPasswordEditControl.GetWindowTextLengthW() == 0 || this->confirmPasswordEditControl.GetWindowTextLengthW() == 0)
	{
		MessageBox(L"输入密码不能为空");
		return;
	}

	CString newPassword, confirmPassword;
	this->newPasswordEditControl.GetWindowTextW(newPassword);
	this->confirmPasswordEditControl.GetWindowTextW(confirmPassword);

	if (newPassword != confirmPassword)
	{
		MessageBox(L"输入密码和确定密码不同");
		return;
	}

	CString oldPassword, username, inputUsername;
	StoreManager::readLogin(username, oldPassword);
	this->usernameEditControl.GetWindowTextW(inputUsername);

	if (inputUsername != username)
	{
		MessageBox(L"用户名错误");
		return;
	}

	if (newPassword == oldPassword)
	{
		MessageBox(L"新密码和原密码相同");
		return;
	}

	if (StoreManager::writePassword(inputUsername, newPassword))
	{
		MessageBox(L"修改密码成功");
	}
	else
	{
		MessageBox(L"修改密码失败");
	}

https://i-blog.csdnimg.cn/img_convert/9b3f6a73ff0763c1597009b1e8576b7a.png

取消修改密码

双击取消按钮

	this->usernameEditControl.SetWindowTextW(L"");
	this->newPasswordEditControl.SetWindowTextW(L"");
	this->confirmPasswordEditControl.SetWindowTextW(L"");

https://i-blog.csdnimg.cn/img_convert/bb975f8970ecceaa2b29164b3e58588c.png

界面挂载

自定义消息发送

类视图 中双击 CMainFrame 类进行编辑,写入自定义消息

constexpr UINT NM_USER = WM_USER + 100;
constexpr UINT NM_SELL = WM_USER + 101;
constexpr UINT NM_INFO = WM_USER + 102;
constexpr UINT NM_ADD = WM_USER + 103;
constexpr UINT NM_DEL = WM_USER + 104;

https://i-blog.csdnimg.cn/img_convert/d33fe40338d4c33222ad92c83b646dea.png

添加自定义消息处理函数,头文件中添加声明

protected:
	afx_msg LRESULT onMyChange(WPARAM wParam, LPARAM lParam);

https://i-blog.csdnimg.cn/img_convert/32f9545de25988f5280c82522823c80f.png

源文件中添加定义

afx_msg LRESULT CMainFrame::onMyChange(WPARAM wParam, LPARAM lParam)
{

}

https://i-blog.csdnimg.cn/img_convert/e57b185079d4bce6e2dde03145b9ba19.png

然后看到 BEGIN_MESSAGE_MAP ,在它和 END_MESSAGE_MAP() 之间添加代码

	// 响应自定义消息
	ON_MESSAGE(NM_USER, onMyChange)
	ON_MESSAGE(NM_SELL, onMyChange)
	ON_MESSAGE(NM_INFO, onMyChange)
	ON_MESSAGE(NM_ADD, onMyChange)
	ON_MESSAGE(NM_DEL, onMyChange)

https://i-blog.csdnimg.cn/img_convert/6b4bb6ce20cfa9286869645727c91ced.png

编辑 SelectView 类中的 OnTvnSelchanged 方法,注意需要引用 MainFrm.h 头文件

	if (selectedText == L"个人信息")
	{
		// 将消息放入消息队列
		::PostMessage(
			AfxGetMainWnd()->GetSafeHwnd(), // 框架窗口对象指针
			NM_USER, // 发送自定义消息
			NM_USER, // 消息的附加参数
			0 // 消息的附加参数,这里不使用
		);
	}
	else if (selectedText == L"销售管理")
	{
		::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_SELL, NM_SELL, 0);
	}
	else if (selectedText == L"库存信息")
	{
		::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_INFO, NM_INFO, 0);
	}
	else if (selectedText == L"库存添加")
	{
		::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_ADD, NM_ADD, 0);
	}
	else if (selectedText == L"库存删除")
	{
		::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_DEL, NM_DEL, 0);
	}

https://i-blog.csdnimg.cn/img_convert/293435abb8301266562a59fbdc97269f.png

自定义消息处理

编辑 CMainFrame 类中的 OnMyChange 方法,注意引用 UserDialog.hpp 头文件

	CCreateContext context;
	this->splitter.DeleteView(0, 1);
	switch (wParam)
	{
		case NM_USER:
		{
			context.m_pNewViewClass = RUNTIME_CLASS(UserDialog);
			this->splitter.CreateView(0, 1, RUNTIME_CLASS(UserDialog), CSize(600, 500), &context);
			((UserDialog*)this->splitter.GetPane(0, 1))->OnInitialUpdate();
			break;
		}
		case NM_SELL:
		{

			break;
		}
		case NM_INFO:
		{

			break;
		}
		case NM_ADD:
		{

			break;
		}
		case NM_DEL:
		{

			break;
		}

	}
	this->splitter.RecalcLayout();
	this->splitter.SetActivePane(0, 1);
	context.m_pLastView = (CFormView*)this->splitter.GetPane(0, 1);
	context.m_pCurrentFrame = this;

	return 0;

https://i-blog.csdnimg.cn/img_convert/326af5f0e6eb0e0c411ee2d766044f9e.png

调试运行效果

https://i-blog.csdnimg.cn/img_convert/976d6c33a350e9a4c5a406578f0c4c63.png

https://i-blog.csdnimg.cn/img_convert/16d6c0a758c1c317c964c76a7a320b96.png

https://i-blog.csdnimg.cn/img_convert/f3e7cb5283c58e0546e23a71167d04c5.png

https://i-blog.csdnimg.cn/img_convert/c13149fdde7922fce5eb7c9b6fbbbe13.png

https://i-blog.csdnimg.cn/img_convert/df119e96457daadbc0017a83d640d071.png

https://i-blog.csdnimg.cn/img_convert/332163556111502fd0bf8e4c0e84a82b.png

销售管理页面

界面设计

资源视图下插入新的对话框

https://i-blog.csdnimg.cn/img_convert/a7faeff23efee6e4e69d75b18c142d3d.png

ID 改为 DIALOG_SELL边框 改为 None样式 改为 Child

https://i-blog.csdnimg.cn/img_convert/b034ab2fc246ccf4e0818b226d35a58d.png

绘制界面,不删除原先的确定和取消按钮, 确定 的描述文字改成 购买 ,商品名那里的 Combo Box 属性里的 类型 改为 下拉列表 ,订单信息那里的大编辑框属性里 多行垂直滚动 依次设置为 True ,数量编辑框属性里的 数字 改为 True

(Group Box、Static Text、Edit Control、Combo Box)

https://i-blog.csdnimg.cn/img_convert/0b1f72500fdef45af39f59da8d209534.png

在对话框空白处右键添加类

https://i-blog.csdnimg.cn/img_convert/e834fc00a1bf878d3583843c0f0efe19.png

类名为 SellDialog ,基类为 CFormView

https://i-blog.csdnimg.cn/img_convert/dbfa773d085bfebd7bb34a3d97f84c5b.png

为商品名组合框创建变量 productNameComboBoxControl

https://i-blog.csdnimg.cn/img_convert/c045e1fe990c47643e47a72e146bb97f.png

https://i-blog.csdnimg.cn/img_convert/e52e9beea8e7b8db576c7647b3858e1d.png

为单价编辑框创建变量 priceEditValue ,注意类别选值,变量类型填 double

https://i-blog.csdnimg.cn/img_convert/af12ac59e4170f6492f7337d0e4abdee.png

为数量编辑框创建变量 numEditValue ,类别选值,变量类型填 int

https://i-blog.csdnimg.cn/img_convert/e773f9dc6c2f7505490490ff9ed0dad0.png

为订单信息编辑框创建变量 sellEditControl

https://i-blog.csdnimg.cn/img_convert/182c69ffcb8db82668f21ec09b137c6e.png

界面挂载

编辑 CMainFrame 类中的 OnMyChange 方法,在 case NM_SELL 下写。注意引用 SellDialog.hpp 头文件

			context.m_pNewViewClass = RUNTIME_CLASS(SellDialog);
			this->splitter.CreateView(0, 1, RUNTIME_CLASS(SellDialog), CSize(600, 500), &context);
			((SellDialog*)this->splitter.GetPane(0, 1))->OnInitialUpdate();

https://i-blog.csdnimg.cn/img_convert/2d8bfdf41fbe94e6c8cd92fc07a161cf.png

调试运行

https://i-blog.csdnimg.cn/img_convert/cd15d7ddb5de5b2df30865380ffa2b8e.png

功能实现

初始化界面

CellDialog 类上右键属性,重写 OnInitialUpdate 方法

https://i-blog.csdnimg.cn/img_convert/fe15abf3f9980c36718ca629eb135d47.png

这里从数据库读取商品数据,把商品名设置给组合框,注意引用 StoreManager.hpp 头文件

	productsVector products;
	StoreManager::readProducts(products);

	for (productStruct product : products)
	{
		this->productNameComboBoxControl.AddString(product.name);
	}
	this->productNameComboBoxControl.SetCurSel(0);

https://i-blog.csdnimg.cn/img_convert/65195b0057dd0c8a9d5d95bfaea388f3.png

调试运行

https://i-blog.csdnimg.cn/img_convert/666cc8af1aa7514ffb3f63427e829e95.png

组合框切换刷新信息

在商品名组合框上右键属性

https://i-blog.csdnimg.cn/img_convert/8feae897822fb1d31d558f6003e82046.png

创建 CBN_SELCHANGE 事件的回调方法

https://i-blog.csdnimg.cn/img_convert/b958299852d71984d30cff8047ce3a14.png

写入

	// 获取当前选中项的索引
	int curIdx = this->productNameComboBoxControl.GetCurSel();

	// 获取当前选中项的文本
	CString curText;
	this->productNameComboBoxControl.GetLBText(curIdx, curText);

	productsVector products;
	StoreManager::readProducts(products);

	for (productStruct product : products)
	{
		if (curText == product.name)
		{
			this->priceEditValue = product.price;
			this->numEditValue = 0;
			UpdateData(FALSE);
			break;
		}
	}

https://i-blog.csdnimg.cn/img_convert/bbaa758e38d8413263c65ba6260de758.png

另外在前面重写的 OnInitialUpdate 方法末尾调用一下,实现初始化

	this->OnCbnSelchangeCombo1();

https://i-blog.csdnimg.cn/img_convert/53b90ad7c961ce13d1ef893a325e44d4.png

调试运行

https://i-blog.csdnimg.cn/img_convert/41cb8b8c9a4b57f4d93742db53943179.png

https://i-blog.csdnimg.cn/img_convert/8d22cdc3be509b8cf97674e9b94c5ce3.png

购买实现

双击购买按钮,创建点击事件回调,写入

	if (this->numEditValue == 0)
	{
		MessageBox(L"购买数量不能为 0");
		return;
	}

	int curIdx = this->productNameComboBoxControl.GetCurSel();
	CString curText;
	this->productNameComboBoxControl.GetLBText(curIdx, curText);

	productsVector products;
	StoreManager::readProducts(products);
	for (productsVector::iterator product = products.begin(); product != products.end(); ++product)
	{
		if (curText == product->name)
		{
			if (this->numEditValue > product->stock)
			{
				CString msg;
				msg.Format(L"购买数量超出库存,当前库存数量:%d,请减小购买数量后再试", product->stock);
				MessageBox(msg.GetString());
				return;
			}
			product->stock = product->stock - this->numEditValue;
			productsVector modifyProduct = { *product };
			StoreManager::modifyProducts(modifyProduct);
			CString sellMsg;
			sellMsg.Format(L"商品:%s\r\n单价:%f\r\n数量:%d\r\n总价:%f\r\n剩余库存:%d", product->name.GetString(), product->price, this->numEditValue, product->price * this->numEditValue, product->stock);
			this->sellEditControl.SetWindowTextW(sellMsg.GetString());
			break;
		}
	}

https://i-blog.csdnimg.cn/img_convert/bebc012a023a573555c39d21d91cbd2f.png

调试运行效果

https://i-blog.csdnimg.cn/img_convert/7bb5624f1fc2f54a6d6989c12479249f.png

https://i-blog.csdnimg.cn/img_convert/2816ea215b9cbbfca9282b92b24bcbf7.png

https://i-blog.csdnimg.cn/img_convert/83db1e6aea58ce956221fea9b3799b16.png

https://i-blog.csdnimg.cn/img_convert/04d36cf172cd1167dda1add10611274f.png

取消

双击取消按钮,创建事件回调

	this->sellEditControl.SetWindowTextW(L"");
	this->numEditValue = 0;
	UpdateData(FALSE);

https://i-blog.csdnimg.cn/img_convert/a4b2d01f6171ddd40a80ab54038a149a.png

库存信息页面

界面设计

添加一个对话框

https://i-blog.csdnimg.cn/img_convert/686ef8b83a1cc16e2bdf02fa2a67e288.png

属性里,ID 设置为 DIALOG_INFO ,边框选 None ,样式选 Child

https://i-blog.csdnimg.cn/img_convert/3cfc067f7f740238bf21e8936ab56e00.png

https://i-blog.csdnimg.cn/img_convert/aab2e7ec817a00413f4c1c720f83c953.png

绘制界面,删除确定和取消按钮,List Control 的视图改为 Report

(Static Text、List Control)

https://i-blog.csdnimg.cn/img_convert/b6fd430fd2084c076f54d65ab2c6b9f1.png

对话框空白处右键添加类

https://i-blog.csdnimg.cn/img_convert/4bdc4153122290f636a234dbe907b89d.png

类名 InfoDialog ,基类 CFormView

https://i-blog.csdnimg.cn/img_convert/3b604cd54319f60f1fc65d9ac0326f4d.png

在列表控件上右键添加变量 infoListControl

https://i-blog.csdnimg.cn/img_convert/b59a6768046371ed551b8ce212d7968e.png

界面挂载

编辑 CMainFrame 类中的 onMyChange 方法,在 case NM_INFO 下写入,注意引用头文件 InfoDialog.hpp

			context.m_pNewViewClass = RUNTIME_CLASS(InfoDialog);
			this->splitter.CreateView(0, 1, RUNTIME_CLASS(InfoDialog), CSize(600, 500), &context);
			((InfoDialog*)this->splitter.GetPane(0, 1))->OnInitialUpdate();

https://i-blog.csdnimg.cn/img_convert/2654bbc29e95d0e00600238db0280006.png

调试运行

https://i-blog.csdnimg.cn/img_convert/e327651edf8eae8c9f082d47bcb87a75.png

功能实现

InfoDialog 类上右键 属性

https://i-blog.csdnimg.cn/img_convert/23e4aff5ab2247d7180f52ba38917881.png

重写 OnInitialUpdate 方法

https://i-blog.csdnimg.cn/img_convert/210ceeca48abb9a7571516e9c4797c7d.png

注意引用 StoreManager.hpp 头文件

	// 显示表头
	this->infoListControl.SetExtendedStyle(this->infoListControl.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
	CString field[] = { L"商品ID", L"商品名称", L"商品价格", L"库存数量" };
	for (int i = 0; i < sizeof(field) / sizeof(field[0]); ++i)
	{
		this->infoListControl.InsertColumn(i, field[i], LVCFMT_CENTER, 90);
	}

	// 读取商品信息
	productsVector products;
	StoreManager::readProducts(products);

	// 显示数据
	int idx = 0;
	CString tmpStr;
	for (productsVector::iterator product = products.begin(); product != products.end(); ++product)
	{
		tmpStr.Format(L"%d", product->id);
		this->infoListControl.InsertItem(idx, tmpStr);
		this->infoListControl.SetItemText(idx, 1, product->name);
		tmpStr.Format(L"%f", product->price);
		this->infoListControl.SetItemText(idx, 2, tmpStr);
		tmpStr.Format(L"%d", product->stock);
		this->infoListControl.SetItemText(idx, 3, tmpStr);

		++idx;
	}

https://i-blog.csdnimg.cn/img_convert/772758f27f1680c7f7c41fdea61808ca.png

调试运行

https://i-blog.csdnimg.cn/img_convert/107cd0e4dfde25a23b9165405be34eff.png

库存添加页面

界面设计

添加一个对话框

https://i-blog.csdnimg.cn/img_convert/14669617ec87900944824f498f603686.png

ID 改为 DIALOG_ADD ,边框改为 None ,样式改为 Child

https://i-blog.csdnimg.cn/img_convert/d538fa886d897b62c23e80c26eef1dbf.png

https://i-blog.csdnimg.cn/img_convert/71f09d0fff9d1984ef94d04397713833.png

绘制界面,删除原来的确定和取消按钮,个数编辑框属性的 数字 设置为 True ,Combo Box 属性类型选 下拉列表 ,库存单价编辑库属性 只读 设置为 True

(Static Text、Group Box、Edit Control、Button、Combo Box)

https://i-blog.csdnimg.cn/img_convert/1d4c75f196a8b31af13a2f5b79aa78a1.png

在对话框空白处右键添加类 AddDialog ,基类 CFormView

https://i-blog.csdnimg.cn/img_convert/9b40cc2ee902ab5797072e0f16764e93.png

为组合框和编辑框添加变量

https://i-blog.csdnimg.cn/img_convert/d2ad2a191d2400cb5aeff832d992e9f8.png

库存商品编辑框,变量名 stockProductComboBoxControl ,访问 private

https://i-blog.csdnimg.cn/img_convert/521cfa5001ebfcb3f8ec23ef864c7d87.png

库存价格编辑框,变量名 stockPriceEditValue ,类别 ,访问 private ,变量类型 double

https://i-blog.csdnimg.cn/img_convert/978efd4e58f1aa1be3a687518f4b3087.png

库存个数编辑框,变量名 stockNumEditValue ,类别 ,访问 private ,变量类型 int

https://i-blog.csdnimg.cn/img_convert/dd84430b9275ca37c427e3c1c7709eae.png

新商品编辑框,变量名 newProductEditValue ,类别 ,访问 private

https://i-blog.csdnimg.cn/img_convert/c51f5a124b1a21b203c8beeee98771aa.png

新商品单价编辑框,变量名 newPriceEditValue ,类别 ,访问 private ,变量类型 double

https://i-blog.csdnimg.cn/img_convert/5d9a758a5270a8bb9086f5f1e84b5ee9.png

新商品库存编辑框,变量名 newNumEditValue ,类别 ,访问 private ,变量类型 int

https://i-blog.csdnimg.cn/img_convert/91e95cdad78c329daacb96bddabecef1.png

界面挂载

编辑 CMainFrame 类中 onMyChange 方法,在 case NM_ADD 中添加,注意引用头文件 AddDialog.hpp

			context.m_pNewViewClass = RUNTIME_CLASS(AddDialog);
			this->splitter.CreateView(0, 1, RUNTIME_CLASS(AddDialog), CSize(600, 500), &context);
			((AddDialog*)this->splitter.GetPane(0, 1))->OnInitialUpdate();

https://i-blog.csdnimg.cn/img_convert/b39d56f85307415aadd3751e2f6a9909.png

调试运行

https://i-blog.csdnimg.cn/img_convert/e1bcf29926b36513f4279602039a3219.png

功能实现

初始化库存组合框

重写 OnInitialUpdate 方法

https://i-blog.csdnimg.cn/img_convert/102b26cf7983c7799455d501090876c1.png

https://i-blog.csdnimg.cn/img_convert/77b55ee18ab6dc17c5c9ffb6a85a5874.png

注意引用 StoreManager.hpp 头文件

	productsVector products;
	StoreManager::readProducts(products);
	for (productsVector::iterator product = products.begin(); product != products.end(); ++product)
	{
		this->stockProductComboBoxControl.AddString(product->name);
	}
	this->stockProductComboBoxControl.SetCurSel(0);

https://i-blog.csdnimg.cn/img_convert/11b54d67bfaaf0e7258de51b8a5a66ed.png

调试运行

https://i-blog.csdnimg.cn/img_convert/6a9bb4e27cc83861ec8a07b1525995d0.png

库存组合框切换事件回调

https://i-blog.csdnimg.cn/img_convert/0dcc7ad3b79374a21e8d0d9a2c88bfa5.png

https://i-blog.csdnimg.cn/img_convert/f755d8c92aa36e73fae064372adf2c16.png

	int curIdx = this->stockProductComboBoxControl.GetCurSel();
	CString curText;
	this->stockProductComboBoxControl.GetLBText(curIdx, curText);

	productsVector products;
	StoreManager::readProducts(products);
	for (productsVector::iterator product = products.begin(); product != products.end(); ++product)
	{
		if (curText == product->name)
		{
			this->stockPriceEditValue = product->price;
			this->stockNumEditValue = 0;
			UpdateData(FALSE);
			break;
		}
	}

https://i-blog.csdnimg.cn/img_convert/1b338bc98d7be2981a9c15f0bed23b2b.png

OnInitialUpdate 里调用这个方法,保证初始化的时候正确设置

	this->OnCbnSelchangeCombo2();

https://i-blog.csdnimg.cn/img_convert/6bc9a2fd2658ba12b46b783f2f60e1eb.png

调试运行

https://i-blog.csdnimg.cn/img_convert/39d052208208319b8184029205ca7169.png

添加库存

双击 添加库存 按钮

	UpdateData(TRUE);

	if (this->stockNumEditValue <= 0 || this->stockPriceEditValue <= 0)
	{
		MessageBox(L"数量必须大于 0,或价格不能低于 0");
		return;
	}

	int curIdx = this->stockProductComboBoxControl.GetCurSel();
	CString curText;
	this->stockProductComboBoxControl.GetLBText(curIdx, curText);

	productsVector products;
	StoreManager::readProducts(products);
	for (productsVector::iterator product = products.begin(); product != products.end(); ++product)
	{
		if (curText == product->name)
		{
			product->stock += this->stockNumEditValue;
			CString msg;
			productsVector tmpProduct = { *product };
			StoreManager::modifyProducts(tmpProduct);
			msg.Format(L"增加库存:%d,库存总量:%d", this->stockNumEditValue, product->stock);
			MessageBox(msg.GetString());
			break;
		}
	}

	this->stockNumEditValue = 0;
	UpdateData(FALSE);

https://i-blog.csdnimg.cn/img_convert/722b5f0ce7fca10452713aaaa7e2737e.png

调试运行

https://i-blog.csdnimg.cn/img_convert/c0f4071bbcb0bf5f34d6d48b9ac1d155.png

取消库存设置

双击 取消库存设置 按钮

	this->stockNumEditValue = 0;
	UpdateData(FALSE);

https://i-blog.csdnimg.cn/img_convert/ffdf84fde80e9cfed14f0c799760cf66.png

添加新商品

双击 添加新商品 按钮

	UpdateData(TRUE);

	if (this->newNumEditValue <= 0 || this->newPriceEditValue <= 0 || this->newProductEditValue.IsEmpty())
	{
		MessageBox(L"输入信息有误");
		return;
	}

	productStruct product;
	product.name = this->newProductEditValue;
	product.price = this->newPriceEditValue;
	product.stock = this->newNumEditValue;
	productsVector tmpProduct = { product };
	StoreManager::writeProducts(tmpProduct);

	this->newProductEditValue.Empty();
	this->newPriceEditValue = 0;
	this->newNumEditValue = 0;
	UpdateData(FALSE);

	this->OnInitialUpdate();

	CString msg;
	msg.Format(L"添加商品:%s,单价:%f,数量:%d", product.name.GetString(), product.price, product.stock);
	MessageBox(msg.GetString());

https://i-blog.csdnimg.cn/img_convert/6ff90900a06cf87ab3074e9b91cccd27.png

调试运行

https://i-blog.csdnimg.cn/img_convert/51f589e888b63bf16104d9e61e740f7b.png

取消商品设置

双击 取消商品设置 按钮

	this->newProductEditValue.Empty();
	this->newPriceEditValue = 0;
	this->newNumEditValue = 0;
	UpdateData(FALSE);

https://i-blog.csdnimg.cn/img_convert/cc7e2d4acfa8ee576fdc588f9406d25c.png

库存删除页面

界面设计

添加一个对话款

https://i-blog.csdnimg.cn/img_convert/2f32f297b8648f9658de2ecc9d00c127.png

ID 改为 DIALOG_DEL ,边框改为 None ,样式改为 Child

https://i-blog.csdnimg.cn/img_convert/aba9a307a65593bff1ab1eb424665816.png

绘制界面,保留确定和取消按钮,Combo Box 类型改为 下拉列表 ,数量编辑框属性数字设置为 True ,设置单价编辑框只读

(Group Box、Static Text、Combo Box、Edit Control)

https://i-blog.csdnimg.cn/img_convert/c84d69e0f7dcfe91551c48ebd130b0ed.png

为对话框添加类 DelDialog ,基类 CFormView

https://i-blog.csdnimg.cn/img_convert/228dfffe816425e2b54f2d03a18d63b9.png

https://i-blog.csdnimg.cn/img_convert/46cc0c6edd2e61021cebed757be2526f.png

为组合框和编辑框添加变量

商品名 productComboBoxControl

https://i-blog.csdnimg.cn/img_convert/972efb1b0943ed4f468d618abf91d768.png

https://i-blog.csdnimg.cn/img_convert/af1ac9a6b90e23e3c4b5c5ec54645989.png

单价 priceEditValue

https://i-blog.csdnimg.cn/img_convert/2ce56ca2ac1cd3be33e85b527b6fca41.png

数量 numEditValue

https://i-blog.csdnimg.cn/img_convert/c99978ac20fd513fb37d38c0f02cad52.png

界面挂载

编辑 CMainFrame 类中 onMyChange 方法,在 case NM_DEL 下写入,注意引用头文件 DelDialog.hpp

			context.m_pNewViewClass = RUNTIME_CLASS(DelDialog);
			this->splitter.CreateView(0, 1, RUNTIME_CLASS(DelDialog), CSize(600, 500), &context);
			((DelDialog*)this->splitter.GetPane(0, 1))->OnInitialUpdate();

https://i-blog.csdnimg.cn/img_convert/c485269ffa3461b63f3219f7d06e39a9.png

调试运行

https://i-blog.csdnimg.cn/img_convert/e41297baf5e264980e281571ea51fd8d.png

功能实现

初始化界面

重写 OnInitialUpdate 方法

https://i-blog.csdnimg.cn/img_convert/41d3b94417c312a2ec89818953d57209.png

https://i-blog.csdnimg.cn/img_convert/b521355e431ad000f142e90c2feaafc2.png

注意引用 StoreManager.hpp 头文件

	productsVector products;
	StoreManager::readProducts(products);

	for (productsVector::iterator product = products.begin(); product != products.end(); ++product)
	{
		this->productComboBoxControl.AddString(product->name);
	}
	this->productComboBoxControl.SetCurSel(0);

https://i-blog.csdnimg.cn/img_convert/49d14d8a346d48bff2f150c72ea4a1f1.png

调试运行

https://i-blog.csdnimg.cn/img_convert/044652974bad7ab3cc0eb676299a2625.png

组合框切换

https://i-blog.csdnimg.cn/img_convert/24312f8c1ee9be2cf98448d538b067ff.png

https://i-blog.csdnimg.cn/img_convert/0e3c2a38b6c0ec6d69cb60373616b706.png

	int curIdx = this->productComboBoxControl.GetCurSel();
	CString curText;
	this->productComboBoxControl.GetLBText(curIdx, curText);

	productsVector products;
	StoreManager::readProducts(products);
	for (productsVector::iterator product = products.begin(); product != products.end(); ++product)
	{
		if (curText == product->name)
		{
			this->priceEditValue = product->price;
			this->numEditValue = 0;
			UpdateData(FALSE);
			break;
		}
	}

https://i-blog.csdnimg.cn/img_convert/e410ab2d266d6368bdbeb3127ac592d4.png

	this->OnCbnSelchangeCombo1();

https://i-blog.csdnimg.cn/img_convert/54e1861980c010265b8020463771ad63.png

调试运行

https://i-blog.csdnimg.cn/img_convert/ea5dda9b1c38ff43110db1ebcb6de1e0.png

确定按钮

双击 确定 按钮

	UpdateData(TRUE);

	if (this->numEditValue <= 0)
	{
		MessageBox(L"数量必须大于 0");
		return;
	}

	int curIdx = this->productComboBoxControl.GetCurSel();
	CString curText;
	this->productComboBoxControl.GetLBText(curIdx, curText);

	productsVector products;
	StoreManager::readProducts(products);
	for (productsVector::iterator product = products.begin(); product != products.end(); ++product)
	{
		if (curText == product->name)
		{
			product->stock -= this->numEditValue;
			productsVector tmpProduct = { *product };
			StoreManager::modifyProducts(tmpProduct);
			CString msg;
			msg.Format(L"删除商品:%s, 单价:%f,数量:%d", product->name.GetString(), product->price, this->numEditValue);
			MessageBox(msg.GetString());
			break;
		}
	}

	this->numEditValue = 0;
	UpdateData(FALSE);

https://i-blog.csdnimg.cn/img_convert/18bb0bef030160cc9c645c1974a8df1b.png

调试运行

https://i-blog.csdnimg.cn/img_convert/848f7bc1e18ae890bd366451178e4cba.png

取消按钮

双击 取消 按钮

	this->numEditValue = 0;
	UpdateData(FALSE);

https://i-blog.csdnimg.cn/img_convert/18896135041eb05a9e68922ca3d5bb8d.png

菜单栏

资源视图,Menu

https://i-blog.csdnimg.cn/img_convert/295ddba539320b6bb6c92bc0560d2410.png

删除帮助以外的所有菜单栏

https://i-blog.csdnimg.cn/img_convert/e813dedb0b6a50339766bbd9add7c20d.png

手动添加菜单

https://i-blog.csdnimg.cn/img_convert/901aa2a0bdd1a723ef065a1b52a79e4b.png

添加事件处理程序

个人信息

https://i-blog.csdnimg.cn/img_convert/caf8f021759b8619683786de1b81adf4.png

https://i-blog.csdnimg.cn/img_convert/f97e575dbc4a0a80ee06469649e146c9.png

	::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_USER, NM_USER, 0);

https://i-blog.csdnimg.cn/img_convert/5aed181984c8de30e1255efcdd1ada34.png

销售管理

https://i-blog.csdnimg.cn/img_convert/4d7fcaaf4d1b5e03903b482c7ee2f818.png

	::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_SELL, NM_SELL, 0);

https://i-blog.csdnimg.cn/img_convert/70ef6ab9481018a7a9afccee40943415.png

库存信息

https://i-blog.csdnimg.cn/img_convert/9ad76cb76eba662700dc48cf8f964d4c.png

	::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_INFO, NM_INFO, 0);

https://i-blog.csdnimg.cn/img_convert/ff16d2b309baad8d23a16b8ab940d47a.png

库存添加

https://i-blog.csdnimg.cn/img_convert/c5f4a90b313baea6f36f3ff79b576192.png

	::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_ADD, NM_ADD, 0);

https://i-blog.csdnimg.cn/img_convert/f1268cff2998cfdaf266be5ad59c64b6.png

库存删除

https://i-blog.csdnimg.cn/img_convert/8b2129345aed1bed94ac35f18fc5db07.png

	::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_DEL, NM_DEL, 0);

https://i-blog.csdnimg.cn/img_convert/fb90f12d16b6c30b1394ae456eecbcc9.png

这样就可以通过菜单进行切换了

https://i-blog.csdnimg.cn/img_convert/62861269ef4bc7a5eb8b0a8c02b9a0b1.png