VC++ + Oracle 实现汽配管理系统
内容整理的 完整技术指南,涵盖开发环境搭建、VC++ 与 Oracle 集成、核心语法知识点、自定义控件设计、数据库操作类封装及综合性案例。所有代码均附详细注释,适用于 Visual C++ 6.0 / Visual Studio(建议 VS2019/2022)+ Oracle 19c 开发场景。
一、开发环境安装与配置
1. 安装 Visual Studio(推荐 VS2019 或 VS2022)
- 下载地址:Visual Studio Community
- 安装时勾选 "使用 C++ 的桌面开发" 工作负载。
2. 安装 Oracle 数据库(Oracle 19c XE)
-
下载地址:Oracle Database XE
-
安装后创建用户:
sqlCREATE USER auto_parts IDENTIFIED BY ap_password; GRANT CONNECT, RESOURCE, DBA TO auto_parts;
3. 安装 Oracle Instant Client(用于 ODBC/OLE DB 连接)
-
- 下载
instantclient-basic-windows.x64.zip - 解压到
C:\oracle\instantclient_19_20
- 下载
-
配置系统环境变量:
bashPATH += C:\oracle\instantclient_19_20 TNS_ADMIN = C:\oracle\instantclient_19_20
4. 配置 ODBC 数据源(DSN)
- 打开 控制面板 → 管理工具 → ODBC 数据源(64位)
- 在"系统 DSN"中点击"添加"
- 选择 Oracle in instantclient_19_20
- 填写:
- Data Source Name:
AutoPartsDB - TNS Service Name:
XE(或你的服务名) - User ID:
auto_parts
- Data Source Name:
- 测试连接成功即可。
✅ 此 DSN 将在 VC++ 中通过 ADO 使用。
二、核心语法知识点与代码示例
知识点 1:VC++ 中使用 ADO 连接 Oracle(RxADO 类设计)
背景
ADO(ActiveX Data Objects)是 Windows 平台访问数据库的标准 COM 接口。通过 #import 引入 msado15.dll。
步骤 1:引入 ADO 库(在 stdafx.h 中)
cpp
// stdafx.h
#pragma once
// 其他 MFC 头文件...
// 引入 ADO 库(智能指针)
#import "C:\Program Files\Common Files\System\ado\msado15.dll" \
no_namespace rename("EOF", "EndOfFile")
⚠️ 路径可能因系统而异,可搜索
msado15.dll。
步骤 2:设计通用数据库操作类 CRxADO
cpp
// RxADO.h
#pragma once
#include <atlbase.h> // 用于 CComBSTR
class CRxADO {
private:
_ConnectionPtr m_pConnection; // 连接对象
_RecordsetPtr m_pRecordset; // 结果集对象
public:
CRxADO();
~CRxADO();
// 初始化连接
BOOL Connect(LPCTSTR lpszDSN, LPCTSTR lpszUser, LPCTSTR lpszPwd);
// 执行非查询 SQL(INSERT/UPDATE/DELETE)
BOOL ExecuteSQL(LPCTSTR lpszSQL);
// 执行查询,返回记录集
_RecordsetPtr GetRecordset(LPCTSTR lpszSQL);
// 关闭连接
void Close();
// 判断记录集是否为空
BOOL IsRecordsetEmpty(_RecordsetPtr pRs);
};
cpp
// RxADO.cpp
#include "stdafx.h"
#include "RxADO.h"
CRxADO::CRxADO() {
CoInitialize(NULL); // 初始化 COM
m_pConnection = NULL;
m_pRecordset = NULL;
}
CRxADO::~CRxADO() {
Close();
CoUninitialize(); // 释放 COM
}
BOOL CRxADO::Connect(LPCTSTR lpszDSN, LPCTSTR lpszUser, LPCTSTR lpszPwd) {
try {
m_pConnection.CreateInstance(__uuidof(Connection));
CString strConn;
strConn.Format(_T("Provider=OraOLEDB.Oracle;Data Source=%s;User ID=%s;Password=%s;"),
lpszDSN, lpszUser, lpszPwd);
m_pConnection->Open((LPCTSTR)strConn, "", "", adConnectUnspecified);
return TRUE;
}
catch (_com_error &e) {
AfxMessageBox(e.ErrorMessage());
return FALSE;
}
}
_RecordsetPtr CRxADO::GetRecordset(LPCTSTR lpszSQL) {
try {
m_pRecordset.CreateInstance(__uuidof(Recordset));
m_pRecordset->Open(lpszSQL, m_pConnection.GetInterfacePtr(),
adOpenStatic, adLockReadOnly, adCmdText);
return m_pRecordset;
}
catch (_com_error &e) {
AfxMessageBox(e.ErrorMessage());
return NULL;
}
}
BOOL CRxADO::ExecuteSQL(LPCTSTR lpszSQL) {
try {
m_pConnection->Execute(lpszSQL, NULL, adExecuteNoRecords);
return TRUE;
}
catch (_com_error &e) {
AfxMessageBox(e.ErrorMessage());
return FALSE;
}
}
void CRxADO::Close() {
if (m_pRecordset && m_pRecordset->State == adStateOpen)
m_pRecordset->Close();
if (m_pConnection && m_pConnection->State == adStateOpen)
m_pConnection->Close();
}
BOOL CRxADO::IsRecordsetEmpty(_RecordsetPtr pRs) {
return (pRs == NULL || pRs->EndOfFile);
}
✅ 使用
OraOLEDB.Oracle提供程序(需安装 Oracle Client)。
知识点 2:自定义按钮类 CBaseButton
目的
实现圆角、背景色、鼠标悬停效果的按钮。
cpp
// BaseButton.h
#pragma once
class CBaseButton : public CButton {
public:
CBaseButton();
virtual ~CBaseButton();
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnMouseLeave();
private:
BOOL m_bHover; // 是否鼠标悬停
CBrush m_brNormal, m_brHover;
};
cpp
// BaseButton.cpp
#include "stdafx.h"
#include "BaseButton.h"
CBaseButton::CBaseButton() : m_bHover(FALSE) {
m_brNormal.CreateSolidBrush(RGB(240, 240, 240)); // 默认灰色
m_brHover.CreateSolidBrush(RGB(255, 220, 180)); // 悬停橙色
}
CBaseButton::~CBaseButton() {}
BEGIN_MESSAGE_MAP(CBaseButton, CButton)
ON_WM_PAINT()
ON_WM_ERASEBKGND()
ON_WM_MOUSEMOVE()
ON_WM_MOUSELEAVE()
END_MESSAGE_MAP()
void CBaseButton::OnPaint() {
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
// 绘制圆角矩形背景
CBrush* pOldBrush = dc.SelectObject(m_bHover ? &m_brHover : &m_brNormal);
dc.RoundRect(rect, CPoint(10, 10)); // 圆角半径 10
dc.SelectObject(pOldBrush);
// 绘制文字
CString strText;
GetWindowText(strText);
dc.SetBkMode(TRANSPARENT);
dc.DrawText(strText, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
BOOL CBaseButton::OnEraseBkgnd(CDC* pDC) {
return TRUE; // 防止闪烁
}
void CBaseButton::OnMouseMove(UINT nFlags, CPoint point) {
if (!m_bHover) {
m_bHover = TRUE;
Invalidate(); // 重绘
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = m_hWnd;
TrackMouseEvent(&tme);
}
CButton::OnMouseMove(nFlags, point);
}
void CBaseButton::OnMouseLeave() {
m_bHover = FALSE;
Invalidate();
CButton::OnMouseLeave();
}
✅ 在对话框资源中,将普通 Button 控件的类型改为 "Owner Draw",并绑定到
CBaseButton。
知识点 3:扩展组合框 CBaseComboBox
功能:支持自动完成、下拉项高亮
cpp
// BaseComboBox.h
class CBaseComboBox : public CComboBox {
DECLARE_DYNAMIC(CBaseComboBox)
public:
CBaseComboBox();
virtual ~CBaseComboBox();
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnCbnEditupdate();
};
cpp
// BaseComboBox.cpp
IMPLEMENT_DYNAMIC(CBaseComboBox, CComboBox)
CBaseComboBox::CBaseComboBox() {}
CBaseComboBox::~CBaseComboBox() {}
BEGIN_MESSAGE_MAP(CBaseComboBox, CComboBox)
ON_CONTROL_REFLECT(CBN_EDITUPDATE, &CBaseComboBox::OnCbnEditupdate)
END_MESSAGE_MAP()
void CBaseComboBox::OnCbnEditupdate() {
// 可在此实现输入时自动筛选(简化版)
CString strInput;
GetWindowText(strInput);
// 实际项目中可遍历下拉项匹配前缀
}
更高级功能可结合
SetDroppedWidth()、SetItemHeight()等 API。
三、模块级实现示例
示例 1:系统登录模块
cpp
// LoginDlg.cpp
void CLoginDlg::OnBnClickedOk() {
UpdateData(TRUE); // 从控件获取数据
if (m_strUser.IsEmpty() || m_strPwd.IsEmpty()) {
AfxMessageBox(_T("用户名或密码不能为空!"));
return;
}
CRxADO db;
if (!db.Connect(_T("AutoPartsDB"), m_strUser, m_strPwd)) {
AfxMessageBox(_T("数据库连接失败!"));
return;
}
// 查询用户是否存在(假设用户表 USERS)
CString strSQL;
strSQL.Format(_T("SELECT COUNT(*) FROM USERS WHERE USERNAME='%s' AND PASSWORD='%s'"),
m_strUser, m_strPwd);
_RecordsetPtr rs = db.GetRecordset(strSQL);
if (!db.IsRecordsetEmpty(rs)) {
long count = rs->Fields->GetItem(_variant_t("COUNT(*)"))->Value.lVal;
if (count > 0) {
CDialogEx::OnOK(); // 登录成功
return;
}
}
AfxMessageBox(_T("用户名或密码错误!"));
}
⚠️ 实际项目应使用参数化查询防 SQL 注入(ADO 支持
_CommandPtr+ 参数)。
示例 2:商品信息查询(基础信息模块)
cpp
// GoodsQueryDlg.cpp
void CGoodsQueryDlg::LoadGoodsList() {
m_listCtrl.DeleteAllItems(); // 清空列表
CRxADO db;
if (!db.Connect(_T("AutoPartsDB"), _T("auto_parts"), _T("ap_password")))
return;
CString strSQL = _T("SELECT GOODS_ID, NAME, BRAND, PRICE FROM GOODS_INFO ORDER BY GOODS_ID");
_RecordsetPtr rs = db.GetRecordset(strSQL);
if (db.IsRecordsetEmpty(rs)) return;
int nIndex = 0;
while (!rs->EndOfFile) {
CString id = (LPCTSTR)(_bstr_t)rs->Fields->GetItem(_T("GOODS_ID"))->Value;
CString name = (LPCTSTR)(_bstr_t)rs->Fields->GetItem(_T("NAME"))->Value;
CString brand = (LPCTSTR)(_bstr_t)rs->Fields->GetItem(_T("BRAND"))->Value;
CString price = (LPCTSTR)(_bstr_t)rs->Fields->GetItem(_T("PRICE"))->Value;
m_listCtrl.InsertItem(nIndex, id);
m_listCtrl.SetItemText(nIndex, 1, name);
m_listCtrl.SetItemText(nIndex, 2, brand);
m_listCtrl.SetItemText(nIndex, 3, price);
rs->MoveNext();
nIndex++;
}
}
✅ 假设使用
CListCtrl控件,需设置报表视图(Report View)。
四、综合性案例:日常业务处理(入库 + 出库)
场景:商品入库登记
数据库表设计(简化)
sql
-- 商品表
CREATE TABLE GOODS_INFO (
GOODS_ID VARCHAR2(20) PRIMARY KEY,
NAME VARCHAR2(100),
BRAND VARCHAR2(50),
STOCK NUMBER DEFAULT 0
);
-- 入库记录表
CREATE TABLE STOCK_IN (
RECORD_ID NUMBER GENERATED ALWAYS AS IDENTITY,
GOODS_ID VARCHAR2(20),
QUANTITY NUMBER,
OPERATOR VARCHAR2(50),
IN_TIME DATE DEFAULT SYSDATE
);
VC++ 实现入库逻辑
cpp
void CStockInDlg::OnBnClickedBtnSave() {
UpdateData(TRUE);
if (m_strGoodsId.IsEmpty() || m_nQuantity <= 0) {
AfxMessageBox(_T("请填写完整信息!"));
return;
}
CRxADO db;
if (!db.Connect(_T("AutoPartsDB"), _T("auto_parts"), _T("ap_password"))) return;
// 1. 更新库存
CString sqlUpdate;
sqlUpdate.Format(_T("UPDATE GOODS_INFO SET STOCK = STOCK + %d WHERE GOODS_ID = '%s'"),
m_nQuantity, m_strGoodsId);
// 2. 插入入库记录
CString sqlInsert;
sqlInsert.Format(_T("INSERT INTO STOCK_IN (GOODS_ID, QUANTITY, OPERATOR) VALUES ('%s', %d, '%s')"),
m_strGoodsId, m_nQuantity, _T("admin"));
// 事务处理(简化:顺序执行)
if (db.ExecuteSQL(sqlUpdate) && db.ExecuteSQL(sqlInsert)) {
AfxMessageBox(_T("入库成功!"));
OnCancel(); // 关闭窗口
} else {
AfxMessageBox(_T("入库失败!"));
}
}
🔒 生产环境应使用 ADO 事务:
cppm_pConnection->BeginTrans(); // 执行多个 SQL m_pConnection->CommitTrans(); // 或 RollbackTrans()
五、小结:关键技术栈
| 技术点 | 说明 |
|---|---|
| ADO 编程 | 通过 _ConnectionPtr, _RecordsetPtr 操作 Oracle |
| Oracle OLE DB | 使用 OraOLEDB.Oracle 提供程序(需安装 Oracle Client) |
| 自定义控件 | 重绘 CButton、扩展 CComboBox 提升 UI 体验 |
| 数据库类封装 | CRxADO 实现连接、查询、执行统一管理 |
| MFC 对话框交互 | 结合 DoDataExchange、控件变量实现数据绑定 |
| 业务逻辑分层 | 登录、查询、业务处理模块解耦 |
六、附:初始化数据库脚本(Oracle)
sql
-- 用户表(登录用)
CREATE TABLE USERS (
USERNAME VARCHAR2(50) PRIMARY KEY,
PASSWORD VARCHAR2(50) NOT NULL,
REAL_NAME VARCHAR2(100)
);
INSERT INTO USERS VALUES ('admin', '123456', '系统管理员');
-- 商品信息表
CREATE TABLE GOODS_INFO (
GOODS_ID VARCHAR2(20) PRIMARY KEY,
NAME VARCHAR2(100) NOT NULL,
BRAND VARCHAR2(50),
SPEC VARCHAR2(100), -- 规格
UNIT VARCHAR2(10), -- 单位
PRICE NUMBER(10,2),
STOCK NUMBER DEFAULT 0
);
-- 日常业务:入库/出库
CREATE TABLE STOCK_RECORD (
RECORD_ID NUMBER GENERATED ALWAYS AS IDENTITY,
GOODS_ID VARCHAR2(20),
TYPE CHAR(1), -- 'I'=入库, 'O'=出库
QUANTITY NUMBER,
OPERATOR VARCHAR2(50),
RECORD_TIME DATE DEFAULT SYSDATE
);
💡 提示:
- 若使用 Visual Studio 2022,默认字符集为 Unicode,所有字符串使用
_T("")宏。- Oracle 字段名建议大写,避免大小写敏感问题。
- 生产系统务必使用参数化查询防止 SQL 注入。
如需提供 完整工程结构 、主窗体菜单设计 或 报表打印模块,可继续扩展。