预览
源链接:https://blog.iyatt.com/?p=19158
测试环境
- https://blog.iyatt.com/?p=18843#%E7%8E%AF%E5%A2%83
项目
参考黑马程序员的案例教程进行实践的记录,部分内容自行修改过,比如原案例直接读写文件保存账号、密码、数据,这里改为使用 SQLite3 数据库。
注意 VS 默认头文件扩展名用的 .h,我是喜欢在 C++ 中使用扩展名 .hpp,只要不是模板创建的代码部分,是我自己添加的都使用 .hpp 扩展名。
源码:下载地址见文首源链接
新建项目
新建一个 MFC 应用,项目名 saleSystem,应用程序类型选单个文档,项目样式选MFC standard
用户界面功能中经典菜单选项选无,再点完成进行创建
不使用安全函数
在项目名称上右键属性
展开配置属性->C/C++->代码生成,在安全检查中选禁用安全检查
添加窗口图标
将 user.ico 放到项目目录下的res目录里
切换到资源视图选项卡,展开上面的资源分支,在 Icon 上右键添加资源
点导入
选择图标文件
将ID改为IDI_ICON_WIN
在类视图下,CMainFrame类中的OnCreate方法里添加代码
// 加载图标HICON winIcon = AfxGetApp()->LoadIconW(IDI_ICON_WIN);// 设置小图标SetIcon(winIcon, FALSE);
按{F5}
调试运行,点是加载图标文件
运行效果
设置窗口大小和居中显示
在类视图下,CMainFrame类中的OnCreate方法里添加代码
// 设置位置(0,0)和窗口大小(800×600)MoveWindow(0, 0, 800, 600);// 居中显示CenterWindow();
设置窗口标题
在资源视图下,展开资源树,双击打开 String Table,在底部添加一个 ID 为ID_STRING_PROJECTNAME,值为销售系统
在类视图下CsaleSystemDoc类中OnNewDocument方法下添加代码
CString projectName;projectName.LoadStringW(ID_STRING_PROJECTNAME); // 导入字符串资源SetTitle(projectName); // 设置窗口标题
运行效果
设计 SQLite3 数据库读写实现
这里实现账号、密码、商品信息的读写,数据库采用 SQLite3,配置 SQLite3 环境参考:https://blog.iyatt.com/?p=19187
手动创建一个数据库文件
在项目目录下打开终端,执行命令打开数据库文件 saleSystem.sb(不存在会自动创建),打开后会处于命令交互模式
sqlite3 saleSystem.sqlite3
新建一张表用于存储账号、密码,并写入初始账号、密码(账号: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');
在创建一张表用于存储商品数据,并插入几条商品数据
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);
退出交互模式
.exit
读写实现
在类视图下,项目上右键添加->类
创建一个类,类名StoreManager
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;
可以把 C++ 中的 struct 看作是默认 public 的 class,这里析构函数需要 public,使用 struct 就不需要额外写一个public:,然后实例化一个对象,这样程序结束的时候就会自动调用这个析构函数,完成资源的释放。
登录对话框
界面设计
在资源视图下,Dialog上右键插入
将对话框 ID 改为 DIALOG_LOGIN
添加控件,并设置好描述文字,设置窗口标题
(使用 Static Text、Edit Control 和 Button)
在对话框空白处右键添加类
创建一个类 LoginDialog
为用户名编辑框创建变量usernameEditControl,访问选private
同样为密码编辑框创建变量passwordEditControl
功能实现
让登录对话框在文档之前创建
在类视图下,CsaleSystemApp 类中的 InitInstance 方法中
找到**CWinApp::InitInstance();**的位置,在它之前以模态的方式运行登录对话框。注意要引用对话框的头文件 LoginDialog.hpp。
然后判断返回值,后续实现中如果登录成功才会调用 OnOK,这边得到的返回值就是 IDOK,如果不是那就说明点击了取消登录或右上角的关闭按钮,以及其它操作,这时候就要直接返回,就不会创建后续文档页面。
LoginDialog loginDialog;if (loginDialog.DoModal() != IDOK){return TRUE;}
这时候调试运行就会先显示登录对话框,但是关闭登录对话框后会显示文档页面,后续还要处理这个逻辑,只有登录验证才应该显示后续的文档。
数据库连接
在资源视图下,StringTable 添加一个字段 DATABASE_PATH,设置 SQLite3 数据库文件的路径,这里就在项目目录下,使用相对路径
类视图下,在登录对话框类LoginDialog上右键属性
上面点击图标切换到重写页面,添加(重写) OnInitDialog
然后回到 OnInitDialog 函数中,添加连接数据库的代码。注意前面要引用数据库读写实现的头文件 StoreManager.hpp。
CString databasePath;databasePath.LoadStringW(DATABASE_PATH);StoreManager::connect(databasePath);
登录按钮回调实现
双击登录按钮会直接创建点击事件的回调
写入代码
// 读取用户输入的用户名和密码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"");}
调试运行
取消按钮回调实现
双击取消按钮,创建点击事件回调
点击取消调用取消方法
CDialogEx::OnCancel();
修改回车键为登录
对话框默认状态下按回车会调用 CDialogEx::OnOK(),这就会让登录验证成为摆设,相当于点击了登录对话框的 OK,那么接下来就会直接进入文档页面。
另外按照一般使用习惯回车键就是确认输入,这里也就是确认登录。
在类视图下,LoginDialog类上右键属性,重写对话框的 OnOK
注释掉CDialogEx::OnOK();,然后调用登录按钮点击事件的回调方法,这就按回车就等于是点击登录按钮。
this->OnBnClickedButton1();
静态拆分窗口
自定义视图类
在类视图中项目名上右键类向导
下拉MFC 类
类名 SelectView,基类CTreeView
同样再添加一个类DisplayView,基类CFormView
创建完上面两个类,在类向导页面点确定,编译在下面可能看到一堆 SelectView 头文件和源文件的报错
在头文件增加引用 #include “afxcview.h”,
再编译就好了
拆分窗口
在类视图中,点开 CMainFrame 类,声明一个对象
private:CSplitterWnd splitter;
在CMainFrame上右键属性
重写 OnCreateClient
把原来的返回注释了,重新写
注意需要引用 SelectView.hpp 和 DisplayView.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;
调试运行,登录后可以看到下图的布局
左侧树视图
添加功能节点
资源视图下,Icon右键添加资源
导入
选择 re.ico 文件
ID 重设为 IDI_ICON_RE
类视图下双击SelectView类,在类头文件中增加
private:CTreeCtrl* treeCtrl;CImageList imageList;
SelectView 类上右键属性
重写 OnInitialUpdate
写入
// 加载图标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);
运行登陆后
功能节点消息处理
查看 SelectView 类属性,添加消息TVN_SELCHANGED 的回调
写入
// 获取选中的项目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"库存删除"){}
个人信息页面
界面设计
资源视图下,Dialog上右键插入
ID改为DIALOG_USER,边框改为None,样式改为Child
绘制页面,原来的确定和取消按钮保留
(Group Box、Static Text、Edit Control)
在对话框空白处右键添加类
类名 UserDialog,基类CFormView
下面分别为 4 个编辑框创建变量
身份
变量名positionEditControl
用户名编辑框变量usernameEditControl
新密码编辑框变量newPasswordEditControl
确定密码编辑框变量confirmPasswordEditControl
功能实现
初始化界面
类视图中 UserDialog 类上右键属性
重写OnInitialUpdate
this->positionEditControl.SetWindowTextW(L"销售员");
确定修改密码
双击确定按钮,编辑确定按钮的单机事件回调,注意要引用 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"修改密码失败");}
取消修改密码
双击取消按钮
this->usernameEditControl.SetWindowTextW(L"");this->newPasswordEditControl.SetWindowTextW(L"");this->confirmPasswordEditControl.SetWindowTextW(L"");
界面挂载
自定义消息发送
在类视图中双击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;
添加自定义消息处理函数,头文件中添加声明
protected:afx_msg LRESULT onMyChange(WPARAM wParam, LPARAM lParam);
源文件中添加定义
afx_msg LRESULT CMainFrame::onMyChange(WPARAM wParam, LPARAM lParam)
{}
然后看到 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)
编辑 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);}
自定义消息处理
编辑 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;
调试运行效果
销售管理页面
界面设计
资源视图下插入新的对话框
ID改为DIALOG_SELL,边框改为None,样式改为Child
绘制界面,不删除原先的确定和取消按钮,确定的描述文字改成购买,商品名那里的 Combo Box 属性里的类型改为下拉列表,订单信息那里的大编辑框属性里多行、垂直滚动依次设置为True,数量编辑框属性里的数字改为True
(Group Box、Static Text、Edit Control、Combo Box)
在对话框空白处右键添加类
类名为SellDialog,基类为CFormView
为商品名组合框创建变量productNameComboBoxControl
为单价编辑框创建变量priceEditValue,注意类别选值,变量类型填 double
为数量编辑框创建变量numEditValue,类别选值,变量类型填 int
为订单信息编辑框创建变量sellEditControl
界面挂载
编辑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();
调试运行
功能实现
初始化界面
CellDialog类上右键属性,重写OnInitialUpdate方法
这里从数据库读取商品数据,把商品名设置给组合框,注意引用 StoreManager.hpp 头文件
productsVector products;StoreManager::readProducts(products);for (productStruct product : products){this->productNameComboBoxControl.AddString(product.name);}this->productNameComboBoxControl.SetCurSel(0);
调试运行
组合框切换刷新信息
在商品名组合框上右键属性
创建 CBN_SELCHANGE 事件的回调方法
写入
// 获取当前选中项的索引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;}}
另外在前面重写的 OnInitialUpdate 方法末尾调用一下,实现初始化
this->OnCbnSelchangeCombo1();
调试运行
购买实现
双击购买按钮,创建点击事件回调,写入
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;}}
调试运行效果
取消
双击取消按钮,创建事件回调
this->sellEditControl.SetWindowTextW(L"");this->numEditValue = 0;UpdateData(FALSE);
库存信息页面
界面设计
添加一个对话框
属性里,ID 设置为DIALOG_INFO,边框选None,样式选Child
绘制界面,删除确定和取消按钮,List Control 的视图改为Report
(Static Text、List Control)
对话框空白处右键添加类
类名InfoDialog,基类CFormView
在列表控件上右键添加变量infoListControl
界面挂载
编辑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();
调试运行
功能实现
在 InfoDialog 类上右键 属性
重写 OnInitialUpdate 方法
注意引用 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;}
调试运行
库存添加页面
界面设计
添加一个对话框
ID 改为 DIALOG_ADD,边框改为None,样式改为Child
绘制界面,删除原来的确定和取消按钮,个数编辑框属性的数字设置为True,Combo Box 属性类型选下拉列表,库存单价编辑库属性只读设置为True
(Static Text、Group Box、Edit Control、Button、Combo Box)
在对话框空白处右键添加类AddDialog,基类CFormView
为组合框和编辑框添加变量
库存商品编辑框,变量名stockProductComboBoxControl,访问private
库存价格编辑框,变量名stockPriceEditValue,类别值,访问private,变量类型double
库存个数编辑框,变量名stockNumEditValue,类别值,访问private,变量类型int
新商品编辑框,变量名newProductEditValue,类别值,访问private
新商品单价编辑框,变量名newPriceEditValue,类别值,访问private,变量类型double
新商品库存编辑框,变量名newNumEditValue,类别值,访问private,变量类型int
界面挂载
编辑 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();
调试运行
功能实现
初始化库存组合框
重写 OnInitialUpdate 方法
注意引用 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);
调试运行
库存组合框切换事件回调
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;}}
在 OnInitialUpdate 里调用这个方法,保证初始化的时候正确设置
this->OnCbnSelchangeCombo2();
调试运行
添加库存
双击添加库存按钮
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);
调试运行
取消库存设置
双击取消库存设置按钮
this->stockNumEditValue = 0;UpdateData(FALSE);
添加新商品
双击添加新商品按钮
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());
调试运行
取消商品设置
双击取消商品设置按钮
this->newProductEditValue.Empty();this->newPriceEditValue = 0;this->newNumEditValue = 0;UpdateData(FALSE);
库存删除页面
界面设计
添加一个对话款
ID 改为DIALOG_DEL,边框改为None,样式改为Child
绘制界面,保留确定和取消按钮,Combo Box 类型改为下拉列表,数量编辑框属性数字设置为True,设置单价编辑框只读
(Group Box、Static Text、Combo Box、Edit Control)
为对话框添加类DelDialog,基类CFormView
为组合框和编辑框添加变量
商品名 productComboBoxControl
单价 priceEditValue
数量 numEditValue
界面挂载
编辑 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();
调试运行
功能实现
初始化界面
重写 OnInitialUpdate 方法
注意引用 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);
调试运行
组合框切换
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;}}
this->OnCbnSelchangeCombo1();
调试运行
确定按钮
双击确定按钮
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);
调试运行
取消按钮
双击取消按钮
this->numEditValue = 0;UpdateData(FALSE);
菜单栏
资源视图,Menu
删除帮助以外的所有菜单栏
手动添加菜单
添加事件处理程序
个人信息
::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_USER, NM_USER, 0);
销售管理
::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_SELL, NM_SELL, 0);
库存信息
::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_INFO, NM_INFO, 0);
库存添加
::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_ADD, NM_ADD, 0);
库存删除
::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), NM_DEL, NM_DEL, 0);
这样就可以通过菜单进行切换了